tuple keys empty dictionaries create array python dictionary syntax

python - keys - ¿Accediendo a las claves de dict como un atributo?



python empty dictionary (23)

Me parece más conveniente acceder a las teclas dict como obj.foo lugar de obj[''foo''] , así que escribí este fragmento:

class AttributeDict(dict): def __getattr__(self, attr): return self[attr] def __setattr__(self, attr, value): self[attr] = value

Sin embargo, asumo que debe haber alguna razón por la que Python no proporciona esta funcionalidad de manera inmediata. ¿Cuáles serían las advertencias y las dificultades de acceder a las claves de dict de esta manera?


¿Cuáles serían las advertencias y las dificultades de acceder a las claves de dict de esta manera?

Como sugiere @Henry, una razón por la que el acceso de puntos no se puede usar en los dicts es que limita los nombres de las claves de dictado a las variables válidas para python, restringiendo así todos los nombres posibles.

Los siguientes son ejemplos de por qué el acceso por puntos no sería útil en general, dado un dict, d :

Validez

Los siguientes atributos no serían válidos en Python:

d.1_foo # enumerated names d./bar # path names d.21.7, d.12:30 # decimals, time d."" # empty strings d.john doe, d.denny''s # spaces, misc punctuation d.3 * x # expressions

Estilo

Las convenciones PEP8 impondrían una restricción suave en la denominación de atributos:

A. Nombres de keyword reservadas (o función incorporada):

d.in d.False, d.True d.max, d.min d.sum d.id

Si el nombre de un argumento de función choca con una palabra clave reservada, generalmente es mejor agregar un solo guión bajo ...

B. La regla de caso sobre methods y nombres de variables :

Los nombres de variables siguen la misma convención que los nombres de funciones.

d.Firstname d.Country

Utilice las reglas de nomenclatura de funciones: minúsculas con palabras separadas por guiones bajos según sea necesario para mejorar la legibilidad.

A veces, estas preocupaciones se plantean en bibliotecas como pandas , que permiten el acceso punteado de las columnas de DataFrame por nombre. El mecanismo predeterminado para resolver las restricciones de nombres también es la notación de matriz, una cadena entre paréntesis.

Si estas restricciones no se aplican a su caso de uso, hay varias opciones en las estructuras de datos de acceso punteado .


Donde contesto la pregunta que fue hecha

¿Por qué Python no lo ofrece fuera de la caja?

Sospecho que tiene que ver con el Zen de Python : "Debe haber una, y preferiblemente solo una, manera obvia de hacerlo". Esto crearía dos formas obvias de acceder a los valores de los diccionarios: obj[''key''] y obj.key .

Advertencias y escollos

Estos incluyen la posible falta de claridad y confusión en el código. es decir, lo siguiente podría ser confuso para alguien que va a mantener su código en una fecha posterior, o incluso para usted, si no va a volver a usarlo durante un tiempo. De nuevo, desde Zen : "¡La legibilidad cuenta!

>>> KEY = ''spam'' >>> d[KEY] = 1 >>> # Several lines of miscellaneous code here... ... assert d.spam == 1

Si d se crea una instancia o se define KEY o d[KEY] se asigna lejos de donde se usa d.spam , puede generar confusión sobre lo que se está haciendo, ya que este no es un lenguaje de uso común. Sé que tendría el potencial de confundirme.

Además, si cambia el valor de KEY siguiente manera (pero no cambia d.spam ), ahora obtiene:

>>> KEY = ''foo'' >>> d[KEY] = 1 >>> # Several lines of miscellaneous code here... ... assert d.spam == 1 Traceback (most recent call last): File "<stdin>", line 2, in <module> AttributeError: ''C'' object has no attribute ''spam''

OMI, no vale la pena el esfuerzo.

Otros elementos

Como han señalado otros, puedes usar cualquier objeto hashable (no solo una cadena) como clave de dictado. Por ejemplo,

>>> d = {(2, 3): True,} >>> assert d[(2, 3)] is True >>>

es legal, pero

>>> C = type(''C'', (object,), {(2, 3): True}) >>> d = C() >>> assert d.(2, 3) is True File "<stdin>", line 1 d.(2, 3) ^ SyntaxError: invalid syntax >>> getattr(d, (2, 3)) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: getattr(): attribute name must be string >>>

