propiedades - ¿Cómo hacer un diccionario de Python que devuelve clave para las claves que faltan en el diccionario en lugar de subir KeyError?
diccionario dentro de diccionario python (6)
Quiero crear un diccionario de Python que me devuelva el valor clave para las teclas que faltan en el diccionario.
Ejemplo de uso:
dic = smart_dict()
dic[''a''] = ''one a''
print(dic[''a''])
# >>> one a
print(dic[''b''])
# >>> b
¿Por qué no solo usas
dic.get(''b'', ''b'')
Claro, usted puede subclase como lo señalan otros, pero me resulta útil recordarme de vez en cuando que get
puede tener un valor predeterminado.
Si quieres probar el defaultdict
, prueba esto:
dic = defaultdict()
dic.__missing__ = lambda key: key
dic[''b''] # should set dic[''b''] to ''b'' and return ''b''
excepto ... bien: AttributeError: ^collections.defaultdict^object attribute ''__missing__'' is read-only
, por lo que tendrá que subclase:
from collections import defaultdict
class KeyDict(defaultdict):
def __missing__(self, key):
return key
d = KeyDict()
print d[''b''] #prints ''b''
print d.keys() #prints []
El primer encuestado mencionó el defaultdict
, pero puede definir __missing__
para cualquier subclase de dict
:
>>> class Dict(dict):
def __missing__(self, key):
return key
>>> d = Dict(a=1, b=2)
>>> d[''a'']
1
>>> d[''z'']
''z''
Además, me gusta el enfoque del segundo encuestado:
>>> d = dict(a=1, b=2)
>>> d.get(''z'', ''z'')
''z''
Estoy de acuerdo en que esto debería ser fácil de hacer, y también fácil de configurar con diferentes valores predeterminados o funciones que transforman un valor perdido de alguna manera.
Inspirado por la answer , me pregunté: ¿por qué no tener el generador predeterminado (ya sea una constante o invocable) como miembro de la clase, en lugar de generar diferentes clases todo el tiempo? Déjame demostrar:
# default behaviour: return missing keys unchanged
dic = FlexDict()
dic[''a''] = ''one a''
print(dic[''a''])
# ''one a''
print(dic[''b''])
# ''b''
# regardless of default: easy initialisation with existing dictionary
existing_dic = {''a'' : ''one a''}
dic = FlexDict(existing_dic)
print(dic[''a''])
# ''one a''
print(dic[''b''])
# ''b''
# using constant as default for missing values
dic = FlexDict(existing_dic, default = 10)
print(dic[''a''])
# ''one a''
print(dic[''b''])
# 10
# use callable as default for missing values
dic = FlexDict(existing_dic, default = lambda missing_key: missing_key * 2)
print(dic[''a''])
# ''one a''
print(dic[''b''])
# ''bb''
print(dic[2])
# 4
¿Como funciona? No es tan difícil:
class FlexDict(dict):
''''''Subclass of dictionary which returns a default for missing keys.
This default can either be a constant, or a callable accepting the missing key.
If "default" is not given (or None), each missing key will be returned unchanged.''''''
def __init__(self, content = None, default = None):
if content is None:
super().__init__()
else:
super().__init__(content)
if default is None:
default = lambda missing_key: missing_key
self.default = default # sets self._default
@property
def default(self):
return self._default
@default.setter
def default(self, val):
if callable(val):
self._default = val
else: # constant value
self._default = lambda missing_key: val
def __missing__(self, x):
return self.default(x)
Por supuesto, uno puede debatir si uno quiere permitir el cambio de la función por defecto después de la inicialización, pero eso solo significa eliminar @default.setter
y absorber su lógica en __init__
.
Permitir la introspección en el valor predeterminado actual (constante) podría agregarse con dos líneas adicionales.
Subclass dict
''s __getitem__
method. Por ejemplo, cómo subclasificar adecuadamente el dictado y anular __getitem__ & __setitem__
dict
tienen un gancho __missing__
para esto:
class smart_dict(dict):
def __missing__(self, key):
return key
Felicidades. Usted también ha descubierto la inutilidad del tipo standard collections.defaultdict
. Si ese execrable montón de códigos maliciosos ofende tus delicadas sensibilidades tanto como a mí, este es tu día afortunado de .
Gracias a la maravilla prohibida de la variante de 3 parámetros del type()
incorporado, crear un tipo de diccionario predeterminado no inútil es a la vez divertido y rentable.
¿Qué está mal con dict .__ missing __ ()?
Absolutamente nada, suponiendo que le guste el exceso de repetición y la escandalosa sorpresa de collections.defaultdict
, que debería comportarse como se esperaba pero realmente no. Para ser justos, la solución aceptada de de subclassing dict
y la implementación del __missing__()
opcional __missing__()
es una solución fantástica para casos de uso a pequeña escala que solo requieren un único diccionario predeterminado.
Pero el estándar de este tipo escasea pobremente. Si se encuentra instanciando múltiples diccionarios predeterminados, cada uno con su propia lógica ligeramente diferente para generar los pares clave-valor que faltan, se requiere una repetición automatizada alternativa de fuerza industrial.
O al menos agradable. Porque, ¿por qué no arreglar lo que está roto?
Presentamos DefaultDict
En menos de diez líneas de Python puro (excluyendo docstrings, comentarios y espacios en blanco), ahora definimos un tipo de DefaultDict
inicializado con valores predeterminados de generación invocable definidos por el usuario para claves faltantes. Mientras que el invocable pasado al estándar collections.defaultdict
type acepta inútilmente ningún parámetro, el invocable pasado a nuestro tipo DefaultDict
acepta de manera útil los siguientes dos parámetros:
- La instancia actual de este diccionario.
- La clave faltante actual para generar un valor predeterminado para.
Dado este tipo, resolver la pregunta de reduce a una sola línea de Python:
>>> dic = DefaultDict(lambda self, missing_key: missing_key)
>>> dic[''a''] = ''one a''
>>> print(dic[''a''])
one a
>>> print(dic[''b''])
b
Cordura. Al final.
Código o no sucedió
def DefaultDict(keygen):
''''''
Sane **default dictionary** (i.e., dictionary implicitly mapping a missing
key to the value returned by a caller-defined callable passed both this
dictionary and that key).
The standard :class:`collections.defaultdict` class is sadly insane,
requiring the caller-defined callable accept *no* arguments. This
non-standard alternative requires this callable accept two arguments:
#. The current instance of this dictionary.
#. The current missing key to generate a default value for.
Parameters
----------
keygen : CallableTypes
Callable (e.g., function, lambda, method) called to generate the default
value for a "missing" (i.e., undefined) key on the first attempt to
access that key, passed first this dictionary and then this key and
returning this value. This callable should have a signature resembling:
``def keygen(self: DefaultDict, missing_key: object) -> object``.
Equivalently, this callable should have the exact same signature as that
of the optional :meth:`dict.__missing__` method.
Returns
----------
MappingType
Empty default dictionary creating missing keys via this callable.
''''''
# Global variable modified below.
global _DEFAULT_DICT_ID
# Unique classname suffixed by this identifier.
default_dict_class_name = ''DefaultDict'' + str(_DEFAULT_DICT_ID)
# Increment this identifier to preserve uniqueness.
_DEFAULT_DICT_ID += 1
# Dynamically generated default dictionary class specific to this callable.
default_dict_class = type(
default_dict_class_name, (dict,), {''__missing__'': keygen,})
# Instantiate and return the first and only instance of this class.
return default_dict_class()
_DEFAULT_DICT_ID = 0
''''''
Unique arbitrary identifier with which to uniquify the classname of the next
:func:`DefaultDict`-derived type.
''''''
La clave ... entiéndelo, ¿ clave ? para esta magia arcana es la llamada a la variante de 3 parámetros del type()
incorporado:
type(default_dict_class_name, (dict,), {''__missing__'': keygen,})
Esta única línea genera dinámicamente una nueva subclase dict
aliasing el método __missing__
opcional para el invocable definido por la persona que llama. Tenga en cuenta la clara falta de repetición, reduciendo el uso de DefaultDict
a una sola línea de Python.
Automatización para la victoria atroz.