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 dedict
, 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(..)
Caveat emptor: por alguna razón, clases como esta parecen romper el paquete de multiprocesamiento. Solo luché con este error por un tiempo antes de encontrar este SO: Encontrando una excepción en el multiprocesamiento de python
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
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 deKeyError
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)
yE1103(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 dedict()
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