Digamos que tenho uma classe simples de 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}
Posso convertê-lo em um número inteiro muito facilmente
>>> w = Wharrgarbl('one', 'two', 'three', 6)
>>> int(w)
9006
O que é ótimo! Mas, agora eu quero convertê-lo para um dict de uma maneira semelhante
>>> w = Wharrgarbl('one', 'two', 'three', 6)
>>> dict(w)
{'a': 'one', 'c': 'three', 'b': 'two'}
O que preciso definir para que isso funcione? Tentei substituir ambos __dict__
e dict
por __what_goes_here__
, mas dict(w)
resultou em um TypeError: Wharrgarbl object is not iterable
em ambos os casos. Não acho que simplesmente tornar a classe iterável resolverá o problema. Eu também tentei muitos googles com tantas formulações diferentes de "python cast object to dict" quanto eu poderia pensar, mas não consegui encontrar nada relevante: {
Além disso! Observe como chamar w.__dict__
não fará o que eu quero porque conterá w.version
e w.sum
. Quero personalizar o elenco dict
da mesma maneira que posso personalizar o elenco int
usando def int(self)
.
Eu sei que poderia simplesmente fazer algo assim
>>> w.__what_goes_here__()
{'a': 'one', 'c': 'three', 'b': 'two'}
Mas estou assumindo que existe uma maneira pítônica de fazer dict(w)
funcionar, uma vez que é o mesmo tipo de coisa que int(w)
ou str(w)
. Se não existe uma forma mais python, tudo bem também, só pensei em perguntar. Oh! Eu acho que já que é importante, isso é para o python 2.7, mas super pontos de bônus para uma solução 2.4 antiga e quebrada também.
Há outra questão Sobrecarregando __dict __ () na classe Python que é semelhante a esta, mas pode ser diferente o suficiente para garantir que não seja uma duplicata. Eu acredito que OP está perguntando como converter todos os dados em seus objetos de classe como dicionários. Estou procurando uma abordagem mais personalizada, já que não quero que tudo __dict__
incluído no dicionário retornado por dict()
. Algo como variáveis públicas x privadas pode ser suficiente para explicar o que estou procurando. Os objetos estarão armazenando alguns valores usados nos cálculos e tal que não preciso / quero que apareçam nos dicionários resultantes.
ATUALIZAÇÃO: Escolhi seguir o asdict
caminho sugerido, mas foi uma escolha difícil selecionar o que eu queria ser a resposta à pergunta. Tanto @RickTeachey quanto @ jpmc26 forneceram a resposta que vou apresentar, mas o primeiro tinha mais informações e opções e também obteve o mesmo resultado e foi mais votado, então eu aceitei. Votos positivos por todos e obrigado pela ajuda. Eu espreitei por muito tempo e duramente no overflow e estou tentando colocar meus pés na água mais.
Respostas:
Existem pelo menos
cincoseis maneiras. A forma preferida depende de qual é o seu caso de uso.Opção 1: basta adicionar um
asdict()
método.Com base na descrição do problema, eu consideraria bastante a
asdict
maneira de fazer as coisas sugerida por outras respostas. Isso ocorre porque não parece que seu objeto seja realmente uma coleção:class Wharrgarbl(object): ... def asdict(self): return {'a': self.a, 'b': self.b, 'c': self.c}
Usar as outras opções abaixo pode ser confuso para os outros, a menos que seja muito óbvio exatamente quais membros do objeto seriam e não seriam iterados ou especificados como pares chave-valor.
Opção 1a: Herdar sua classe
'typing.NamedTuple'
(ou o equivalente principalmente'collections.namedtuple'
) e usar o_asdict
método fornecido para você.from typing import NamedTuple class Wharrgarbl(NamedTuple): a: str b: str c: str sum: int = 6 version: str = 'old'
Usar uma tupla nomeada é uma maneira muito conveniente de adicionar muitas funcionalidades à sua classe com um mínimo de esforço, incluindo um
_asdict
método . No entanto, uma limitação é que, conforme mostrado acima, o NT incluirá todos os seus membros_asdict
.Se houver membros que você não deseja incluir em seu dicionário, você precisará modificar o
_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
Outra limitação é que o NT é somente leitura. Isso pode ou não ser desejável.
Opção 2: Implementar
__iter__
.Assim, por exemplo:
def __iter__(self): yield 'a', self.a yield 'b', self.b yield 'c', self.c
Agora você pode apenas fazer:
dict(my_object)
Isso funciona porque o
dict()
construtor aceita um iterável de(key, value)
pares para construir um dicionário. Antes de fazer isso, pergunte a si mesmo se iterar o objeto como uma série de pares de chave e valor dessa maneira - embora seja conveniente para criar umdict
- pode realmente ser um comportamento surpreendente em outros contextos. Por exemplo, pergunte a si mesmo "qual deveria ser o comportamento delist(my_object)
...?"Além disso, observe que acessar valores diretamente usando a
obj["a"]
sintaxe get item não funcionará e a descompactação de argumento de palavra-chave não funcionará. Para eles, você precisa implementar o protocolo de mapeamento.Opção 3: implemente o protocolo de mapeamento . Isso permite o comportamento de acesso por chave, lançando para um
dict
sem usar__iter__
, e também fornece comportamento de descompactação ({**my_obj}
) e comportamento de descompactação de palavra-chave se todas as chaves forem strings (dict(**my_obj)
).O protocolo de mapeamento requer que você forneça (no mínimo) dois métodos juntos:
keys()
e__getitem__
.class MyKwargUnpackable: def keys(self): return list("abc") def __getitem__(self, key): return dict(zip("abc", "one two three".split()))[key]
Agora você pode fazer coisas 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 mencionado acima, se você estiver usando uma versão nova o suficiente de python, você também pode descompactar seu objeto de protocolo de mapeamento em uma compreensão de dicionário como esta (e neste caso não é necessário que suas chaves sejam strings):
>>> {**m} {'a': 'one', 'b': 'two', 'c': 'three'}
Observe que o protocolo de mapeamento tem precedência sobre o
__iter__
método ao lançar um objeto para umdict
diretamente (sem usar o desempacotamento kwarg, isto édict(m)
). Portanto, é possível - e às vezes conveniente - fazer com que o objeto tenha um comportamento diferente quando usado como um iterável (por exemplo,list(m)
) vs. quando convertido para adict
(dict(m)
).DESTACADO : Só porque você PODE usar o protocolo de mapeamento, NÃO significa que você DEVE fazê-lo . Faz sentido que seu objeto seja transmitido como um conjunto de pares de valores-chave ou como argumentos e valores de palavras-chave? Acessar por chave - assim como um dicionário - realmente faz sentido?
Se a resposta a essas perguntas for sim , provavelmente é uma boa ideia considerar a próxima opção.
Opção 4: tente usar o
'collections.abc
módulo ' .Herdar sua classe de
'collections.abc.Mapping
ou'collections.abc.MutableMapping
sinaliza para outros usuários que, para todos os efeitos, sua classe é um mapeamento * e pode-se esperar que se comporte dessa maneira.Você ainda pode lançar seu objeto para um
dict
conforme necessário, mas provavelmente haveria poucos motivos para isso. Por causa da digitação de pato , incomodar-se em lançar seu objeto de mapeamento para adict
seria apenas uma etapa adicional desnecessária na maioria das vezes.Essa resposta também pode ser útil.
Como observado nos comentários abaixo: vale a pena mencionar que fazer isso da maneira abc essencialmente transforma sua classe de objeto em uma
dict
classe semelhante (assumindo que você usaMutableMapping
e não aMapping
classe base somente leitura ). Tudo com o que você seria capaz de fazerdict
, você poderia fazer com seu próprio objeto de classe. Isso pode ser, ou não, desejável.Considere também observar os abcs numéricos no
numbers
módulo:https://docs.python.org/3/library/numbers.html
Como você também está lançando seu objeto para um
int
, pode fazer mais sentido essencialmente transformar sua classe em um completoint
para que o lançamento não seja necessário.Opção 5: tente usar o
dataclasses
módulo (somente Python 3.7), que inclui umasdict()
método utilitário 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)
Agora você pode fazer isso:
>>> asdict(Wharrgarbl(1,2,3,4,"X")) {'a': 1, 'b': 2, 'c': 3}
Opção 6: uso
typing.TypedDict
, que foi adicionado ao python 3.8 .NOTA: a opção 6 provavelmente NÃO é o que o OP ou outros leitores com base no título desta pergunta estão procurando. Veja comentários adicionais abaixo.
class Wharrgarbl(TypedDict): a: str b: str c: str
Usando esta opção, o objeto resultante é uma
dict
(ênfase: é não umWharrgarbl
). Não há razão alguma para "convertê-lo" em um dicionário (a menos que você esteja fazendo uma cópia).E como o objeto é a
dict
, a assinatura de inicialização é idêntica à dedict
e, como tal, só aceita argumentos de palavra-chave ou outro dicionário.>>> w = Wharrgarbl(a=1,b=2,b=3) >>> w {'a': 1, 'b': 2, 'c': 3} >>> type(w) <class 'dict'>
Enfatizado : a "classe" acima
Wharrgarbl
não é realmente uma classe nova. É simplesmente um açúcar sintático para criardict
objetos digitados com campos de tipos diferentes para o verificador de tipo.Como tal, essa opção pode ser muito conveniente para sinalizar aos leitores de seu código (e também a um verificador de tipo como mypy) que
dict
se espera que esse objeto tenha chaves específicas com tipos de valor específicos.Mas isso significa que você não pode, por exemplo, adicionar outros métodos, embora possa tentar:
class MyDict(TypedDict): def my_fancy_method(self): return "world changing result"
... mas não vai 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'
* "Mapeamento" tornou-se o "nome" padrão do
dict
tipo de pato semelhantefonte
for k, v in my_object
, também será ativado.a
,b
ec
nodict
. Avars
função retornará um dicionário com TODOS os membros do objeto incluindo aqueles que não eram desejados:sum
eversion
. Em alguns casosvars
funcionará, mas não é realmente python idiomática na minha experiência. Normalmente as pessoas são mais explícitos, dizendo: "Eu agora estou lançando meu objeto de um dicionário", fazendo, por exemploobj.asdict()
. Comvars
, geralmente é algo mais parecido comobj_kwargs = vars(obj)
seguido porMyObject(**obj_kwargs)
. Em outras palavras:vars
é usado principalmente para copiar objetos.self.__dict__
?Não existe um método mágico que fará o que você quiser. A resposta é simplesmente nomeá-lo apropriadamente.
asdict
é uma escolha razoável para uma conversão simples paradict
, inspirada principalmente pornamedtuple
. No entanto, seu método obviamente conterá uma lógica especial que pode não ser imediatamente óbvia a partir desse nome; você está retornando apenas um subconjunto do estado da classe. Se você puder sugerir um nome um pouco mais prolixo que comunique os conceitos claramente, tanto melhor.Outras respostas sugerem o uso
__iter__
, mas a menos que seu objeto seja verdadeiramente iterável (represente uma série de elementos), isso realmente faz pouco sentido e constitui um abuso estranho do método. O fato de você querer filtrar parte do estado da classe torna essa abordagem ainda mais duvidosa.fonte
__iter__
como outras respostas sugeriram: "Para mapeamentos, ele deve iterar sobre as chaves do contêiner e também deve ser disponibilizado como o método iterkeys ()."Mapping
classe base. Um objeto que possui atributos nomeados específicos não se enquadra nesta categoria.algo assim provavelmente funcionaria
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)
fonte
x
criação da primeira tupla com , mas antes da segunda tupla comy
. Estou errado e, na verdade, a compreensão da lista é atômica em relação ao agendamento de threads?dis
módulo para ver os bytecodes reais.Eu acho que isso vai funcionar para você.
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)
Resultado
{'a': 1, 'c': 3, 'b': 2, 'soma': 6, 'versão': 5}
fonte
__dict__
como a outra resposta sugerida?self.__dict__
que contém itens que não quero ter na versão do dicionário do objeto.Como muitos outros, eu sugeriria implementar uma função to_dict () em vez de (ou além de) permitir a conversão para um dicionário. Acho que fica mais óbvio que a classe suporta esse tipo de funcionalidade. Você poderia implementar facilmente um 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
fonte
É difícil dizer sem conhecer todo o contexto do problema, mas eu não iria ignorar
__iter__
.Gostaria de implementar
__what_goes_here__
na aula.as_dict(self: d = {...whatever you need...} return d
fonte
w.__dict__
não faz o que eu preciso.__dict__
poderia ser anulado, não é?Estou tentando escrever uma classe que seja "ao mesmo tempo" a
list
ou adict
. Eu quero que o programador seja capaz de "lançar" este objeto para umlist
(soltar as chaves) oudict
(com as chaves).Observando a maneira como o Python atualmente faz o
dict()
elenco: ele chamaMapping.update()
com o objeto que é passado. Este é o código do repositório 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
O último subcaso da instrução if, onde está iterando,
other
é o que a maioria das pessoas tem em mente. Porém, como vê, também é possível ter umkeys()
imóvel. Isso, combinado com um,__getitem__()
deve facilitar que uma subclasse seja devidamente convertida em um dicionário: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)
Então isso vai funcionar:
>>> w = Wharrgarbl('one', 'two', 'three', 6) >>> dict(w) {'a': 'one', 'c': 'three', 'b': 'two'}
fonte
list
drop drop das chaves, você pode adicionar um__iter__
método que itera através dos valores, e a conversão paradict
continuará a funcionar. Observe que, normalmente, com dicionários, se você lançar para uma lista, ele retornará as CHAVES, e não os valores que você precisa. Isso pode ser surpreendente para outras pessoas que usam seu código, a menos que seja óbvio por que isso aconteceria.