no es. Esto le da acceso a toda la gama de caracteres imprimibles u otros objetos hashable para sus claves de diccionario, que no tiene cuando accede a un atributo de objeto. Esto hace posible tal magia como una metaclase de objetos en caché, como la receta del Libro de cocina de Python (Capítulo 9) .

En donde yo editorialize

Prefiero la estética del spam.eggs sobre el spam[''eggs''] (creo que se ve más limpio), y realmente empecé a desear esta funcionalidad cuando me reuní con la namedtuple . Pero la conveniencia de poder hacer lo siguiente lo supera.

>>> KEYS = ''spam eggs ham'' >>> VALS = [1, 2, 3] >>> d = {k: v for k, v in zip(KEYS.split('' ''), VALS)} >>> assert d == {''spam'': 1, ''eggs'': 2, ''ham'': 3} >>>

Este es un ejemplo simple, pero con frecuencia me encuentro usando dictados en situaciones diferentes de las que usaría la notación obj.key (es decir, cuando necesito leer las preferencias de un archivo XML). En otros casos, cuando estoy tentado a crear una instancia de una clase dinámica y abofetear algunos atributos por razones estéticas, continúo usando un dict para la coherencia con el fin de mejorar la legibilidad.

Estoy seguro de que el OP desde hace mucho tiempo lo resolvió para su satisfacción, pero si todavía desea esta funcionalidad, le sugiero que descargue uno de los paquetes de pypi que lo proporciona:

  • Bunch es con el que estoy más familiarizado. Subclase de dict , para que tengas toda esa funcionalidad.
  • AttrDict también parece que es bastante bueno, pero no estoy tan familiarizado con él y no he revisado la fuente con tanto detalle como Bunch .
  • Como se señala en los comentarios de Rotareti, Bunch ha quedado en desuso, pero hay una bifurcación activa llamada Munch .

Sin embargo, para mejorar la legibilidad de su código, le recomiendo encarecidamente que no mezcle sus estilos de notación. Si prefiere esta notación, simplemente debe crear una instancia de un objeto dinámico, agregarle sus atributos deseados y llamarlo día:

>>> C = type(''C'', (object,), {}) >>> d = C() >>> d.spam = 1 >>> d.eggs = 2 >>> d.ham = 3 >>> assert d.__dict__ == {''spam'': 1, ''eggs'': 2, ''ham'': 3}

Dónde actualizo, para responder una pregunta de seguimiento en los comentarios

En los comentarios (abajo), Elmo pregunta:

¿Y si quieres profundizar más? (refiriéndose al tipo (...))

Aunque nunca he usado este caso de uso (nuevamente, tiendo a usar dict anidado, por coherencia), el siguiente código funciona:

>>> C = type(''C'', (object,), {}) >>> d = C() >>> for x in ''spam eggs ham''.split(): ... setattr(d, x, C()) ... i = 1 ... for y in ''one two three''.split(): ... setattr(getattr(d, x), y, i) ... i += 1 ... >>> assert d.spam.__dict__ == {''one'': 1, ''two'': 2, ''three'': 3}


¿Qué __eq__ si desea una clave que sea un método, como __eq__ o __getattr__ ?

Y no podría tener una entrada que no comenzara con una letra, por lo que usar 0343853 como clave no está disponible.

¿Y si no quisieras usar una cuerda?


¿Qué hay de Prodict , la pequeña clase de Python que escribí para gobernarlos a todos :)

Además, obtienes la finalización automática del código , las instancias recursivas de objetos y la conversión automática de tipos .

Puedes hacer exactamente lo que pediste:

p = Prodict() p.foo = 1 p.bar = "baz"

Ejemplo 1: Tipo de insinuación

class Country(Prodict): name: str population: int turkey = Country() turkey.name = ''Turkey'' turkey.population = 79814871

Ejemplo 2: conversión automática de tipo

germany = Country(name=''Germany'', population=''82175700'', flag_colors=[''black'', ''red'', ''yellow'']) print(germany.population) # 82175700 print(type(germany.population)) # <class ''int''> print(germany.flag_colors) # [''black'', ''red'', ''yellow''] print(type(germany.flag_colors)) # <class ''list''>


Aparentemente, ahora hay una biblioteca para esto, pypi.python.org/pypi/attrdict , que implementa esta funcionalidad exacta más la fusión recursiva y la carga json. Podría valer la pena un vistazo.


Aquí hay un breve ejemplo de registros inmutables que usan collections.namedtuple incorporadas.

