在python中,如何将类对象转换为dict

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/35282222/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 16:14:08  来源:igfitidea点击:

In python, how do I cast a class object to a dict

python

提问by penchant

Let's say I've got a simple class in python

假设我在 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}

I can cast it to an integer very easily

我可以很容易地将它转换为整数

>>> w = Wharrgarbl('one', 'two', 'three', 6)
>>> int(w)
9006

Which is great! But, now I want to cast it to a dict in a similar fashion

这很棒!但是,现在我想以类似的方式将其转换为 dict

>>> w = Wharrgarbl('one', 'two', 'three', 6)
>>> dict(w)
{'a': 'one', 'c': 'three', 'b': 'two'}

What do I need to define for this to work? I tried substituting both __dict__and dictfor __what_goes_here__, but dict(w)resulted in a TypeError: Wharrgarbl object is not iterablein both cases. I don't think simply making the class iterable will solve the problem. I also attempted many googles with as many different wordings of "python cast object to dict" as I could think of but couldn't find anything relevant :{

我需要定义什么才能使其工作?我尝试用__dict__dict代替__what_goes_here__,但在两种情况下都dict(w)产生了 a TypeError: Wharrgarbl object is not iterable。我不认为简单地使类可迭代就可以解决问题。我还尝试了许多谷歌,其中包含尽可能多的不同措辞的“python cast object to dict”,但找不到任何相关内容:{

Also! Notice how calling w.__dict__won't do what I want because it's going to contain w.versionand w.sum. I want to customize the cast to dictin the same way that I can customize the cast to intby using def int(self).

还!请注意 callw.__dict__将不会执行我想要的操作,因为它将包含w.versionw.sum。我想以dictint使用def int(self).

I know that I could just do something like this

我知道我可以做这样的事情

>>> w.__what_goes_here__()
{'a': 'one', 'c': 'three', 'b': 'two'}

But I am assuming there is a pythonic way to make dict(w)work since it is the same type of thing as int(w)or str(w). If there isn't a more pythonic way, that's fine too, just figured I'd ask. Oh! I guess since it matters, this is for python 2.7, but super bonus points for a 2.4 old and busted solution as well.

但我假设有一种 pythonic 方式来dict(w)工作,因为它与int(w)or是同一类型的东西str(w)。如果没有更pythonic的方式,那也很好,只是想我会问。哦!我想因为这很重要,这适用于 python 2.7,但也适用于 2.4 旧的和破坏的解决方案。

There is another question Overloading __dict__() on python classthat is similar to this one but may be different enough to warrant this not being a duplicate. I believe that OP is asking how to cast all the data in his class objects as dictionaries. I'm looking for a more customized approach in that I don't want everything in __dict__included in the dictionary returned by dict(). Something like public vs private variables may suffice to explain what I'm looking for. The objects will be storing some values used in calculations and such that I don't need/want to show up in the resulting dictionaries.

在 python 类上有另一个问题Overloading __dict__()与这个类似,但可能不同到足以保证这不是重复的。我相信 OP 正在询问如何将他的类对象中的所有数据转换为字典。我在寻找,在我不想要的一切,更个性化的方法__dict__包括在返回的字典dict()。诸如公共变量与私有变量之类的东西可能足以解释我在寻找什么。这些对象将存储一些用于计算的值,因此我不需要/不想显示在结果字典中。

UPDATE: I've chosen to go with the asdictroute suggested but it was a tough choice selecting what I wanted to be the answer to the question. Both @RickTeachey and @jpmc26 provided the answer I'm going to roll with but the former had more info and options and landed on the same result as well and was upvoted more so I went with it. Upvotes all around though and thanks for the help. I've lurked long and hard on stackoverflow and I'm trying to get my toes in the water more.

更新:我选择了asdict建议的路线,但选择我想要的答案是一个艰难的选择。@RickTeachey 和@jpmc26 都提供了我将要采用的答案,但前者有更多信息和选项,并且也获得了相同的结果,并且获得了更多投票,所以我同意了。不过大家都点赞,感谢您的帮助。我在stackoverflow上潜伏了很长时间,我正试图让我的脚趾更多地浸入水中。

采纳答案by Rick supports Monica

There are at least fivesix ways. The preferred way depends on what your use case is.

至少有六种方式。首选方式取决于您的用例是什么。

Option 1:Simply add an asdict()method.

选项 1:只需添加一个asdict()方法。

Based on the problem description I would very much consider the asdictway of doing things suggested by other answers. This is because it does not appear that your object is really much of a collection:

根据问题描述,我会非常考虑asdict其他答案建议的做事方式。这是因为看起来您的对象并不是真正的集合:

class Wharrgarbl(object):

    ...

    def asdict(self):
        return {'a': self.a, 'b': self.b, 'c': self.c}

Using the other options below could be confusing for others unless it is very obvious exactly which object members would and would not be iterated or specified as key-value pairs.

使用下面的其他选项可能会让其他人感到困惑,除非很明显哪些对象成员会和不会被迭代或指定为键值对。

Option 1a:Inherit your class from 'typing.NamedTuple'(or the mostly equivalent 'collections.namedtuple'), and use the _asdictmethodprovided for you.

选项 1a:'typing.NamedTuple'(或大部分等效的'collections.namedtuple')继承您的类,并使用为您提供的_asdict方法

from typing import NamedTuple

class Wharrgarbl(NamedTuple):
    a: str
    b: str
    c: str
    sum: int = 6
    version: str = 'old'

Using a named tuple is a very convenient way to add lots of functionality to your class with a minimum of effort, including an _asdictmethod. However, a limitation is that, as shown above, the NT will include allthe members in its _asdict.

使用命名元组是一个以大量的功能添加到您的类用最少的努力,包括非常方便的方法_asdict方法。然而,一个限制是,如上述所示,NT将包括所有在其成员_asdict

If there are members you don't want to include in your dictionary, you'll need to modify the _asdictresult:

如果您不想将某些成员包含在字典中,则需要修改_asdict结果:

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

Another limitation is that NT is read-only. This may or may not be desirable.

另一个限制是 NT 是只读的。这可能是也可能不是可取的。

Option 2:Implement __iter__.

选项 2:实施__iter__.

Like this, for example:

像这样,例如:

def __iter__(self):
    yield 'a', self.a
    yield 'b', self.b
    yield 'c', self.c

Now you can just do:

现在你可以这样做:

dict(my_object)

This works because the dict()constructor accepts an iterable of (key, value)pairs to construct a dictionary. Before doing this, ask yourself the question whether iterating the object as a series of key,value pairs in this manner- while convenient for creating a dict- might actually be surprising behavior in other contexts. E.g., ask yourself the question "what should the behavior of list(my_object)be...?"

这是有效的,因为dict()构造函数接受可迭代的(key, value)对来构造字典。在执行此操作之前,先问问自己这个问题,以这种方式将对象作为一系列键值对进行迭代dict-虽然创建 a 很方便- 在其他上下文中实际上可能是令人惊讶的行为。例如,问自己这个问题“ list(my_object)......的行为应该是什么?”

Additionally, note that accessing values directly using the get item obj["a"]syntax will not work, and keyword argument unpacking won't work. For those, you'd need to implement the mapping protocol.

此外,请注意,使用 get itemobj["a"]语法直接访问值将不起作用,并且关键字参数解包也不起作用。对于那些,您需要实现映射协议。

Option 3:Implementthe mapping protocol. This allows access-by-key behavior, casting to a dictwithout using __iter__, and also provides unpacking behavior ({**my_obj}) and keyword unpacking behavior if all the keys are strings (dict(**my_obj)).

方案3:实施所述映射协议。这允许按键访问行为,在dict不使用的情况下强制转换为 a __iter__{**my_obj}如果所有键都是字符串,还提供解包行为 ( ) 和关键字解包行为 ( dict(**my_obj))。

The mapping protocol requires that you provide (at minimum) two methods together: keys()and __getitem__.

映射协议要求您(至少)一起提供两种方法:keys()__getitem__

class MyKwargUnpackable:
    def keys(self):
        return list("abc")
    def __getitem__(self, key):
        return dict(zip("abc", "one two three".split()))[key]

Now you can do things like:

现在您可以执行以下操作:

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

As mentioned above, if you are using a new enough version of python you can also unpack your mapping-protocol object into a dictionary comprehension like so (and in this case it is not required that your keys be strings):

如上所述,如果您使用的是足够新的 python 版本,您还可以将映射协议对象解包到字典理解中,像这样(在这种情况下,您的键不需要是字符串):

>>> {**m}
{'a': 'one', 'b': 'two', 'c': 'three'}

Note that the mapping protocol takes precedence overthe __iter__method when casting an object to a dictdirectly (without using kwarg unpacking, i.e. dict(m)). So it is possible- and sometimes convenient- to cause the object to have different behavior when used as an iterable (e.g., list(m)) vs. when cast to a dict(dict(m)).

注意,映射协议的优先级高于所述__iter__铸造时的对象的方法dict直接(不使用kwarg拆包,即dict(m))。因此,当将对象用作可迭代对象(例如,list(m))与强制转换为dictdict(m))时,可能会(有时是方便)导致对象具有不同的行为。

EMPHASIZED: Just because you CAN use the mapping protocol, does NOT mean that you SHOULDdo so. Does it actually make sensefor your object to be passed around as a set of key-value pairs, or as keyword arguments and values? Does accessing it by key- just like a dictionary- really make sense?

EMPHASIZED:仅仅因为您可以使用映射协议,并不意味着您应该这样做。将您的对象作为一组键值对或关键字参数和值传递是否真的有意义?通过键访问它 - 就像字典一样 - 真的有意义吗?

If the answer to these questions is yes, it's probably a good idea to consider the next option.

如果这些问题的答案是肯定的,那么考虑下一个选项可能是个好主意。

Option 4:Look into using the 'collections.abc' module.

选项 4:考虑使用'collections.abc' 模块

Inheriting your class from 'collections.abc.Mappingor 'collections.abc.MutableMappingsignals to other users that, for all intents and purposes, your class is a mapping* and can be expected to behave that way.

从其他用户继承您的类'collections.abc.Mapping'collections.abc.MutableMapping向其他用户发出信号,无论出于何种意图和目的,您的类都是一个映射* 并且可以预期以这种方式运行。

You can still cast your object to a dictjust as you require, but there would probably be little reason to do so. Because of duck typing, bothering to cast your mapping object to a dictwould just be an additional unnecessary step the majority of the time.

您仍然可以dict根据需要将对象强制转换为 a ,但可能没有什么理由这样做。由于鸭子输入dict在大多数情况下,麻烦将映射对象转换为 a只是一个额外的不必要步骤。

This answermight also be helpful.

这个答案也可能有帮助。

As noted in the comments below: it's worth mentioning that doing this the abc way essentially turns your object class into a dict-like class (assuming you use MutableMappingand not the read-only Mappingbase class). Everything you would be able to do with dict, you could do with your own class object. This may be, or may not be, desirable.

正如下面的评论中所指出的:值得一提的是,以 abc 方式执行此操作实质上会将您的对象类转换为dict-like 类(假设您使用MutableMapping而不是只读Mapping基类)。你可以用 做的一切dict,你可以用你自己的类对象来做。这可能是,也可能不是,是可取的。

Also consider looking at the numerical abcs in the numbersmodule:

还可以考虑查看numbers模块中的数字 abc :

https://docs.python.org/3/library/numbers.html

https://docs.python.org/3/library/numbers.html

Since you're also casting your object to an int, it might make more sense to essentially turn your class into a full fledged intso that casting isn't necessary.

由于您还将对象强制转换为int,因此本质上将您的类转换为成熟的类可能更有意义,int这样就不需要强制转换了。

Option 5:Look into using the dataclassesmodule(Python 3.7 only), which includes a convenient asdict()utility method.

选项 5:考虑使用该dataclasses模块(仅限 Python 3.7),其中包括一个方便的asdict()实用方法。

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)

