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


85

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

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

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

太好了!但是,现在我想以类似的方式将其转换为字典

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

我需要定义什么才能使其正常工作?我尝试将__dict__和都替换dict__what_goes_here__,但在两种情况下均dict(w)导致TypeError: Wharrgarbl object is not iterable。我不认为简单地使类可迭代即可解决问题。我也尝试过许多Google,尽我所能想到但没有找到任何相关的词:“ python dict to dict”

也!请注意,调用w.__dict__将不会执行我想要的操作,因为它将包含w.versionand w.sum。我想投定制dict以同样的方式,我可以自定义投地int使用def int(self)

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

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

但是我假设有一种pythonic的dict(w)工作方式,因为它与int(w)or属于同一类型str(w)。如果没有更Python的方式,那也很好,只是想知道我会问。哦! 我想因为这很重要,所以这是针对python 2.7的,但是对于2.4老旧的解决方案也有超级加分。

在python类上还有另一个问题,就是重载__dict __()与此类似,但可能有很大的不同,以保证不会重复。我相信OP正在询问如何将其类对象中的所有数据转换为字典。我正在寻找一种更具个性化的方法,因为我不希望__dict__包含的所有内容都包含在返回的字典中dict()。诸如公共变量和私有变量之类的东西可能足以解释我在寻找什么。这些对象将存储一些用于计算的值,这样我就不需要/不想在结果字典中显示出来。

更新:我选择了asdict建议的路线,但选择我想成为该问题的答案的确是一个艰难的选择。@RickTeachey和@ jpmc26都提供了我要介绍的答案,但是前者具有更多信息和选项,并且也获得了相同的结果,并且被更多投票,所以我选择了它。尽管各地都支持,并感谢您的帮助。我在stackoverflow上潜伏了很长时间,并且努力将自己的脚趾更多地浸入水中。



@GarrettR很有帮助,但是并没有真正揭示出对该特定问题的很多见识。
Zizouz212 '16


@RickTeachey它当然类似于Overloading ...问题,尽管我正在寻找更可定制的dict表示形式。例如,对象可能存储了我不在乎在字典中看到的东西。
亲切的

您的问题被误导了,您不想将对象转换或转换为dict表示形式(这样,如果您转换回去,就可以完全恢复对象)。您只想为(例如)公共实例变量创建字典表示视图。
smci

Answers:


117

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

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

基于问题描述,我会非常考虑asdict其他答案建议的处理方式。这是因为看来您的对象实际上并不是一个集合:

class Wharrgarbl(object):

    ...

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

使用以下其他选项可能会使其他人感到困惑,除非非常清楚地知道哪些对象成员将被或不会被迭代或指定为键值对。

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

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

如果有些成员不想包含在字典中,则需要修改_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

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

选项2:实施__iter__

像这样,例如:

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

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

dict(my_object)

dict()是可行的,因为构造函数接受可重复的(key, value)成对构造字典。在执行此操作之前,请先问自己一个问题,即在方便创建对象的情况下,以这种方式将对象作为一系列键,值对进行迭代dict在其他情况下是否可能实际上是令人惊讶的行为。例如,问自己一个问题:“ list(my_object)……的行为应该是什么?”

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

方案3: 实施所述映射协议。这允许按键访问行为,dict而无需使用强制转换为__iter__,并且{**my_obj}如果所有键都是字符串(dict(**my_obj)),则还提供了拆包行为()和关键字拆包行为。

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

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

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

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

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

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

请注意,在将对象直接投射到直接对象时(不使用kwarg解包,即),映射协议优先于__iter__方法。因此,当将对象用作可迭代对象(例如)与强制转换为()时,可能导致对象具有不同的行为。dictdict(m)list(m)dictdict(m)

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

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

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

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

您仍然可以将对象dict强制转换为所需的对象,但是这样做的理由可能很少。由于使用鸭子输入法dict在大多数情况下,麻烦的将映射对象转换为a只是额外的不必要步骤。

这个答案可能也有帮助。

如下面的注释中所述:值得一提的是,使用abc方法本质上将您的对象类转换为类似dict类(假设您使用的MutableMapping是只读Mapping基类,而不是只读基类)。您可以使用的所有内容dict,也可以使用自己的类对象。这可能是理想的,也可能不是理想的。

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

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

由于您也将对象强制转换为int,因此从本质上将您的类转换为完全成熟的类可能更为有意义,int因此无需强制转换。

选项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)

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

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

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

注意:选项6可能不是OP或基于此问题标题的其他读者所寻找的。请参阅下面的其他注释。

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

使用此选项,得到的对象dict(强调:这是不是一个Wharrgarbl)。完全没有理由将其“投射”到字典中(除非您要进行复制)。

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

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

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

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

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

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

...但是它不起作用:

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

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


1
获得OP想要的效果的一种方法,但是您可能需要澄清它还有其他效果,例如for k, v in my_object也将被启用。
zwol 2016年

@RickTeachey我知道您的目标是什么。我当然不需要对象是可迭代的。
亲切的

为什么我们可以简单地使用其他链接中提到的vars函数/programming/61517/python-dictionary-from-an-objects-fields。我在这个问题上想念什么吗?
用户

