¿Acceder a claves dict como un atributo?


303

Me parece más conveniente acceder a las claves dict como en obj.foolugar 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, supongo que debe haber alguna razón por la cual Python no proporciona esta funcionalidad de fábrica. ¿Cuáles serían las advertencias y las dificultades de acceder a las claves dict de esta manera?


16
Si accede a teclas codificadas desde un conjunto limitado de tamaño fijo en todas partes, es mejor que cree objetos que las contengan. collections.namedtupleEs muy útil para esto.

66
stackoverflow.com/questions/3031219/… tiene una solución similar pero va un paso más allá
keflavich

1
Encontré un módulo para esto en github.com/bcj/AttrDict . No sé cómo se compara con las soluciones aquí y en las preguntas relacionadas.
Matt Wilkie

También usé hacks similares, ahora usoeasydict.EasyDict
muon

Más formas de acceder a los miembros del diccionario con un '.' : stackoverflow.com/questions/2352181/…
Pale Blue Dot

Respuestas:


304

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:

  • En realidad funciona!
  • No hay métodos de clase de diccionario sombreados (p. Ej. .keys() funcionan bien. A menos que, por supuesto, les asignes algún valor, ver más abajo)
  • Los atributos y los elementos siempre están sincronizados
  • Intentar acceder a una clave inexistente como un atributo aumenta correctamente en AttributeErrorlugar deKeyError

Contras:

  • Métodos como .keys()voluntad no funcionará bien si consiguen sobrescritos por los datos entrantes
  • Causa una pérdida de memoria en Python <2.7.4 / Python3 <3.2.3
  • Pylint va a las bananas 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 con nombre __dict__.
  • No hay ningún requisito de que el diccionario interno __dict__deba ser "solo un simple dict", por lo que podemos asignar cualquier subclase dict()al diccionario interno.
  • En nuestro caso, simplemente asignamos la AttrDict()instancia que estamos creando instancias (tal como estamos __init__).
  • Al llamar super()al __init__()método nos aseguramos de que (ya) se comporte exactamente como un diccionario, ya que esa función llama a todo el código de instanciación del diccionario .

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

Como se señaló en la lista "contras", esto combina el espacio de nombres de las claves almacenadas (¡que pueden provenir de datos arbitrarios y / o no confiables!) Con el espacio de nombres de los atributos del método dict 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!"

1
¿Crees que la pérdida de memoria podría ocurrir con un objeto simple como: >>> clase MyD (objeto): ... def init __ (self, d): ... self .__ dict = d
Rafe

Provoca la fuga incluso en 2.7
pi.

1
Haga que <= 2.7.3, ya que eso es lo que estoy usando.
pi.

1
En las notas de la versión 2.7.4 lo mencionan arreglado (no antes).
Robert Siemer

1
@viveksinghggits solo porque está accediendo a cosas a través de ., no puede romper las reglas del lenguaje :) Y no me gustaría AttrDictconvertir automáticamente los campos que contienen espacio en algo diferente.
Yurik

125

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


1
@Izkata sí. Lo divertido de SE es que generalmente hay una "pregunta principal", es decir. título y una 'pregunta de fondo', tal vez porque a SE no le gusta escuchar "título lo dice todo"; las 'advertencias' son las inferiores aquí.
n611x007

2
No es que JavaScript sea un ejemplo particularmente bueno de lenguaje de programación, pero los objetos en JS admiten tanto el acceso a atributos como la notación de matriz, lo que permite la comodidad para el caso común y una alternativa genérica para símbolos que no son nombres de atributos legales.
André Caron

@Izkata ¿Cómo responde esto a la pregunta? Esta respuesta solo dice que las claves pueden tener cualquier nombre.
Melab

44
@Melab La pregunta es What would be the caveats and pitfalls of accessing dict keys in this manner?(como atributos), y la respuesta es que la mayoría de los caracteres que se muestran aquí no serían utilizables.
Izkata

83

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 concisa y no deja ningún espacio para cruft adicional metan en sus __getattr__y __setattr__funciones en el futuro.


¿Sería capaz de llamar a AttributeDict.update o AttributeDict.get usando este método?
Dor

13
Debe tener en cuenta que si agrega nuevos atributos en tiempo de ejecución, no se agregarán al dict en sí, sino al atributo dict . Por ej d = AttributeDict(foo=1). d.bar = 1el atributo de barra se almacena dentro del atributo dict pero no en el propio dict. la impresión dmuestra solo el elemento foo.
P3trus

77
+1 porque funciona perfectamente hasta donde puedo decir. @GringoSuave, @Izkata, @ P3trus Solicito a cualquiera que diga que falla muestra un ejemplo que no funciona d = AttributeDict(foo=1);d.bar = 1;print d=> ¡ {'foo': 1, 'bar': 1}Funciona para mí!
Dave Abrahams

