valor propiedades diccionarios diccionario dentro declarar declaracion crear consultar como clave buscar acceder python dictionary data-structures dictionary-missing

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.



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:

  1. La instancia actual de este diccionario.
  2. 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.