Now you can do this:

现在你可以这样做:

    >>> asdict(Wharrgarbl(1,2,3,4,"X"))
    {'a': 1, 'b': 2, 'c': 3}

Option 6:Use typing.TypedDict, which has been added in python 3.8.

选项 6:使用typing.TypedDict,已在 python 3.8 中添加

NOTE: option 6 is likely NOTwhat the OP, or other readers based on the title of this question, are looking for. See additional comments below.

注意:选项 6 可能不是OP 或基于此问题标题的其他读者正在寻找的内容。请参阅下面的其他评论。

class Wharrgarbl(TypedDict):
    a: str
    b: str
    c: str

Using this option, the resulting object is a dict(emphasis: it is nota Wharrgarbl). There is no reason at all to "cast" it to a dict (unless you are making a copy).

使用此选项,生成的对象是 adict(强调:它不是a Wharrgarbl)。根本没有理由将它“投射”到字典中(除非您正在制作副本)。

And since the object is a dict, the initialization signature is identical to that of dictand as such it only accepts keyword arguments or another dictionary.

并且由于对象是 adict,初始化签名与 的相同,dict因此它只接受关键字参数或另一个字典。

    >>> w = Wharrgarbl(a=1,b=2,b=3)
    >>> w
    {'a': 1, 'b': 2, 'c': 3}
    >>> type(w)
    <class 'dict'>