44
@DaveAbrahams Lea la pregunta completa y vea las respuestas de Hery, Ryan y TheCommunistDuck. No se trata de cómo hacer esto, sino de los problemas que pueden surgir .
Izkata

66
Debe proporcionar un __getattr__método que genere un valor AttributeErrorsi el atributo dado no existe, de lo contrario, cosas como getattr(obj, attr, default_value)no funcionan (es decir, no regresa default_valuesi attrno existe obj)
jcdude

83

En donde respondo la pregunta que se hizo

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

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

Advertencias y trampas

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

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

Si dse instancia o KEY se define o d[KEY] se asigna lejos de donded.spam se está usando, puede fácilmente generar confusión sobre lo que se está haciendo, ya que este no es un idioma comúnmente usado. Sé que podría confundirme.

Además, si cambia el valor de la KEYsiguiente 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 otros han señalado, puede usar cualquier objeto hashable (no solo una cadena) como clave dict. 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 hashaable para sus claves de diccionario, que no tiene al acceder a un atributo de objeto. Esto hace posible tal magia como una metaclase de objeto en caché, como la receta del Libro de cocina de Python (Capítulo 9) .

En donde publico

Prefiero la estética de spam.eggsover spam['eggs'](creo que se ve más limpia), y realmente comencé a desear esta funcionalidad cuando conocí el 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 dictos en situaciones diferentes de las que usaría la obj.keynotación (es decir, cuando necesito leer preferencias de un archivo XML). En otros casos, donde tengo la tentación de crear una instancia de una clase dinámica y aplicar algunos atributos por razones estéticas, sigo usando un dict por coherencia para mejorar la legibilidad.

Estoy seguro de que el OP hace tiempo que resolvió esto a su satisfacción, pero si todavía quiere 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, por lo que tiene toda esa funcionalidad.
  • AttrDict también parece que también es bastante bueno, pero no estoy tan familiarizado con él y no he revisado la fuente con tanto detalle como Bunch .
  • Addict se mantiene activamente y proporciona acceso similar a un atributo y más.
  • Como se señaló 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 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 un 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}


En donde actualizo, para responder una pregunta de seguimiento en los comentarios

En los comentarios (abajo), Elmo pregunta:

¿Qué pasa si quieres ir más profundo? (refiriéndose al tipo (...))

Si bien nunca he usado este caso de uso (nuevamente, tiendo a usar anidado dict, 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}

1
Bunch está en desuso, pero hay una bifurcación activa: github.com/Infinidat/munch
Rotareti

@Rotareti - ¡Gracias por el aviso! Esta no es la funcionalidad que uso, así que no estaba al tanto de eso.
Doug R.

¿Qué pasa si quieres ir más profundo? (refiriéndose al tipo (...))
Ole Aldric

66
Python es como un paraguas invertido en lo alto de una fuerte lluvia. Para empezar, todo parece inteligente y funky, después de un tiempo comienza a ponerse pesado, luego, de repente, lees algunas cosas de gurú integradas en SE y todo vuelve a la carga útil sobre tus hombros. Si bien aún empapado te sientes más ligero y todo es tan claro y renovado.
Ole Aldric


19

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

from argparse import Namespace

para evitar tener que copiar bits de código. No hay acceso estándar al diccionario, pero es fácil recuperarlo 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__

2
ADEMÁS 1 para hacer referencia a una biblioteca estándar, que aborda el primer comentario del OP.
Gordon Bean

44
Python incluye una clase más rápida (implementada en C) para ese caso: types.SimpleNamespace docs.python.org/dev/library/types.html#types.SimpleNamespace
Nuno André

18

¿Qué pasa si desea una clave que es 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 está desactivado.

¿Y si no quisieras usar una cuerda?


De hecho, o por ejemplo otros objetos como claves. Sin embargo, clasificaría el error de eso como 'comportamiento esperado'; con mi pregunta, apuntaba más hacia lo inesperado.
Izz ad-Din Ruhulessin

pickle.dumpusos__getstate__
Cees Timmerman

12

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

Además, namedtuple es una estructura conveniente que puede proporcionar valores a través del acceso al atributo.


77
La desventaja de namedtuples es que son inmutables.
Izz ad-Din Ruhulessin

10
Algunos dirían que ser inmutable no es un error, sino una característica de las tuplas.
ben autor

9

¿Qué tal 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 .

Puede hacer exactamente lo que pidió:

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

Ejemplo 1: tipo de sugerencia

class Country(Prodict):
    name: str
    population: int

