2009-09-21 6 views
87

Je suis en train de le faire:Pourquoi un python dict.update() ne renvoie-t-il pas l'objet?

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 

Mais si vraiment senti lourd dans la fonction, et je l'aurais plutôt fait:

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

Pourquoi ne met pas à jour retourner l'objet afin que vous puissiez chaîne?

JQuery fait cela pour chaîner. Pourquoi n'est-ce pas acceptable en python?

Répondre

154

Python mise en œuvre de la plupart du temps une saveur pragmatiquement teintés de command-query separation: muteurs retour None (avec des exceptions telles que pragmatiquement induites pop ;-) donc ils ne peuvent pas être confondus avec accesseurs (et dans le même ordre d'idées, la cession n'est pas une expression , la séparation expression-expression est là, et ainsi de suite). Cela ne veut pas dire qu'il n'y a pas beaucoup de façons de fusionner les choses quand vous voulez vraiment, par exemple, dict(a, **award_dict) rend une nouvelle dict beaucoup comme celle que vous semblez souhaiter .update retourné - alors pourquoi ne pas utiliser QUE si tu sens vraiment que c'est important?

Modifier: BTW, pas besoin, dans votre cas particulier, de créer a le long du chemin, soit:

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

crée un seul dict avec exactement la même sémantique que votre a.update(award_dict) (y compris, cas de conflit, le fait que les entrées de award_dict remplacent celles que vous donnez explicitement, pour obtenir les autres sémantiques, par exemple, d'avoir des entrées explicites « gagnantes » de tels conflits, passer award_dict comme seul position arg, avant la ke yword ones, et dépourvu du formulaire ** - dict(award_dict, name=name etc etc).

+0

Eh bien, cela va créer un autre dictionnaire après que je devais faire un. Je voulais créer un dict, puis ajouter un tas d'autres valeurs, puis le donner à une fonction. –

+0

@Paul, et c'est exactement ce que vous faites - avec deux déclarations (beaucoup plus lisibles que la manière imbriquée que vous vouliez) qui vous "se sentaient vraiment lourdes". Modification de ma réponse pour montrer comment éviter de créer 'a' tout à fait, btw, –

+12

' dict (a, ** award_dict) 'rock juste et était exactement ce que je voulais-merci pour cela! (utilisait 'dict (d1.items() + d2.items()) 'before) –

3

Ce n'est pas que ce n'est pas acceptable, mais plutôt que dicts n'ont pas été mis en œuvre de cette façon.

Si vous regardez l'ORM de Django, il utilise beaucoup le chaînage. Ce n'est pas déconseillé, vous pouvez même hériter de dict et seulement remplacer update pour faire la mise à jour et return self, si vous le voulez vraiment.

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

Merci, cela pourrait déranger dict, je voulais juste savoir pourquoi dict() ne permet pas cette fonctionnalité elle-même (car il est aussi facile que vous le démontrez.) Est-ce que Django patch dict comme ça? –

28

L'API de Python, par convention, fait la distinction entre les procédures et les fonctions. Les fonctions calculent de nouvelles valeurs à partir de leurs paramètres (y compris tout objet cible); les procédures modifient les objets et ne retournent rien (c'est-à-dire qu'ils retournent None). Les procédures ont donc des effets secondaires, mais pas les fonctions. update est une procédure, donc elle ne renvoie pas de valeur. La motivation pour le faire de cette façon est que sinon, vous pouvez obtenir des effets secondaires indésirables. Considérez

bar = foo.reverse() 

Si inverse (qui inverse la liste en place) redonnerait aussi la liste, les utilisateurs peuvent penser que inverse retourne une nouvelle liste qui obtient attribué à la barre, et ne jamais remarquer que foo obtient également modifié. En faisant un retour inversé Aucun, ils reconnaissent immédiatement que la barre n'est pas le résultat de l'inversion, et regarderont de plus près ce que l'effet inverse est.

+1

Merci. t reverse donne aussi la possibilité de ne pas le faire inplace? Performance? doing 'reverse (foo)' se sent bizarre –

+0

une option serait inappropriée: elle changerait la nature de la méthode en fonction d'un paramètre. Cependant, les méthodes devraient vraiment avoir des types de retour fixes (il y a, malheureusement, des cas où cette règle est brisée). Il est facile de créer une copie inversée: il suffit de faire une copie (en utilisant 'bar = foo [:]'), puis de rétablir la copie. –

+2

Je pense que la raison en est l'explicitation. Dans 'bar = foo.reverse()', vous pourriez penser que 'foo' n'est pas modifié. Pour éviter toute confusion, vous avez à la fois 'foo.reverse()' et '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} 

Notez qu'en plus de renvoyer le dict fusionné, il modifie le premier paramètre in-situ. Donc dict_merge (a, b) va modifier a.

Ou, bien sûr, vous pouvez tout faire en ligne:

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

-1 'lambda' ne devrait pas être utilisé comme ça, au lieu d'utiliser la fonction conventionnelle' def' à la place – jamylak

+2

N'a même pas besoin d'un lambda, juste utiliser 'a.update (b) ou a' – Pycz

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

réputation ne suffit pas pour commentaire laissé sur réponse

@beardc cela ne semble pas être Chose CPython. PyPy me donne « TypeError: mots-clés doivent être des chaînes »

La solution **kwargs ne fonctionne que parce que le dictionnaire à fusionner uniquement a clés de type string.

-à-dire

>>> 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

aussi près de votre solution proposée que je pourrais obtenir

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

juste essayé moi-même en Python 3.4 (n'a donc pas été capable d'utiliser la syntaxe fantaisie {**dict_1, **dict_2}). Je voulais pouvoir avoir des clés non-chaînes dans les dictionnaires et fournir une quantité arbitraire de dictionnaires.

Aussi, je voulais faire un nouveau dictionnaire donc je choisi de ne pas utiliser collections.ChainMap (un peu la raison pour laquelle je ne voulais pas utiliser dict.update initialement

Voici ce que je fini par écrire:.

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} 
Questions connexes