2010-08-14 21 views
144

La adición de collections.defaultdict en Python 2.5 redujo en gran medida la necesidad del método dictsetdefault. Esta pregunta es para nuestra educación colectiva:Casos de uso para el método dict 'setdefault'

  1. ¿Qué es setdefault todavía útil para hoy en Python 2.6/2.7?
  2. ¿Qué casos de uso popular de setdefault se reemplazaron con collections.defaultdict?
+1

Ligeramente relacionados demasiado http://stackoverflow.com/questions/7423428/python-dict-get-vs- setdefault/22326645 – Medorator

Respuesta

156

Se podría decir defaultdict es útil para los ajustes por defecto antes de llenar el dict y setdefault es útil para la configuración de ajustes mientras o después de llenar el dict.

probablemente el caso de uso más común: Agrupación de elementos (en datos no ordenados, bien utilizar itertools.groupby)

# really verbose 
new = {} 
for (key, value) in data: 
    if key in new: 
     new[key].append(value) 
    else: 
     new[key] = [value] 


# easy with setdefault 
new = {} 
for (key, value) in data: 
    group = new.setdefault(key, []) # key might exist already 
    group.append(value) 


# even simpler with defaultdict 
new = defaultdict(list) 
for (key, value) in data: 
    new[key].append(value) # all keys have a default already 

A veces se quieren asegurarse de que no existen teclas específicas después de crear un diccionario. defaultdict no funciona en este caso, porque solo crea claves en acceso explícito. Piensa que utilice algo HTTP-ish con muchas cabeceras - algunos son opcionales, pero que desea por defecto para ellos:

headers = parse_headers(msg) # parse the message, get a dict 
# now add all the optional headers 
for headername, defaultvalue in optional_headers: 
    headers.setdefault(headername, defaultvalue) 
+0

De hecho, este mi humilde opinión es el principal caso de uso para el reemplazo por 'defaultdict'. ¿Puedes dar un ejemplo de lo que significas en el primer párrafo? –

+0

No haría eso para el último ejemplo. ¿Por qué no usar 'headers = dict (optional_headers); headers.update (parse_headers (msg)) 'o incluso un' defaultdict' para encabezados antes de usar 'update'? –

+0

Muhammad Alkarouri: Lo primero que debes hacer es copiar el dict y luego sobrescribir algunos de los elementos. También hago mucho eso y creo que ese es el idioma que más prefieren a 'setdefault'. Un 'defaultdict' por otro lado no funcionaría si no todos los' defaultvalues' son iguales (es decir, algunos son '0' y algunos son' [] '). –

5

Teóricamente hablando, setdefault todavía sería útil si veces desea establecer un valor predeterminado y, a veces no. En la vida real, no he encontrado ese caso de uso.

Sin embargo, un caso de uso interesante surge de la biblioteca estándar (Python 2.6, _threadinglocal.py):

>>> mydata = local() 
>>> mydata.__dict__ 
{'number': 42} 
>>> mydata.__dict__.setdefault('widgets', []) 
[] 
>>> mydata.widgets 
[] 

, diría que el uso de __dict__.setdefault es un caso bastante útil.

Editar: Como es el caso, este es el único ejemplo en la biblioteca estándar y está en un comentario. Entonces puede ser que no es suficiente justificar la existencia de setdefault. Aún así, aquí hay una explicación:

Los objetos almacenan sus atributos en el atributo __dict__. Como sucede, el atributo __dict__ es grabable en cualquier momento después de la creación del objeto. También es un diccionario no es defaultdict. No es sensato que los objetos en el caso general tengan __dict__ como defaultdict porque eso haría que cada objeto tenga todos los identificadores legales como atributos. Por lo tanto, no puedo prever ningún cambio en los objetos de Python que eliminen __dict__.setdefault, además de eliminarlo por completo si se considera que no es útil.

+0

¿Podría explicarnos qué hace __dict_.setdefault particularmente útil? –

+1

@Eli: Creo que el punto es que '__dict__' es por implementación un' dict', no un 'defaultdict'. – katrielalex

+0

katrielalex tiene razón. Expandiré la respuesta a una explicación más clara más adelante. –

24

que usan comúnmente para setdefault predice argumento de palabra clave, como en esta función:

def notify(self, level, *pargs, **kwargs): 
    kwargs.setdefault("persist", level >= DANGER) 
    self.__defcon.set(level, **kwargs) 
    try: 
     kwargs.setdefault("name", self.client.player_entity().name) 
    except pytibia.PlayerEntityNotFound: 
     pass 
    return _notify(level, *pargs, **kwargs) 

Es muy bueno para ajustar argumentos en envolturas alrededor de las funciones que toman argumentos de palabra clave.

13

