functions - dictionary method python
Casos de uso para el método dict ''setdefault'' (16)
Aquí hay 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''])
La adición de collections.defaultdict
en Python 2.5 redujo en gran medida la necesidad del método setdefault
dict
. Esta pregunta es para nuestra educación colectiva:
- ¿
setdefault
qué sirvesetdefault
todavía, hoy en Python 2.6 / 2.7? - ¿Qué casos de uso popular de
setdefault
se reemplazaron concollections.defaultdict
?
Comúnmente uso setdefault
para los setdefault
de 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 genial para ajustar argumentos en envoltorios alrededor de funciones que toman argumentos de palabra clave.
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.
Considera 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 incumplimiento no puede hacer esto. En su lugar, se debe usar un dict regular con los métodos get y setdefault.
Cuando el valor predeterminado requerido no es siempre el mismo, o solo se desea para claves específicas, pero se prefiere no tener uno para los demás, se puede 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:
...
...
El caso de uso diferente para setdefault()
es cuando no desea sobreescribir el valor de una clave ya establecida. 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}
La lógica de dict.get
es:
if key in a_dict:
value = a_dict[key]
else:
value = default_value
Tome un ejemplo:
In [72]: a_dict = {''mapping'':[''dict'', ''OrderedDict''], ''array'':[''list'', ''tuple'']}
In [73]: a_dict.get(''string'', [''str'', ''bytes''])
Out[73]: [''str'', ''bytes'']
In [74]: a_dict.get(''array'', [''str'', ''byets''])
Out[74]: [''list'', ''tuple'']
El mecamismo de setdefault
es:
levels = [''master'', ''manager'', ''salesman'', ''accountant'', ''assistant'']
#group them by the leading letter
group_by_leading_letter = {}
# the logic expressed by obvious if condition
for level in levels:
leading_letter = level[0]
if leading_letter not in group_by_leading_letter:
group_by_leading_letter[leading_letter] = [level]
else:
group_by_leading_letter[leading_letter].append(word)
In [80]: group_by_leading_letter
Out[80]: {''a'': [''accountant'', ''assistant''], ''m'': [''master'', ''manager''], ''s'': [''salesman'']}
El método setdefault dict es precisamente para este propósito. El ciclo for anterior se puede reescribir como sigue:
In [87]: for level in levels:
...: leading = level[0]
...: group_by_leading_letter.setdefault(leading,[]).append(level)
Out[80]: {''a'': [''accountant'', ''assistant''], ''m'': [''master'', ''manager''], ''s'': [''salesman'']}
Es muy simple, significa que una lista no nula agrega un elemento o una lista nula anexa un elemento.
El defaultdict
, que hace que esto sea aún más fácil. Para crear uno, pasas un tipo o función para generar el valor predeterminado para cada ranura en el dict:
from collections import defualtdict
group_by_leading_letter = defaultdict(list)
for level in levels:
group_by_leading_letter[level[0]].append(level)
Me gusta la respuesta dada aquí:
http://stupidpythonideas.blogspot.com/2013/08/defaultdict-vs-setdefault.html
En resumen, la decisión (en aplicaciones que no son críticas para el rendimiento) debe tomarse sobre la base de cómo se desea manejar la búsqueda de claves vacías en sentido descendente (por KeyError
versus valor predeterminado).
Otro caso de uso que no creo que se haya mencionado anteriormente. A veces, conservas un caché de objetos con su id. Donde la instancia principal está en el caché y quieres configurar el caché cuando falta.
return self.objects_by_id.setdefault(obj.id, obj)
Eso es útil cuando siempre desea 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.
Se podría decir que el valor predeterminado es útil para los valores predeterminados de configuración antes de llenar el dict y setdefault
es útil para establecer los valores predeterminados mientras o después de llenar el dict .
Probablemente el caso de uso más común: agrupar elementos (en datos no ordenados, sino usar 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 querrás asegurarte de que existen claves específicas después de crear un dict. defaultdict
no funciona en este caso, ya que solo crea claves en acceso explícito. Creo que usas algo HTTP-ish con muchos encabezados, algunos son opcionales, pero quieres los predeterminados 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 )
Teóricamente hablando, setdefault
aún sería útil si a veces desea establecer un valor predeterminado y otras 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
[]
Yo diría que usar __dict__.setdefault
es un caso bastante útil.
Editar : sucede que este es el único ejemplo en la biblioteca estándar y está en un comentario. Entonces puede ser que no sea un caso suficiente para 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 escribible en cualquier momento después de la creación del objeto. También es un diccionario, no un defaultdict
. No es sensato que los objetos en el caso general tengan __dict__
como un 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 __dict__.setdefault
, además de eliminarlo por completo si no se considera útil.
Un caso de uso muy importante que acabo de tropezar: dict.setdefault()
es ideal para código de subprocesos múltiples cuando solo quieres un único objeto canónico (a diferencia de varios objetos que pasan a ser iguales).
Por ejemplo, el (Int)Flag
Enum en Python 3.6.0 tiene un error : si hay varios hilos compitiendo por un miembro compuesto (Int)Flag
, puede terminar siendo 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 usar setdefault()
como el último paso para guardar el miembro compuesto calculado: si ya se ha guardado otro, se usa en lugar del nuevo, lo que garantiza miembros exclusivos de Enum.
Un posible inconveniente de defaultdict
over dict
( dict.setdefault
) es que un objeto defaultdict
crea un nuevo elemento cada vez que se da una 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.).
Las funciones PSO IMO (métodos) no destinados a mutar un objeto, no deberían mutar un objeto.
Uso setdefault con frecuencia cuando, obtengo esto, establezco un valor predeterminado (!!!) en un diccionario; algo común el diccionario os.environ:
# Set the venv dir if it isn''t already overridden:
os.environ.setdefault(''VENV_DIR'', ''/my/default/path'')
De manera menos sucinta, esto se ve así:
# 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 puede usar 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.
Yo uso setdefault()
cuando quiero un valor predeterminado en un OrderedDict
. No hay una colección estándar de Python que haga ambas cosas, pero hay ways de implementar dicha colección.
[Editar] Muy mal! Setdefault siempre activaría 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 búsquedas, felizmente escribiría :
return memo.setdefault(x, long_computation(x))
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 únicos. defaultdict(int)
siempre usará 0 para el valor predeterminado. Del mismo modo, defaultdict(intGen())
siempre produce 1.
En cambio, usé un dict regular:
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 también necesito poder referirme a estos valores más adelante.
intGen
es una pequeña clase I build que automáticamente incrementa 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 forma de hacer esto con un defaultdict
, me encantaría verlo.