@user的OP的问题,只想成员ab以及cdict。该vars函数将返回一个字典,其中包含所有对象成员,包括不需要的成员:sumversion。在某些情况下vars可以使用,但以我的经验来看,它并不是真的惯用的python。通常,人们会更加明确一些,例如通过说“我正在将对象投射到字典上” obj.asdict()。使用时vars,通常更像是obj_kwargs = vars(obj)MyObject(**obj_kwargs)。换句话说:vars主要用于复制对象。
瑞克

如何添加返回的方法self.__dict__呢?
user32882

17

没有任何魔术方法可以满足您的需求。答案就是简单地命名它。asdictdict受到主要启发的普通转换的合理选择namedtuple。但是,您的方法显然将包含特殊的逻辑,从该名称可能不会立即显而易见。您只返回类状态的子集。如果您想出一个稍微冗长的名称来清楚地传达概念,那就更好了。

其他答案建议使用__iter__,但是除非您的对象是真正可迭代的(代表一系列元素),否则这确实没有什么意义,并且构成了该方法的尴尬使用。您想过滤掉某些类状态的事实使这种方法变得更加令人怀疑。


是的,返回类的整个状态的一个子集,即相关性的值,是我想要达到的目标。我已经存储了一些“隐藏”的值,这些值可以使事情更快地进行计算/比较等,但是它们并不是人类想要看到或玩耍的东西,它们也并不能真正代表对象本身。
亲切的

@ tr3buchet在您的示例代码中,这些值被传递给初始化器。这就是您的真实代码中的方式吗?缓存的计算将作为参数传入似乎很奇怪。
jpmc26 2016年

一点都没有,我只是在做它提供了一个例子
倾向

只需要注意,文档似乎可以宽容地使用__iter__其他答案:“对于映射,它应该在容器的键上进行迭代,并且还应作为iterkeys()方法提供。”
dbenton

@dbenton这不是OP所做的,也不是其他答案所建议的。OP的对象不是“容器”,在这种情况下,它基本上被用作collection的同义词。OP的对象也不是映射通常所指的对象。映射将具有任意数量的键和值,并且应该扩展Mapping基类。具有特定命名属性的对象不属于此类别。
jpmc26

11

这样的事情可能会工作

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)

哦,有趣!一个元组的迭代,我没有考虑这一点。我会做一个快速的测试
爱好

同样,返回元组列表的迭代器会将x,y和z的所有值都快照到迭代器所基于的列表中。如果有3条yield语句,则可能(在一个很小的窗口中,但是仍然存在)在线程环境中运行它可能导致另一个线程修改y属性或z属性,从而报告的x,y和z不一致。
PaulMcG'2

@PaulMcGuire:CPython是否保证即使与Joran的代码都一致?我本以为可以安排另一个线程,例如,在x创建第一个元组之后但在使用的第二个元组之前y。我是错的,实际上对于线程调度,列表理解是原子的吗?
史蒂夫·杰索普

现在您要问,我意识到即使列表理解也不能保证是原子的,因为Python的线程切换器是在字节码级别而不是源代码级别这样做的。但是您的公开只在创建列表的过程中-创建列表之后,对属性的任何更新都将无关紧要(除非属性值是可变对象)。最好使用该dis模块查看实际的字节码。
PaulMcG '16

11

我认为这对您有用。

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)

输出量

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


总和和版本发生了什么?与仅__dict__按建议的其他答案进行访问有何不同?
Joran Beasley

这不是字典。对iteritems的调用返回一个迭代器
Garrett R

1
我喜欢这个答案...但是要满足OP的要求,还需要做更多的工作...
Joran Beasley

这个答案与@JoranBeasley确实没有什么不同。区别在于,他具有选择性,而您可以捕获self.__dict__包含我不想在对象的字典版本中包含的项目的整体。
亲切的

6

像许多其他应用程序一样,我建议实现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

1

在不知道问题的全部背景的情况下很难说,但是我不会重写__iter__

我会__what_goes_here__在课堂上实施。

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

不,那是什么变量存储英寸
Zizouz212

3
我特别指出这w.__dict__并不能满足我的需求。
亲切的

但是__dict__可以被覆盖,不是吗?
noisewaterphd

@noisewaterphd不,这是一种神奇的方法(也许您可以重写它,但是至少可以说这令人恐惧)
Joran Beasley

1
为什么不继承dict,或者为什么更好呢,只需在类上创建一个方法以将其作为dict返回即可。
noisewaterphd

0

我正在尝试编写既是alist还是a的类dict。我希望程序员能够将这个对象“投射”到list(删除键)或dict(使用键)。

查看Python当前执行转换的方式dict():它Mapping.update()使用传递的对象进行调用。这是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

大多数情况下,if语句的最后一个子实例(在该子实例中进行迭代other)将在其中重复。但是,如您所见,也可以拥有一个keys()属性。结合使用a__getitem__()可以轻松地将子类正确地转换为字典:

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)

然后这将起作用:

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

您已经实现了映射协议。请参阅上面我的答案中的选项3。如果您还想list放下键,则可以添加一个__iter__遍历值的方法,并且强制转换dict仍将继续起作用。请注意,通常使用字典时,如果将其强制转换为列表,它将返回键,而不是所需的值。除非很明显为什么会发生这种情况,这对于其他使用您的代码的人来说可能是令人惊讶的。
瑞克
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.