defaultdict es excelente cuando el valor predeterminado es estático, como una nueva lista, pero no tanto si es dinámico.

Por ejemplo, necesito un diccionario para asignar cadenas a ints únicas. defaultdict(int) siempre usará 0 para el valor predeterminado. Del mismo modo, defaultdict(intGen()) produce siempre 1.

En su lugar, utiliza un diccionario normal:

nextID = intGen() 
myDict = {} 
for lots of complicated stuff: 
    #stuff that generates unpredictable, possibly already seen str 
    strID = myDict.setdefault(myStr, nextID()) 

Tenga en cuenta que dict.get(key, nextID()) es insuficiente porque tengo que ser capaz de hacer referencia a estos valores más adelante también.

intGen es una clase pequeña construyo que incrementa automáticamente un int y devuelve su valor:

class intGen: 
    def __init__(self): 
     self.i = 0 

    def __call__(self): 
     self.i += 1 
    return self.i 

Si alguien tiene una manera de hacer esto con defaultdict Me encantaría verlo.

+0

para una forma de hacerlo con (una subclase de) defaultdict, mira esta pregunta: http://stackoverflow.com/questions/2912231/is-there-a-clever-way-to -pass-the-key-to-defaultdicts-default-factory – weronika

+6

Puede reemplazar 'intGen' con' itertools.count(). next'. – Antimony

+5

'nextID()' el valor se incrementará cada vez 'myDict. se llama a setdefault() ', incluso si el valor que devuelve no se usa como' strID'. Esto parece un desperdicio de alguna manera e ilustra una de las cosas que no me gustan de 'setdefault()' en general, a saber, que siempre evalúa su argumento 'default' independientemente de si realmente se usa. – martineau

6

Como dijo Muhammad, hay situaciones en las que a veces solo deseas establecer un valor predeterminado. Un gran ejemplo de esto es una estructura de datos que primero se rellena y se consulta.

Considere un trie. Al agregar una palabra, si se necesita un subnodo pero no está presente, se debe crear para extender el trie. Al consultar la presencia de una palabra, un subnodo faltante indica que la palabra no está presente y no debe crearse.

Un defaultdict no puede hacer esto. En su lugar, se debe usar un dict regular con los métodos get y setdefault.

9

Uso setdefault() cuando quiero un valor predeterminado en OrderedDict. No hay una colección estándar de Python que haga ambas cosas, pero hay areways para implementar dicha colección.

2

Éstos son algunos ejemplos de setdefault para mostrar su utilidad:

""" 
d = {} 
# To add a key->value pair, do the following: 
d.setdefault(key, []).append(value) 

# To retrieve a list of the values for a key 
list_of_values = d[key] 

# To remove a key->value pair is still easy, if 
# you don't mind leaving empty lists behind when 
# the last value for a given key is removed: 
d[key].remove(value) 

# Despite the empty lists, it's still possible to 
# test for the existance of values easily: 
if d.has_key(key) and d[key]: 
    pass # d has some values for key 

# Note: Each value can exist multiple times! 
""" 
e = {} 
print e 
e.setdefault('Cars', []).append('Toyota') 
print e 
e.setdefault('Motorcycles', []).append('Yamaha') 
print e 
e.setdefault('Airplanes', []).append('Boeing') 
print e 
e.setdefault('Cars', []).append('Honda') 
print e 
e.setdefault('Cars', []).append('BMW') 
print e 
e.setdefault('Cars', []).append('Toyota') 
print e 

# NOTE: now e['Cars'] == ['Toyota', 'Honda', 'BMW', 'Toyota'] 
e['Cars'].remove('Toyota') 
print e 
# NOTE: it's still true that ('Toyota' in e['Cars']) 
1

utilizo con frecuencia cuando setdefault, conseguir esto, el establecimiento de un defecto (!!!) en un diccionario; algo comúnmente el diccionario os.environ:

# Set the venv dir if it isn't already overridden: 
os.environ.setdefault('VENV_DIR', '/my/default/path') 

Menos de manera sucinta, esto se parece a esto:

# Set the venv dir if it isn't already overridden: 
if 'VENV_DIR' not in os.environ: 
    os.environ['VENV_DIR'] = '/my/default/path') 

Vale la pena señalar que también se puede utilizar la variable resultante:

venv_dir = os.environ.setdefault('VENV_DIR', '/my/default/path') 

Pero eso es menos necesario de lo que era antes de que existieran los valores predeterminados.

1

Otro caso de uso que no creo que se haya mencionado anteriormente. A veces mantiene un caché de objetos por su id, donde la instancia principal está en la memoria caché y desea establecer la caché cuando falta.

return self.objects_by_id.setdefault(obj.id, obj) 