Emphasized: the above "class" Wharrgarblisn't actually a new class at all. It is simply syntactic sugar for creating typed dictobjects with fields of different types for the type checker.

强调:上面的“类”Wharrgarbl实际上根本不是一个新类。它只是一种语法糖,dict用于为类型检查器创建具有不同类型字段的类型化对象。

As such this option can be pretty convenient for signaling to readers of your code (and also to a type checker such as mypy) that such a dictobject is expected to have specific keys with specific value types.

因此,此选项可以非常方便地向代码的读者(以及类型检查器,如 mypy)发出信号,告知此类dict对象应具有具有特定值类型的特定键。

But this means you cannot, for example, add other methods, although you can try:

但这意味着您不能,例如,添加其他方法,尽管您可以尝试:

class MyDict(TypedDict):
    def my_fancy_method(self):
        return "world changing result"

...but it won't work:

...但它不会工作:

>>> MyDict().my_fancy_method()
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
AttributeError: 'dict' object has no attribute 'my_fancy_method'


* "Mapping" has become the standard "name" of the dict-like duck type

* “映射”已成为dict-like鸭子类型的标准“名称”

回答by noisewaterphd

It's hard to say without knowing the whole context of the problem, but I would not override __iter__.

在不了解问题的整个背景的情况下很难说,但我不会覆盖__iter__.