def record(name, d): return namedtuple(name, d.keys())(**d)

y un ejemplo de uso:

rec = record(''Model'', { ''train_op'': train_op, ''loss'': loss, }) print rec.loss(..)



Como señaló Doug, hay un paquete Bunch que puede usar para lograr la funcionalidad obj.key . En realidad hay una nueva versión llamada

NeoBunch

Sin embargo, tiene una gran característica que convierte su dictado en un objeto NeoBunch a través de su función neobunchify . Utilizo mucho las plantillas de Mako y el paso de datos como objetos NeoBunch los hace mucho más legibles, así que si terminas usando un dict normal en tu programa de Python pero quieres la notación de puntos en una plantilla de Mako, puedes usarlo de esa manera:

from mako.template import Template from neobunch import neobunchify mako_template = Template(filename=''mako.tmpl'', strict_undefined=True) data = {''tmpl_data'': [{''key1'': ''value1'', ''key2'': ''value2''}]} with open(''out.txt'', ''w'') as out_file: out_file.write(mako_template.render(**neobunchify(data)))

Y la plantilla de Mako podría verse como:

% for d in tmpl_data: Column1 Column2 ${d.key1} ${d.key2} % endfor


Creé esto basado en la entrada de este hilo. Necesito usar odict, así que tuve que anular get y set attr. Creo que esto debería funcionar para la mayoría de los usos especiales.

El uso se ve así:

# Create an ordered dict normally... >>> od = OrderedAttrDict() >>> od["a"] = 1 >>> od["b"] = 2 >>> od OrderedAttrDict([(''a'', 1), (''b'', 2)]) # Get and set data using attribute access... >>> od.a 1 >>> od.b = 20 >>> od OrderedAttrDict([(''a'', 1), (''b'', 20)]) # Setting a NEW attribute only creates it on the instance, not the dict... >>> od.c = 8 >>> od OrderedAttrDict([(''a'', 1), (''b'', 20)]) >>> od.c 8

La clase:

class OrderedAttrDict(odict.OrderedDict): """ Constructs an odict.OrderedDict with attribute access to data. Setting a NEW attribute only creates it on the instance, not the dict. Setting an attribute that is a key in the data will set the dict data but will not create a new instance attribute """ def __getattr__(self, attr): """ Try to get the data. If attr is not a key, fall-back and get the attr """ if self.has_key(attr): return super(OrderedAttrDict, self).__getitem__(attr) else: return super(OrderedAttrDict, self).__getattr__(attr) def __setattr__(self, attr, value): """ Try to set the data. If attr is not a key, fall-back and set the attr """ if self.has_key(attr): super(OrderedAttrDict, self).__setitem__(attr, value) else: super(OrderedAttrDict, self).__setattr__(attr, value)

Este es un patrón bastante bueno ya mencionado en el hilo, pero si solo desea tomar un dictado y convertirlo en un objeto que funcione con autocompletar en un IDE, etc.

class ObjectFromDict(object): def __init__(self, d): self.__dict__ = d


De esta otra pregunta SO, hay un gran ejemplo de implementación que simplifica su código existente. Qué tal si:

class AttributeDict(dict): __getattr__ = dict.__getitem__ __setattr__ = dict.__setitem__

Mucho más conciso y no deja ningún espacio para un viaje extra en sus funciones __getattr__ y __setattr__ en el futuro.


Esta no es una respuesta "buena", pero pensé que era ingeniosa (no maneja los dicts anidados en la forma actual). Simplemente envuelva su dictado en una función:

def make_funcdict(d={}, **kwargs) def funcdict(d={}, **kwargs): funcdict.__dict__.update(d) funcdict.__dict__.update(kwargs) return funcdict.__dict__ funcdict(d, **kwargs) return funcdict

Ahora tienes una sintaxis ligeramente diferente. Para acceder a los elementos dict como atributos haz f.key . Para acceder a los elementos de dictado (y otros métodos de dictado) de la manera habitual, haga f()[''key''] y podemos actualizar el dictado de manera conveniente llamando a f con argumentos de palabras clave y / o un diccionario.

Ejemplo

d = {''name'':''Henry'', ''age'':31} d = make_funcdict(d) >>> for key in d(): ... print key ... age name >>> print d.name ... Henry >>> print d.age ... 31 >>> d({''Height'':''5-11''}, Job=''Carpenter'') ... {''age'': 31, ''name'': ''Henry'', ''Job'': ''Carpenter'', ''Height'': ''5-11''}