Esto es útil cuando siempre quiere mantener una sola instancia por identificación distinta, sin importar cómo obtenga un obj cada vez. Por ejemplo, cuando los atributos del objeto se actualizan en la memoria y se pospone el almacenamiento en el almacenamiento.

0

[Editar] ¡Muy mal! setdefault siempre activará long_computation, Python está ansioso.

Ampliando la respuesta de Tuttle. Para mí, el mejor caso de uso es el mecanismo de caché. En lugar de:

if x not in memo: 
    memo[x]=long_computation(x) 
return memo[x] 

que consume 3 líneas y 2 o 3 operaciones de búsqueda, volvería a escribir :

return memo.setdefault(x, long_computation(x)) 
+0

Buen ejemplo. Sigo pensando que las 3 líneas son más comprensibles, pero quizás mi cerebro aprecie setdefault. –

+3

Esos no son equivalentes. En el primero, 'long_computation (x)' solo se llama si 'x not in memo'.Mientras que en el segundo, siempre se llama 'long_computation (x)'. Solo la asignación es condicional, el código equivalente a 'setdefault' se vería así:' v = long_computation (x) '/' si x no está en la nota: '/' memo [x] = v'. –

+0

@DanD. ¡Gracias! Demasiado perezoso en ese momento! – YvesgereY

0

Cuando el valor predeterminado requerido no es siempre el mismo, o que sólo ha deseado para teclas específicas, sino que es preferible no tener uno para los demás, se podría considerar el uso de setdefault:

d = {} 
... 
# `i` should default to zero 
i = d.setdefault(key, 0) 
... 
# `s` should default to an empty string 
s = d.setdefault(key, '') 
... 

 

d = {} 
... 
# v should always default to a list 
v = d.setdefault(key, []) 
... 
try: 
    # EAFP, but I need the dict to raise a KeyError if the key is not found. 
    w = d[k2] 
except KeyError: 
    ... 
... 
2

Un inconveniente probable de defaultdict sobre dict (dict.setdefault) es que un objeto defaultdict crea un nuevo elemento cada vez se da clave no existente (por ejemplo, con print, ==). También la clase defaultdict es mucho menos común que la clase dict (serialización, representación, etc.).

P.S. Las funciones de IMO (métodos) que no están destinadas a mutar un objeto, no deberían mutar un objeto.

+0

No tiene que crear un nuevo objeto cada vez. En su lugar, puede hacer 'defaultdict (lambda l = []: l)'. – Artyer

+2

Nunca haga lo que sugiere @Artyer: los valores predeterminados mutables le morderán. –

1

Un caso de uso muy importante que acabo de tropezar: dict.setdefault() es ideal para código de subprocesos múltiples cuando solo se quiere un único objeto canónico (a diferencia de varios objetos que son iguales).

Por ejemplo, el (Int)Flag Enum in Python 3.6.0 has a bug: si múltiples hilos están compitiendo por un material compuesto (Int)Flag miembro, puede llegar a ser más de uno:

from enum import IntFlag, auto 
import threading 

class TestFlag(IntFlag): 
    one = auto() 
    two = auto() 
    three = auto() 
    four = auto() 
    five = auto() 
    six = auto() 
    seven = auto() 
    eight = auto() 

    def __eq__(self, other): 
     return self is other 

    def __hash__(self): 
     return hash(self.value) 

seen = set() 

class cycle_enum(threading.Thread): 
    def run(self): 
     for i in range(256): 
      seen.add(TestFlag(i)) 

threads = [] 
for i in range(8): 
    threads.append(cycle_enum()) 

for t in threads: 
    t.start() 

for t in threads: 
    t.join() 

len(seen) 
# 272 (should be 256) 

La solución es utilizar setdefault() como el último paso de salvar el miembro compilado calculado: si ya se ha guardado otro, se usa en lugar del nuevo, lo que garantiza miembros exclusivos de Enum.

0

El caso de uso diferente para setdefault() es cuando no desea sobrescribir el valor de una clave ya configurada. defaultdict sobrescribe, mientras que setdefault() no lo hace. En el caso de los diccionarios anidados, es más frecuente que desee establecer un valor predeterminado solo si la clave aún no está establecida, porque no desea eliminar el presente sub Diccionario. Esto es cuando usas setdefault().

Ejemplo con defaultdict:

>>> from collection import defaultdict() 
>>> foo = defaultdict() 
>>> foo['a'] = 4 
>>> foo['a'] = 2 
>>> print(foo) 
defaultdict(None, {'a': 2}) 

setdefault no sobrescribe:

>>> bar = dict() 
>>> bar.setdefault('a', 4) 
>>> bar.setdefault('a', 2) 
>>> print(bar) 
{'a': 4} 
Cuestiones relacionadas