turkey = Country()
turkey.name = 'Turkey'
turkey.population = 79814871

código automático completo

Ejemplo 2: conversión de tipo automática

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'>

2
se instala en python2 a través de pip, pero no funciona en python2
Antón

2
@ Ant6n requiere python 3.6+ debido a anotaciones de tipo
Ramazan Polat

8

No funciona en general. No todas las claves dict válidas hacen atributos direccionables ("la clave"). Entonces, deberás tener cuidado.

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


8

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

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

Un ejemplo de los documentos:

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'

8

Me pregunté cuál era el estado actual de "dict keys as attr" en el ecosistema de python. Como han señalado varios comentaristas, esto probablemente no sea algo que desee rodar desde cero , ya que hay varias trampas y pistolas, algunas de ellas muy sutiles. Además, no recomendaría usarNamespace como clase base, he estado en ese camino, no es bonito.

Afortunadamente, hay varios paquetes de código abierto que proporcionan esta funcionalidad, ¡listos para instalar pip! Lamentablemente, hay varios paquetes. Aquí hay una sinopsis, a partir de diciembre de 2019.

Contenders (compromiso más reciente para master | #commits | #contribs | cobertura%):

  • adicto (2019-04-28 | 217 | 22 | 100%)
  • mascar (12/16/2019 | 160 | 17 |?%)
  • easydict (2018-10-18 | 51 | 6 |?%)
  • Atributo (2019-02-01 | 108 | 5 | 100%)
  • prodict (2019-10-01 | 65 | 1 |?%)

Ya no se mantiene o no se mantiene:

  • treedict (2014-03-28 | 95 | 2 |?%)
  • grupo (2012-03-12 | 20 | 2 |?%)
  • NeoBunch

Actualmente recomiendo munch o adicto . Tienen la mayor cantidad de commits, contribuyentes y lanzamientos, lo que sugiere una base de código de código abierto saludable para cada uno. Tienen el readme.md de aspecto más limpio, 100% de cobertura y un buen conjunto de pruebas.

No tengo un perro en esta carrera (¡por ahora!), Además de haber lanzado mi propio código dict / attr y haber perdido un montón de tiempo porque no estaba al tanto de todas estas opciones :). Puedo contribuir a adicto / munch en el futuro, ya que preferiría ver un paquete sólido que un montón de fragmentados. Si te gustan, ¡contribuye! En particular, parece que munch podría usar una insignia de codecov y el adicto podría usar una insignia de versión de python.

pros adictos:

  • inicialización recursiva (foo.abc = 'bar'), los argumentos tipo dict se convierten en adictos.

adicto contras:

  • sombras typing.Dictsi tufrom addict import Dict
  • Sin comprobación de clave. Debido a que permite el inicio recursivo, si escribe mal una clave, simplemente crea un nuevo atributo, en lugar de KeyError (gracias AljoSt)

munch pros:

  • nombres únicos
  • Funciones de ser / de incorporadas para JSON y YAML

munch contras:

  • sin inicio recursivo / solo puede iniciar un atributo a la vez

En donde publico

Hace muchas lunas, cuando usaba editores de texto para escribir python, en proyectos solo con mí u otro desarrollador, me gustaba el estilo de dict-attrs, la capacidad de insertar claves simplemente declarando foo.bar.spam = eggs. Ahora trabajo en equipos y uso un IDE para todo, y me he alejado de este tipo de estructuras de datos y tipeo dinámico en general, a favor del análisis estático, las técnicas funcionales y las sugerencias de tipo. Comencé a experimentar con esta técnica, subclasificando Pstruct con objetos de mi propio diseño:

class  BasePstruct(dict):
    def __getattr__(self, name):
        if name in self.__slots__:
            return self[name]
        return self.__getattribute__(name)

    def __setattr__(self, key, value):
        if key in self.__slots__:
            self[key] = value
            return
        if key in type(self).__dict__:
            self[key] = value
            return
        raise AttributeError(
            "type object '{}' has no attribute '{}'".format(type(self).__name__, key))


class FooPstruct(BasePstruct):
    __slots__ = ['foo', 'bar']

Esto le da un objeto que todavía se comporta como un dict, pero también le permite acceder a claves como atributos, de una manera mucho más rígida. La ventaja aquí es que yo (o los desafortunados consumidores de su código) sé exactamente qué campos pueden y no pueden existir, y el IDE puede completar automáticamente los campos. Subclasificar también vainilla dictsignifica que la serialización json es fácil. Creo que la próxima evolución en esta idea sería un generador de prototipos personalizado que emita estas interfaces, y un buen efecto adicional es que obtienes estructuras de datos en varios idiomas e IPC a través de gRPC de forma casi gratuita.