Y ahí está. Estaré feliz si alguien sugiere beneficios e inconvenientes de este método.


Esto no responde a la pregunta original, pero debería ser útil para personas que, como yo, terminan aquí cuando buscan una biblioteca que proporciona esta funcionalidad.

Addict es una gran biblioteca para esto: https://github.com/mewwts/addict se ocupa de muchas de las preocupaciones mencionadas en las respuestas anteriores.

Un ejemplo de la documentación:

body = { ''query'': { ''filtered'': { ''query'': { ''match'': {''description'': ''addictive''} }, ''filter'': { ''term'': {''created_by'': ''Mats''} } } } }

Con adicto:

from addict import Dict body = Dict() body.query.filtered.query.match.description = ''addictive'' body.query.filtered.filter.term.created_by = ''Mats''


La mejor manera de hacer esto es:

class AttrDict(dict): def __init__(self, *args, **kwargs): super(AttrDict, self).__init__(*args, **kwargs) self.__dict__ = self

Algunos pros:

  • ¡Realmente funciona!
  • Ningún método de clase de diccionario está sombreado (por ejemplo, .keys() funciona bien)
  • Los atributos y elementos siempre están sincronizados
  • Intentar acceder a la clave no existente como atributo correctamente genera AttributeError lugar de KeyError

Contras:

  • Métodos como .keys() no funcionarán bien si se sobrescriben con los datos entrantes
  • Causa una pérdida de memoria en Python <2.7.4 / Python3 <3.2.3
  • Pylint se vuelve loco con E1123(unexpected-keyword-arg) y E1103(maybe-no-member)
  • Para los no iniciados parece pura magia.

Una breve explicación de cómo funciona esto.

  • Todos los objetos de Python almacenan internamente sus atributos en un diccionario que se llama __dict__ .
  • No hay ningún requisito de que el diccionario interno __dict__ deba ser "simplemente un dict simple", por lo que podemos asignar cualquier subclase de dict() al diccionario interno.
  • En nuestro caso, simplemente asignamos la instancia AttrDict() que estamos creando (como estamos en __init__ ).
  • Al llamar al método __init__() , nos aseguramos de que (ya) se comporte exactamente como un diccionario, ya que esa función llama a todo el código de creación de instancias del diccionario .

Una de las razones por las que Python no proporciona esta funcionalidad fuera de la caja

Como se indica en la lista de "contras", esto combina el espacio de nombres de las claves almacenadas (que puede provenir de datos arbitrarios y / o no confiables) con el espacio de nombres de los atributos del método incorporado. Por ejemplo:

d = AttrDict() d.update({''items'':["jacket", "necktie", "trousers"]}) for k, v in d.items(): # TypeError: ''list'' object is not callable print "Never reached!"


La solución es:

DICT_RESERVED_KEYS = vars(dict).keys() class SmartDict(dict): """ A Dict which is accessible via attribute dot notation """ def __init__(self, *args, **kwargs): """ :param args: multiple dicts ({}, {}, ..) :param kwargs: arbitrary keys=''value'' If ``keyerror=False`` is passed then not found attributes will always return None. """ super(SmartDict, self).__init__() self[''__keyerror''] = kwargs.pop(''keyerror'', True) [self.update(arg) for arg in args if isinstance(arg, dict)] self.update(kwargs) def __getattr__(self, attr): if attr not in DICT_RESERVED_KEYS: if self[''__keyerror'']: return self[attr] else: return self.get(attr) return getattr(self, attr) def __setattr__(self, key, value): if key in DICT_RESERVED_KEYS: raise AttributeError("You cannot set a reserved name as attribute") self.__setitem__(key, value) def __copy__(self): return self.__class__(self) def copy(self): return self.__copy__()


Las tuplas se pueden utilizar para las teclas dict. ¿Cómo accederías a la tupla en tu construcción?

Además, namedtuple es una estructura conveniente que puede proporcionar valores mediante el acceso de atributo.


No es necesario que escribas tu propio ya que setattr() y getattr () ya existen.

La ventaja de los objetos de clase probablemente entra en juego en la definición de clase y la herencia.


No funciona en general. No todas las claves dict válidas hacen atributos direccionables ("la clave"). Por lo tanto, tendrás que tener cuidado.