I would implement __what_goes_here__on the class.

我会__what_goes_here__在课堂上实施。

as_dict(self:
    d = {...whatever you need...}
    return d

回答by Joran Beasley

something like this would probably work

这样的事情可能会奏效

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)

回答by Garrett R

I think this will work for you.

我认为这对你有用。

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)

Output

输出

{'a': 1, 'c': 3, 'b': 2, 'sum': 6, 'version': 5}

{'a':1,'c':3,'b':2,'sum':6,'版本':5}

回答by jpmc26

There is no magic method that will do what you want. The answer is simply name it appropriately. asdictis a reasonable choice for a plain conversion to dict, inspired primarily by namedtuple. However, your method will obviously contain special logic that might not be immediately obvious from that name; you are returning only a subset of the class' state. If you can come up with with a slightly more verbose name that communicates the concepts clearly, all the better.

没有什么神奇的方法可以做你想做的事。答案是简单地命名它。asdict是简单转换到 的合理选择dict,主要受namedtuple. 但是,您的方法显然会包含特殊的逻辑,从该名称可能不会立即显而易见;你只返回类状态的一个子集。如果你能想出一个稍微详细一点的名字来清楚地传达概念,那就更好了。

Other answers suggest using __iter__, but unless your object is truly iterable (represents a series of elements), this really makes little sense and constitutes an awkward abuse of the method. The fact that you want to filter out some of the class' state makes this approach even more dubious.

其他答案建议使用__iter__,但除非您的对象是真正可迭代的(代表一系列元素),否则这真的毫无意义并构成对该方法的尴尬滥用。您想要过滤掉一些类的状态这一事实使这种方法更加可疑。

回答by tvt173

Like many others, I would suggest implementing a to_dict() function rather than (or in addition to) allowing casting to a dictionary. I think it makes it more obvious that the class supports that kind of functionality. You could easily implement such a method like this:

像许多其他人一样,我建议实现一个 to_dict() 函数,而不是(或除此之外)允许转换为字典。我认为这使得该类支持这种功能变得更加明显。您可以轻松实现这样的方法:

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