2009-09-21 21 views
87

I 'm cercando di fare:Perché un python dict.update() non restituisce l'oggetto?

award_dict = { 
    "url" : "http://facebook.com", 
    "imageurl" : "http://farm4.static.flickr.com/3431/3939267074_feb9eb19b1_o.png", 
    "count" : 1, 
} 

def award(name, count, points, desc_string, my_size, parent) : 
    if my_size > count : 
     a = { 
      "name" : name, 
      "description" : desc_string % count, 
      "points" : points, 
      "parent_award" : parent, 
     } 
     a.update(award_dict) 
     return self.add_award(a, siteAlias, alias).award 

Ma se sentiva davvero ingombrante nella funzione, e avrei preferito fare:

 return self.add_award({ 
      "name" : name, 
      "description" : desc_string % count, 
      "points" : points, 
      "parent_award" : parent, 
     }.update(award_dict), siteAlias, alias).award 

Perché non aggiorna restituire l'oggetto in modo da poter catena?

JQuery fa questo per fare concatenamento. Perché non è accettabile in Python?

risposta

154

di attuazione principalmente un sapore pragmaticamente tinta di command-query separation Python: mutatori ritornano None (con così essi non possono essere confusi con funzioni di accesso pragmaticamente indotti eccezioni come pop ;-) (e nella stessa vena, assegnazione non è un'espressione , la separazione espressione-espressione è lì, e così via).

questo non significa che non ci sono un sacco di modi per unire le cose quando si vuole veramente, per esempio, dict(a, **award_dict) fa un nuovo dict molto simile a quella in cui sembra voler .update restituito - quindi perché non utilizzare tale se pensi davvero che sia importante?

Edit: btw, non c'è bisogno, nel caso specifico, per creare a lungo la strada, o:

dict(name=name, description=desc % count, points=points, parent_award=parent, 
    **award_dict) 

crea un unico dict con esattamente la stessa semantica tua a.update(award_dict) (tra cui, in caso di conflitti, il fatto che le voci in award_dict sovrascrivono quelli che stai dando esplicitamente, per ottenere le altre semantiche, cioè, di avere le voci esplicite "vincente" tali conflitti, passare award_dict come unico posizionale arg, prima la ke yword ones e privato del modulo ** - dict(award_dict, name=name ecc. ecc.).

+0

Bene, questo creerà un altro dizionario dopo che dovevo fare un. Volevo creare un dict, quindi aggiungere un gruppo di altri valori e quindi assegnarlo a una funzione. –

+0

@Paul, ed è esattamente quello che stai facendo - con due affermazioni (molto più leggibili del modo in cui nidificavi desiderato) che per te "sono state davvero ingombranti". Modifica la mia risposta per mostrare come evitare di creare 'a' del tutto, btw, –

+12

' dict (a, ** award_dict) 'solo sassi ed era esattamente quello che volevo - grazie per questo! (stava usando 'dict (d1.items() + d2.items()) 'before) –

3

Non è che non sia accettabile, ma piuttosto che dicts non sono stati implementati in questo modo.

Se si guarda all'ORM di Django, fa ampio uso di concatenamento. Non è scoraggiato, potresti anche ereditare da dict e sostituire solo update per fare l'aggiornamento e return self, se lo vuoi davvero.

class myDict(dict): 
    def update(self, *args): 
     dict.update(self, *args) 
     return self 
+0

Grazie, questo potrebbe doppiare dict, volevo solo sapere perché dict() non ha permesso questa funzionalità stessa (dal momento che è facile come si dimostra). La patch di Django dice in questo modo? –

28

API Python, per convenzione, distingue tra procedure e funzioni. Le funzioni calcolano i nuovi valori fuori dai loro parametri (incluso qualsiasi oggetto obiettivo); le procedure modificano gli oggetti e non restituiscono nulla (cioè restituiscono None). Quindi le procedure hanno effetti collaterali, le funzioni no. update è una procedura, quindi non restituisce un valore.

La motivazione per farlo in questo modo è che in caso contrario, si potrebbero ottenere effetti collaterali indesiderati. Considerare

bar = foo.reverse() 

Se inversa (che inverte l'elenco sul posto) avrebbe anche restituire la lista, gli utenti possono pensare che inversa restituisce un nuovo elenco che viene assegnato a bar, e mai notare che foo anche viene modificato. Effettuando il ritorno inverso Nessuno, riconoscono immediatamente che la barra non è il risultato dell'inversione e guarderà più da vicino quale sia l'effetto del contrario.

+1

Grazie. Perché wouldn ' t reverse anche dare la possibilità di non farlo inplace? Performance? doing 'reverse (foo)' sembra strano –

+0

Aggiunta un'opzione sarebbe inappropriata: cambierebbe la natura del metodo a seconda di un parametro. Tuttavia, i metodi dovrebbero in realtà avere tipi di rendimento corretti (purtroppo ci sono casi in cui questa regola non funziona). È facile creare una copia ripristinata: basta fare una copia (usando 'bar = pippo [:]'), quindi ripristinare la copia. –

+2

Penso che la ragione sia esplicita. In 'bar = foo.reverse()', si potrebbe pensare che 'foo' non sia modificato. Per evitare confusione, hai sia 'foo.reverse()' che 'bar = reverse (foo)'. –

9
>>> dict_merge = lambda a,b: a.update(b) or a 
>>> dict_merge({'a':1, 'b':3},{'c':5}) 
{'a': 1, 'c': 5, 'b': 3} 

Si noti che, così come la restituzione del dict fusione, modifica il primo parametro sul posto. Quindi dict_merge (a, b) modificherà a.

O, naturalmente, si può fare tutto in linea:

>>> (lambda a,b: a.update(b) or a)({'a':1, 'b':3},{'c':5}) 
{'a': 1, 'c': 5, 'b': 3} 
+8

-1 'lambda' non deve essere usato in questo modo, usa invece la funzione convenzionale' def' invece – jamylak

+2

Non hai nemmeno bisogno di un lambda, usa solo 'a.update (b) o a' – Pycz

0
import itertools 
dict_merge = lambda *args: dict(itertools.chain(*[d.iteritems() for d in args])) 
3

non abbastanza reputazione per il commento lasciato sulla risposta superiore

@beardc questo non sembra essere Cosa CPython. PyPy mi dà "TypeError: le parole chiave devono essere stringhe"

La soluzione con **kwargs funziona solo perché il dizionario da unire solo ha chiavi di tipo stringa.

cioè

>>> dict({1:2}, **{3:4}) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: keyword arguments must be strings 

vs

>>> dict({1:2}, **{'3':4}) 
{1: 2, '3': 4} 
1

più vicino al tuo soluzione proposta, come ho potuto ottenere

from collections import ChainMap 

return self.add_award(ChainMap(award_dict, { 
    "name" : name, 
    "description" : desc_string % count, 
    "points" : points, 
    "parent_award" : parent, 
}), siteAlias, alias).award 
0

Proprio cercato io stesso in Python 3.4 (quindi non era in grado di utilizzare la sintetica fantasia {**dict_1, **dict_2}).

Volevo essere in grado di avere chiavi non stringa nei dizionari oltre a fornire una quantità arbitraria di dizionari.

Inoltre, ho voluto fare un nuovo dizionario così ho optato per non utilizzare collections.ChainMap (kinda la ragione per cui non volevo usare dict.update inizialmente

Ecco quello che ho finito per scrivere:.

def merge_dicts(*dicts): 
    all_keys = set(k for d in dicts for k in d.keys()) 
    chain_map = ChainMap(*reversed(dicts)) 
    return {k: chain_map[k] for k in all_keys} 

merge_maps({'1': 1}, {'2': 2, '3': 3}, {'1': 4, '3': 5}) 
# {'1': 4, '3': 5, '2': 2} 
Problemi correlati