Si decide ir con atributos, es esencial documentar qué campos se esperan, para su propia cordura (y la de sus compañeros de equipo).

¡Siéntase libre de editar / actualizar esta publicación para mantenerla reciente!


2
Una gran desventaja addictes que no generará excepciones cuando escribe mal un atributo, ya que devolverá uno nuevo Dict(esto es necesario para que foo.abc = 'bar' funcione).
AljoSt

5

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

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(..)

5

Solo para agregar algo de variedad a la respuesta, sci-kit learn lo ha implementado como 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 setattry getattr: las getattrcomprobaciones de las claves dict y los movimientos para verificar los atributos reales. Esta setstaetes una solución para solucionar los "racimos" de decapado / deshebrado: si no está interesado, consulte https://github.com/scikit-learn/scikit-learn/issues/6196


3

No es necesario escribir el suyo ya que ya existen setattr () y getattr ().

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


3

Creé esto basado en la entrada de este hilo. Sin embargo, 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 dict y convertirlo en un objeto que funcione con autocompletar en un IDE, etc.

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


3

Esto es lo que uso

args = {
        'batch_size': 32,
        'workers': 4,
        'train_dir': 'train',
        'val_dir': 'val',
        'lr': 1e-3,
        'momentum': 0.9,
        'weight_decay': 1e-4
    }
args = namedtuple('Args', ' '.join(list(args.keys())))(**args)

print (args.lr)

Esta es una buena respuesta rápida y sucia. Mi única observación / comentario es que creo que el constructor de tuplas nombradas aceptará una lista de cadenas, por lo que su solución puede simplificarse (creo) para:namedtuple('Args', list(args.keys()))(**args)
Dan Nguyen

2

Puedes hacerlo usando esta clase que acabo de hacer. Con esta clase, puede usar el Mapobjeto como otro diccionario (incluida la serialización json) o con la notación de puntos. Espero ayudarte

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']

1
Tenga en cuenta que puede sombrear dictmétodos, por ejemplo: m=Map(); m["keys"] = 42; m.keys()give TypeError: 'int' object is not callable.
bfontaine

@bfontaine La idea es ser una especie de field/attributey no un method, pero si asigna un método en su lugar un número con el que puede acceder a ese método m.method().
epool

2

Permítanme 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://stackoverflow.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

1
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

1

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__()

1

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

Como sugiere @Henry, una de las razones por las cuales el acceso punteado no se puede usar en los dictos es que limita los nombres de clave dict a variables válidas para python, restringiendo así todos los nombres posibles.

Los siguientes son ejemplos de por qué el acceso de puntos no sería útil en general, dada una sentencia d:

Validez

Los siguientes atributos serían invá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 de PEP8 impondrían una restricción suave en la denominación de atributos:

A. Nombres de palabras clave 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 guión bajo final ...

B. La regla del caso sobre métodos y nombres de variables :

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

d.Firstname
d.Country

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


A veces, estas inquietudes se plantean en bibliotecas como los pandas , lo que permite el acceso punteado de las columnas de DataFrame por nombre. El mecanismo predeterminado para resolver las restricciones de nomenclatura es también 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 .



1

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

def make_funcdict(d=None, **kwargs)
    def funcdict(d=None, **kwargs):
        if d is not None:
            funcdict.__dict__.update(d)
        funcdict.__dict__.update(kwargs)
        return funcdict.__dict__
    funcdict(d, **kwargs)
    return funcdict

Ahora tiene una sintaxis ligeramente diferente. Para acceder a los elementos dictados como lo hacen los atributosf.key . Para acceder a los elementos dict (y otros métodos dict) de la manera habitual, hagamos f()['key']y podamos actualizar convenientemente el dict 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á. Seré feliz si alguien sugiere beneficios e inconvenientes de este método.


0

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

NeoBunch

Sin embargo, tiene una gran característica para convertir su dict en un objeto NeoBunch a través de su función neobunchify . Utilizo mucho las plantillas de Mako y pasar datos como objetos NeoBunch los hace mucho más legibles, por lo que si terminas usando un dict normal en tu programa 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 así:

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

Enlace a NeoBunch es 404
DeusXMachina

0

La forma más fácil es definir una clase, llamémosla Espacio de nombres. que usa el objeto dict .update () en el dict. Entonces, el dict será tratado como un objeto.

class Namespace(object):
    '''
    helps referencing object in a dictionary as dict.key instead of dict['key']
    '''
    def __init__(self, adict):
        self.__dict__.update(adict)



Person = Namespace({'name': 'ahmed',
                     'age': 30}) #--> added for edge_cls


print(Person.name)
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.