2009-09-21 28 views
87

No estoy tratando de hacer:¿Por qué un pitton dict.update() no devuelve el objeto?

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 

Pero si se sentía muy incómodo en la función, y me hubiera gustado hacer:

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

¿Por qué no se actualiza devolver el objeto para que pueda ¿cadena?

JQuery hace esto para encadenar. ¿Por qué no es aceptable en Python?

Respuesta

154

de la mayoría de la implementación de un sabor pragmáticamente teñida de command-query separation Python: mutators vuelven None (con excepciones pragmáticamente inducidos como pop ;-) así que no pueden posiblemente ser confundidos con descriptores de acceso (y en el mismo orden de ideas, la asignación no es una expresión , la separación expresión-expresión está allí, y así sucesivamente).

Eso no significa que no hay muchas formas de combinar las cosas cuando realmente lo desea, por ejemplo, dict(a, **award_dict) hace una nueva dicción muy parecida a la que parece desear .update devuelta, entonces ¿por qué no usar ESO? si realmente sientes que es importante?

Editar: por cierto, no es necesario, en su caso específico, para crear a lo largo del camino, ya sea:

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

crea un único dict con exactamente la misma semántica que su a.update(award_dict) (incluyendo, en caso de conflictos, el hecho de que las entradas en award_dict sustituyan a los que está dando de forma explícita, para obtener los demás semántica, es decir, que tenga entradas explícitas "ganadores" este tipo de conflictos, pasar award_dict como el único posicional arg, antes de la ke y uno, y sin el formulario ** - dict(award_dict, name=name etc, etc.).

+0

Bueno, eso creará otro diccionario después de que tuve que hacer un. Quería crear un dict, y luego agregar muchos otros valores y luego darle una función. –

+0

@Paul, y eso es exactamente lo que está haciendo, con dos afirmaciones (mucho más legibles que la forma anidada que quería) que para usted "se sentía realmente engorroso". La edición de mi respuesta para mostrar cómo evitar la creación de 'a' por completo, por cierto, –

+12

' dict (a, ** award_dict) 'solo me gustó y era exactamente lo que quería, ¡gracias por eso! (estaba usando 'dict (d1.items() + d2.items()) 'before) –

3

No es que no sea aceptable, sino que dicts no se implementaron de esa manera.

Si mira el ORM de Django, hace un uso extensivo del encadenamiento. No está desaconsejado, incluso puede heredar desde dict y solo anular update para actualizar y return self, si realmente lo desea.

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

Gracias, esto podría parchar un dict, solo quería saber por qué dict() no permitió esta funcionalidad en sí misma (ya que es tan fácil como lo demuestra). ¿Django parche dict así? –

28

API de Python, por convención, se distingue entre procedimientos y funciones. Las funciones calculan nuevos valores a partir de sus parámetros (incluido cualquier objeto de destino); los procedimientos modifican objetos y no devuelven nada (es decir, devuelven None). Entonces los procedimientos tienen efectos secundarios, las funciones no. la actualización es un procedimiento, por lo tanto, no devuelve un valor.

La motivación para hacerlo de esa manera es que de lo contrario, puede obtener efectos secundarios indeseables. Considere

bar = foo.reverse() 

Si inverso (que invierte la lista en contexto) también devolverá la lista, los usuarios pueden pensar que inversa devuelve una nueva lista en la que se le asigna a la barra, y nunca darse cuenta de que fu también es modificado. Al hacer que Invertir devuelva Ninguno, inmediatamente reconocen que la barra no es el resultado de la inversión, y se verán más cerca de cuál es el efecto de invertir.

+1

Gracias. ¿Por qué? t reverse también dan la opción de no hacerlo en el lugar? Performance? doing 'reverse (foo)' se siente raro. –

+0

Adding una opción sería inapropiada: cambiaría la naturaleza del método dependiendo de un parámetro. Sin embargo, los métodos realmente deberían tener tipos de devolución fijos (desafortunadamente, los casos en los que esta regla está quebrada). Es fácil crear una copia revertida: simplemente haga una copia (usando 'bar = foo [:]'), luego invierta la copia. –

+2

Creo que la razón es explícita. En 'bar = foo.reverse()', podrías pensar que 'foo' no se modifica. Para evitar confusiones, tiene 'foo.reverse()' y 'bar = invertido (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} 

Tenga en cuenta que, además de devolver el dict fusionado, modifica el primer parámetro en contexto. Entonces dict_merge (a, b) modificará a.

O, por supuesto, usted puede hacerlo todo en línea:

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

-1 'lambda' no debe usarse así, en su lugar use la función convencional' def' en su lugar – jamylak

+2

Ni siquiera necesita una lambda, solo use 'a.update (b) o a' – Pycz

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

no es suficiente reputación para hacer comentarios a la izquierda en la parte superior respuesta

@beardc esto no parece ser Lo de CPython. PyPy me da "TypeError: palabras clave deben ser cadenas"

La solución con **kwargs sólo funciona porque el diccionario que se fusionó sólo tiene claves de tipo cadena.

es decir

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

lo más cercano a la solución propuesta como pude

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

Sólo estado tratando esto mismo en Python 3.4 (lo que no era capaz de usar la elegante sintaxis {**dict_1, **dict_2}).

Quería poder tener claves que no sean cadenas en los diccionarios, así como proporcionar una cantidad arbitraria de diccionarios.

Además, quería hacer un nuevo diccionario así que opté por no utilizar collections.ChainMap (un poco la razón por la que no quiero usar dict.update inicialmente

Esto es lo que terminó escribiendo:.

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} 
Cuestiones relacionadas