Los objetos de Python son básicamente diccionarios. Así que dudo que haya mucho rendimiento u otra sanción.


Permítame publicar otra implementación, que se basa en la respuesta de Kinvais, pero integra ideas del AttributeDict propuesto en http://databio.org/posts/python_AttributeDict.html .

La ventaja de esta versión es que también funciona para diccionarios anidados:

class AttrDict(dict): """ A class to convert a nested Dictionary into an object with key-values that are accessible using attribute notation (AttrDict.attribute) instead of key notation (Dict["key"]). This class recursively sets Dicts to objects, allowing you to recurse down nested dicts (like: AttrDict.attr.attr) """ # Inspired by: # http://.com/a/14620633/1551810 # http://databio.org/posts/python_AttributeDict.html def __init__(self, iterable, **kwargs): super(AttrDict, self).__init__(iterable, **kwargs) for key, value in iterable.items(): if isinstance(value, dict): self.__dict__[key] = AttrDict(value) else: self.__dict__[key] = value


Puede extraer una clase de contenedor conveniente de la biblioteca estándar:

from argparse import Namespace

Para evitar tener que copiar alrededor de bits de código. Sin acceso estándar al diccionario, pero fácil de recuperar si realmente lo desea. El código en argparse es simple,

class Namespace(_AttributeHolder): """Simple object for storing attributes. Implements equality by attribute names and values, and provides a simple string representation. """ def __init__(self, **kwargs): for name in kwargs: setattr(self, name, kwargs[name]) __hash__ = None def __eq__(self, other): return vars(self) == vars(other) def __ne__(self, other): return not (self == other) def __contains__(self, key): return key in self.__dict__


Puede usar todos los caracteres de cadena legales como parte de la clave si usa la notación de matriz. Por ejemplo, obj[''!#$%^&*()_'']


Puedes hacerlo usando esta clase que acabo de hacer. Con esta clase puede usar el objeto Map como otro diccionario (incluida la serialización json) o con la notación de puntos. Espero que te ayude:

class Map(dict): """ Example: m = Map({''first_name'': ''Eduardo''}, last_name=''Pool'', age=24, sports=[''Soccer'']) """ def __init__(self, *args, **kwargs): super(Map, self).__init__(*args, **kwargs) for arg in args: if isinstance(arg, dict): for k, v in arg.iteritems(): self[k] = v if kwargs: for k, v in kwargs.iteritems(): self[k] = v def __getattr__(self, attr): return self.get(attr) def __setattr__(self, key, value): self.__setitem__(key, value) def __setitem__(self, key, value): super(Map, self).__setitem__(key, value) self.__dict__.update({key: value}) def __delattr__(self, item): self.__delitem__(item) def __delitem__(self, key): super(Map, self).__delitem__(key) del self.__dict__[key]

Ejemplos de uso:

m = Map({''first_name'': ''Eduardo''}, last_name=''Pool'', age=24, sports=[''Soccer'']) # Add new key m.new_key = ''Hello world!'' print m.new_key print m[''new_key''] # Update values m.new_key = ''Yay!'' # Or m[''new_key''] = ''Yay!'' # Delete key del m.new_key # Or del m[''new_key'']


Solo para agregar algo de variedad a la respuesta, sci-kit learn ha implementado esto como un Bunch :

class Bunch(dict): """ Scikit Learn''s container object Dictionary-like object that exposes its keys as attributes. >>> b = Bunch(a=1, b=2) >>> b[''b''] 2 >>> b.b 2 >>> b.c = 6 >>> b[''c''] 6 """ def __init__(self, **kwargs): super(Bunch, self).__init__(kwargs) def __setattr__(self, key, value): self[key] = value def __dir__(self): return self.keys() def __getattr__(self, key): try: return self[key] except KeyError: raise AttributeError(key) def __setstate__(self, state): pass

Todo lo que necesita es obtener los métodos setattr y getattr , los controles getattr para las claves dict y los movimientos para verificar los atributos reales. El setstaet es un arreglo para la corrección de los "racimos" de decapado y despiece de encurtidos: si se comprueba, https://github.com/scikit-learn/scikit-learn/issues/6196


class AttrDict(dict): def __init__(self): self.__dict__ = self if __name__ == ''____main__'': d = AttrDict() d[''ray''] = ''hope'' d.sun = ''shine'' >>> Now we can use this . notation print d[''ray''] print d.sun