Digamos que tengo una clase simple en Python
class Wharrgarbl(object):
def __init__(self, a, b, c, sum, version='old'):
self.a = a
self.b = b
self.c = c
self.sum = 6
self.version = version
def __int__(self):
return self.sum + 9000
def __what_goes_here__(self):
return {'a': self.a, 'b': self.b, 'c': self.c}
Puedo convertirlo en un número entero muy fácilmente
>>> w = Wharrgarbl('one', 'two', 'three', 6)
>>> int(w)
9006
¡Lo cual es genial! Pero ahora quiero convertirlo en un dictado de manera similar.
>>> w = Wharrgarbl('one', 'two', 'three', 6)
>>> dict(w)
{'a': 'one', 'c': 'three', 'b': 'two'}
¿Qué necesito definir para que esto funcione? Intenté sustituir ambos __dict__
y dict
por __what_goes_here__
, pero dict(w)
resultó en un TypeError: Wharrgarbl object is not iterable
en ambos casos. No creo que simplemente hacer que la clase sea iterable resolverá el problema. También intenté muchos Google con tantas redacciones diferentes de "objeto de lanzamiento de python para dict" como pude pensar, pero no pude encontrar nada relevante: {
¡También! Observe cómo llamar w.__dict__
no hará lo que quiero porque va a contener w.version
y w.sum
. Quiero personalizar el reparto de dict
la misma manera que puedo personalizar el reparto paraint
usando def int(self)
.
Sé que podría hacer algo como esto
>>> w.__what_goes_here__()
{'a': 'one', 'c': 'three', 'b': 'two'}
Pero supongo que hay una forma pitónica de hacer que dict(w)
funcione, ya que es el mismo tipo de cosas que int(w)
ostr(w)
. Si no hay una forma más pitónica, también está bien, pensé que preguntaría. Oh! Supongo que, dado que importa, esto es para Python 2.7, pero también puntos de súper bonificación para una solución 2.4 antigua y rota.
Hay otra pregunta sobrecarga de __dict __ () en la clase python que es similar a esta, pero puede ser lo suficientemente diferente como para justificar que no sea un duplicado. Creo que OP está preguntando cómo convertir todos los datos en sus objetos de clase como diccionarios. Estoy buscando un enfoque más personalizado en el sentido de que no quiero que todo __dict__
incluido en el diccionario sea devuelto por dict()
. Algo como variables públicas vs privadas puede ser suficiente para explicar lo que estoy buscando. Los objetos almacenarán algunos valores utilizados en los cálculos y que no necesito / quiero aparecer en los diccionarios resultantes.
ACTUALIZACIÓN: Elegí seguir la asdict
ruta sugerida, pero fue una decisión difícil seleccionar lo que quería que fuera la respuesta a la pregunta. Tanto @RickTeachey como @ jpmc26 proporcionaron la respuesta con la que voy a seguir, pero el primero tenía más información y opciones y también obtuvo el mismo resultado y recibió más votos, así que lo seguí. Sin embargo, todos votaron a favor y gracias por la ayuda. He estado al acecho largo y tendido en el desbordamiento de la pila y estoy tratando de meter más los dedos de los pies en el agua.
Respuestas:
Hay al menos
cincoseis formas. La forma preferida depende de cuál sea su caso de uso.Opción 1: simplemente agregue un
asdict()
método.Según la descripción del problema, consideraría mucho la
asdict
forma de hacer las cosas sugerida por otras respuestas. Esto se debe a que no parece que su objeto sea realmente una gran colección:class Wharrgarbl(object): ... def asdict(self): return {'a': self.a, 'b': self.b, 'c': self.c}
El uso de las otras opciones a continuación podría resultar confuso para los demás, a menos que sea muy obvio exactamente qué miembros del objeto se iterarían y no se especificarían como pares clave-valor.
Opción 1a: herede su clase de
'typing.NamedTuple'
(o la mayoría equivalente'collections.namedtuple'
) y use el_asdict
método proporcionado para usted.from typing import NamedTuple class Wharrgarbl(NamedTuple): a: str b: str c: str sum: int = 6 version: str = 'old'
Usar una tupla con nombre es una forma muy conveniente de agregar muchas funcionalidades a su clase con un mínimo de esfuerzo, incluido un
_asdict
método . Sin embargo, una limitación es que, como se muestra arriba, el NT incluirá a todos los miembros en su_asdict
.Si hay miembros que no desea incluir en su diccionario, deberá modificar el
_asdict
resultado:from typing import NamedTuple class Wharrgarbl(NamedTuple): a: str b: str c: str sum: int = 6 version: str = 'old' def _asdict(self): d = super()._asdict() del d['sum'] del d['version'] return d
Otra limitación es que NT es de solo lectura. Esto puede ser deseable o no.
Opcion 2: Implementar
__iter__
.Así, por ejemplo:
def __iter__(self): yield 'a', self.a yield 'b', self.b yield 'c', self.c
Ahora puedes simplemente hacer:
Esto funciona porque el
dict()
constructor acepta un iterable de(key, value)
pares para construir un diccionario. Antes de hacer esto, hágase la pregunta de si iterar el objeto como una serie de pares clave-valor de esta manera, aunque es conveniente para crear undict
, podría ser un comportamiento sorprendente en otros contextos. Por ejemplo, hágase la pregunta "¿cuál debería ser el comportamiento delist(my_object)
...?"Además, tenga en cuenta que acceder a los valores directamente utilizando el elemento obtener
obj["a"]
sintaxis no funcionará y el desempaquetado de argumentos de palabras clave no funcionará. Para esos, necesitaría implementar el protocolo de mapeo.Opción 3: implementar el protocolo de mapeo . Esto permite el comportamiento de acceso por clave, emitiendo a un
dict
sin usar__iter__
, y también proporciona un comportamiento de desempaquetado ({**my_obj}
) y un comportamiento de desempaquetado de palabras clave si todas las claves son cadenas (dict(**my_obj)
).El protocolo de mapeo requiere que proporcione (como mínimo) dos métodos juntos:
keys()
y__getitem__
.class MyKwargUnpackable: def keys(self): return list("abc") def __getitem__(self, key): return dict(zip("abc", "one two three".split()))[key]
Ahora puedes hacer cosas como:
>>> m=MyKwargUnpackable() >>> m["a"] 'one' >>> dict(m) # cast to dict directly {'a': 'one', 'b': 'two', 'c': 'three'} >>> dict(**m) # unpack as kwargs {'a': 'one', 'b': 'two', 'c': 'three'}
Como se mencionó anteriormente, si está utilizando una versión lo suficientemente nueva de Python, también puede descomprimir su objeto de protocolo de mapeo en un diccionario de comprensión como tal (y en este caso no es necesario que sus claves sean cadenas):
>>> {**m} {'a': 'one', 'b': 'two', 'c': 'three'}
Tenga en cuenta que el protocolo de mapeo tiene prioridad sobre el
__iter__
método cuando se lanza un objeto a undict
directamente (sin usar el desempaquetado de kwarg, es decirdict(m)
). Por lo tanto, es posible, y a veces conveniente, hacer que el objeto tenga un comportamiento diferente cuando se usa como iterable (por ejemplo,list(m)
) frente a cuando se lanza adict
(dict(m)
).DESTACADO : El hecho de que PUEDA usar el protocolo de mapeo, NO significa que DEBE hacerlo . ¿Tiene realmente sentido que su objeto se transmita como un conjunto de pares clave-valor o como argumentos y valores de palabras clave? ¿Tiene sentido acceder a él mediante una tecla, al igual que un diccionario?
Si la respuesta a estas preguntas es afirmativa , probablemente sea una buena idea considerar la siguiente opción.
Opción 4: considere el uso del
'collections.abc
módulo ' .Heredar su clase de
'collections.abc.Mapping
o le'collections.abc.MutableMapping
indica a otros usuarios que, para todos los efectos, su clase es un mapeo * y se puede esperar que se comporte de esa manera.Aún puede lanzar su objeto a un
dict
tal como lo necesite, pero probablemente haya pocas razones para hacerlo. Debido al tipo de pato , molestarse en convertir su objeto de mapeo en undict
sería un paso adicional innecesario la mayoría de las veces.Esta respuesta también puede ser útil.
Como se indica en los comentarios a continuación: vale la pena mencionar que hacer esto de la forma abc esencialmente convierte su clase de objeto en una
dict
clase similar (asumiendo que usaMutableMapping
y no la de solo lecturaMapping
clase base de ). Todo lo que podría hacer con éldict
, podría hacerlo con su propio objeto de clase. Esto puede ser deseable o no.También considere mirar los abcs numéricos en el
numbers
módulo:https://docs.python.org/3/library/numbers.html
Dado que también está lanzando su objeto a un
int
, podría tener más sentido convertir esencialmente su clase en unint
para que el lanzamiento no sea necesario.Opción 5: Analice el uso del
dataclasses
módulo (solo Python 3.7), que incluye unasdict()
método de utilidad conveniente .from dataclasses import dataclass, asdict, field, InitVar @dataclass class Wharrgarbl(object): a: int b: int c: int sum: InitVar[int] # note: InitVar will exclude this from the dict version: InitVar[str] = "old" def __post_init__(self, sum, version): self.sum = 6 # this looks like an OP mistake? self.version = str(version)
Ahora puedes hacer esto:
>>> asdict(Wharrgarbl(1,2,3,4,"X")) {'a': 1, 'b': 2, 'c': 3}
Opción 6: uso
typing.TypedDict
, que se ha agregado en python 3.8 .NOTA: Es probable que la opción 6 NO sea lo que buscan el OP u otros lectores según el título de esta pregunta. Consulte los comentarios adicionales a continuación.
class Wharrgarbl(TypedDict): a: str b: str c: str
El uso de esta opción, el objeto resultante es un
dict
(énfasis: se no unaWharrgarbl
). No hay ninguna razón para "convertirlo" en un dictado (a menos que esté haciendo una copia).Y dado que el objeto es a
dict
, la firma de inicialización es idéntica a la dedict
y, como tal, solo acepta argumentos de palabras clave u otro diccionario.>>> w = Wharrgarbl(a=1,b=2,b=3) >>> w {'a': 1, 'b': 2, 'c': 3} >>> type(w) <class 'dict'>
Enfatizado : la "clase" anterior
Wharrgarbl
no es en realidad una clase nueva en absoluto. Es simplemente azúcar sintáctico para crear tiposdict
objetos con campos de diferentes tipos para el verificador de tipos.Como tal, esta opción puede ser bastante conveniente para señalar a los lectores de su código (y también a un verificador de tipos como mypy) que
dict
se espera que dicho objeto tenga claves específicas con tipos de valores específicos.Pero esto significa que no puede, por ejemplo, agregar otros métodos, aunque puede probar:
class MyDict(TypedDict): def my_fancy_method(self): return "world changing result"
... pero no funcionará:
>>> MyDict().my_fancy_method() Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'dict' object has no attribute 'my_fancy_method'
* "Mapeo" se ha convertido en el "nombre" estándar del
dict
tipo -como patofuente
for k, v in my_object
, también estará habilitado.a
,b
yc
en eldict
. Lavars
función devolverá un diccionario con TODOS los miembros del objeto, incluidos los que no se querían:sum
yversion
. En algunos casosvars
funcionará, pero en mi experiencia no es realmente una pitón idiomática. Por lo general, la gente es más explícita, diciendo "ahora yo echo mi objeto a un diccionario", haciendo por ejemploobj.asdict()
. Convars
, suele ser algo más comoobj_kwargs = vars(obj)
seguido deMyObject(**obj_kwargs)
. En otras palabras:vars
se usa principalmente para copiar objetos.self.__dict__
Qué tal agregar un método que regrese ?No existe un método mágico que haga lo que quieres. La respuesta es simplemente nombrarlo apropiadamente.
asdict
es una opción razonable para una conversión simpledict
, inspirada principalmente ennamedtuple
. Sin embargo, su método obviamente contendrá una lógica especial que podría no ser inmediatamente obvia por ese nombre; está devolviendo solo un subconjunto del estado de la clase. Si puede encontrar un nombre un poco más detallado que comunique los conceptos con claridad, mucho mejor.Otras respuestas sugieren usar
__iter__
, pero a menos que su objeto sea verdaderamente iterable (representa una serie de elementos), esto realmente tiene poco sentido y constituye un abuso incómodo del método. El hecho de que desee filtrar parte del estado de la clase hace que este enfoque sea aún más dudoso.fuente
__iter__
como han sugerido otras respuestas: "Para las asignaciones, debe iterar sobre las claves del contenedor y también debe estar disponible como el método iterkeys ()".Mapping
clase base. Un objeto que tiene atributos con nombre específicos no se incluye en esta categoría.algo como esto probablemente funcionaría
class MyClass: def __init__(self,x,y,z): self.x = x self.y = y self.z = z def __iter__(self): #overridding this to return tuples of (key,value) return iter([('x',self.x),('y',self.y),('z',self.z)]) dict(MyClass(5,6,7)) # because dict knows how to deal with tuples of (key,value)
fuente
x
se crea la primera tupla con , pero antes de la segunda tupla cony
. ¿Me equivoco y, en realidad, la comprensión de la lista es atómica con respecto a la programación de subprocesos?dis
módulo para ver los códigos de bytes reales.Creo que esto funcionará para ti.
class A(object): def __init__(self, a, b, c, sum, version='old'): self.a = a self.b = b self.c = c self.sum = 6 self.version = version def __int__(self): return self.sum + 9000 def __iter__(self): return self.__dict__.iteritems() a = A(1,2,3,4,5) print dict(a)
Salida
{'a': 1, 'c': 3, 'b': 2, 'suma': 6, 'versión': 5}
fuente
__dict__
como se sugiere la otra respuesta?self.__dict__
que contiene elementos que no quiero tener en la versión de diccionario del objeto.Como muchos otros, sugeriría implementar una función to_dict () en lugar de (o además) permitir la conversión a un diccionario. Creo que hace más obvio que la clase admite ese tipo de funcionalidad. Podría implementar fácilmente un método como este:
def to_dict(self): class_vars = vars(MyClass) # get any "default" attrs defined at the class level inst_vars = vars(self) # get any attrs defined on the instance (self) all_vars = dict(class_vars) all_vars.update(inst_vars) # filter out private attributes public_vars = {k: v for k, v in all_vars.items() if not k.startswith('_')} return public_vars
fuente
Es difícil de decir sin conocer todo el contexto del problema, pero no anularía
__iter__
.Lo implementaría
__what_goes_here__
en la clase.as_dict(self: d = {...whatever you need...} return d
fuente
w.__dict__
no hace lo que necesito.__dict__
podría anularse, ¿no?Estoy tratando de escribir una clase que sea "tanto"
list
como adict
. Quiero que el programador pueda "lanzar" este objeto alist
(soltando las teclas) odict
(con las teclas).Mirando la forma en que Python actualmente hace el
dict()
lanzamiento: llamaMapping.update()
con el objeto que se pasa. Este es el código del repositorio de Python :def update(self, other=(), /, **kwds): ''' D.update([E, ]**F) -> None. Update D from mapping/iterable E and F. If E present and has a .keys() method, does: for k in E: D[k] = E[k] If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v In either case, this is followed by: for k, v in F.items(): D[k] = v ''' if isinstance(other, Mapping): for key in other: self[key] = other[key] elif hasattr(other, "keys"): for key in other.keys(): self[key] = other[key] else: for key, value in other: self[key] = value for key, value in kwds.items(): self[key] = value
El último subcaso de la instrucción if, en el que se repite,
other
es el que la mayoría de la gente tiene en mente. Sin embargo, como puede ver, también es posible tener unakeys()
propiedad. Eso, combinado con un,__getitem__()
debería facilitar que una subclase se transmita correctamente a un diccionario:class Wharrgarbl(object): def __init__(self, a, b, c, sum, version='old'): self.a = a self.b = b self.c = c self.sum = 6 self.version = version def __int__(self): return self.sum + 9000 def __keys__(self): return ["a", "b", "c"] def __getitem__(self, key): # have obj["a"] -> obj.a return self.__getattribute__(key)
Entonces esto funcionará:
>>> w = Wharrgarbl('one', 'two', 'three', 6) >>> dict(w) {'a': 'one', 'c': 'three', 'b': 'two'}
fuente
list
soltar las claves, puede agregar un__iter__
método que itera a través de los valores, y la conversión adict
seguirá funcionando. Tenga en cuenta que, por lo general, con los diccionarios, si envía a una lista, devolverá las CLAVES y no los valores que necesita. Esto podría ser sorprendente para otras personas que usen su código a menos que sea obvio por qué sucedería esto.