如何在Python中枚举对象的属性?

191

在 C# 中,我们可以通过反射来实现。在 JavaScript 中,它很简单:

for(var propertyName in objectName)
    var currentPropertyValue = objectName[propertyName];

如何用Python实现此操作?


1
你如何获取Python类中的方法列表? - ジョージ
9
请注意,这个问题的名称是错误的。大多数答案都是关于枚举“成员”的。我正在寻找一种方法来枚举“属性”,即用@property修饰的类成员。 - Tomasz Gandor
8个回答

216
for property, value in vars(theObject).items():
    print(property, ":", value)

请注意,在一些罕见情况下存在__slots__属性,这种类通常没有__dict__


1
现在如何更改值?在Javascript中,您可以这样做:object [property] = newValue。在Python中怎么做呢? - Jader Dias
1
@Nelson 你能详细说明为什么这是更好的选择吗?它只是更短,还是有其他考虑因素? - WhyNotHugo
8
@Hugo: 首先因为它符合"Python风格",也就是说这是大多数社区成员期望看到的语法。另一种语法可能会让任何阅读你代码的人不必要地停顿。其次,一些类型实现了一个setter __setattr__()。直接在字典上设置值会绕过对象的setter(和/或其父级) 。在python中,属性设置时背后可能发生更多的事情(例如数据清理),使用setattr()可以确保您不会错过它们,或被迫自己明确处理它们。 - Michael Ekoka
3
@Hi-Angel,这确实有效。然而,不起作用的是你在Python中关于状态和动态对象成员的预设和假设。vars()仅返回静态成员(即使用该对象的__dict__注册的对象属性和方法)。它不会返回动态成员(即由该对象的__getattr__()方法或类似魔术方法动态定义的对象属性和方法)。很可能你期望的file.ImplementationName属性是动态定义的,因此在vars()dir()中 _不可用_。 - Cecil Curry
3
@CecilCurry,虽然我欣赏你的有启发性的评论,但我不欣赏你的讽刺。你所说的话既不明显,也不容易找到,除非一个人足够了解语言内部结构,才不会在这里提出这个问题。它应该在答案中被提到。 - Hi-Angel
显示剩余5条评论

92

6
Python官方文档中有一点需要注意:由于dir()方法主要是为了在交互式提示下使用而提供的便利性,它会尝试提供一组有趣的名称,而不是严格或一致定义的名称,并且它的详细行为可能会随着版本更新而改变。例如,当参数是一个类时,元类属性不会出现在结果列表中。 - skyler
2
在命令行界面进行草稿工作时,此答案是最佳的。 - user1427008
2
我已经使用Python 15年了(虽然不是全职),但我仍然会忘记dir(),谢谢。 - Abhishek Dujari

74

请查看inspect.getmembers(object[, predicate])函数。

返回对象的所有成员,以按名称排序的(名称,值)对列表形式返回。如果提供了可选的谓词参数,则仅包括谓词返回 true 值的成员。

>>> [name for name,thing in inspect.getmembers([])]
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', 
'__delslice__',    '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', 
'__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', 
'__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__','__reduce_ex__', 
'__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__setslice__', 
'__sizeof__', '__str__', '__subclasshook__', 'append', 'count', 'extend', 'index', 
'insert', 'pop', 'remove', 'reverse', 'sort']
>>> 

是的,这个答案很棒;我以前从未使用过这个模块。顺便说一下,getmembers() 是通过遍历 dir(object) 的结果来实现的。 - Nelson
4
inspect.getmembers()保证即使内部细节发生变化也会继续正常工作。 - Georg Schölly
有人可以澄清一下,inspect.getmembers()和dir()在根本上有什么不同,还是它只是dir()的一个方便包装(它文档中的“注意:”暗示可能是这种情况)? - Nicholas Knight
嗯...看起来应该可以工作,但出现了“inspect.UnknownPropertyException”的错误。 - Hi-Angel
3
inspect.getmembers()是将dir()进行了包装,(A)主要包括动态类属性元类属性,这些相对较小的优点。 (B)它排除了不符合传递的谓词的成员。然而,对于标准用例来说,使用dir()就足够了。inspect.getmembers()适用于通用的第三方库,支持所有可能的对象类型。 - Cecil Curry
显示剩余2条评论

21

__dict__ 属性是对象的一个字典,包含了所有定义在该对象中的属性。需要注意的是,Python 类可以覆盖 getattr 并创建看起来像属性但不在__dict__ 中的属性。此外还有内置函数 vars()dir(),它们在细节上有所不同。在一些特殊的类中,__slots__ 可以替代__dict__

在 Python 中,对象很复杂。如果需要进行反射式编程,则应该从 __dict__ 开始。如果你在交互式 shell 中进行编程,则应该从 dir() 开始。


“print vars.__doc__” 表示 “带有参数时,等同于 object.__dict__”。那么微妙的区别是什么呢? - sancho.s ReinstateMonicaCellio

19

对于一行代码:

print vars(theObject)

14

如果您正在寻找所有属性的反映,则上面的答案非常好。

如果您只是想获取字典的键(在Python中与“对象”不同),请使用

my_dict.keys()

my_dict = {'abc': {}, 'def': 12, 'ghi': 'string' }
my_dict.keys() 
> ['abc', 'def', 'ghi']

2
尽管这是我的答案,但我认为它不应该成为被采纳的答案:对象的键与类的对象实例的属性不同。它们的访问方式也不同(obj['key'] vs. obj.property),而问题是关于对象属性的。我在这里放置了我的答案,因为两者之间很容易混淆。 - MrE
1
我实际上建议删除这个答案。 - Ente
如上所述,来自JavaScript或C#等语言的人很容易被术语搞混,因为这两个概念之间没有区别。学习Python并尝试访问键的人很可能会搜索“属性”,最终到达这里,这就是我认为它应该属于这里的原因。 - MrE
你说得没错,但是有点偏离重点:在其他语言中(比如JS),对象和字典之间没有区别。OP来自C#并提出了问题。这个回答虽然是错误的,但它获得了11个赞,证明来到这里的人们发现它有用,即使它在严格的Python术语下不是正确的答案(正如我所指出的)。我会进行编辑,让它更加清晰易懂。 - MrE

12

虽然其他回答已经完全覆盖了这一点,但我会明确表达一下。 一个对象可能有类属性和静态和动态实例属性。

class foo:
    classy = 1
    @property
    def dyno(self):
        return 1
    def __init__(self):
        self.stasis = 2

    def fx(self):
        return 3

stasis是静态的,dyno是动态的(参见属性装饰器),而classy是类属性。如果我们简单地使用__dict__vars,我们只会得到静态的那一个。

o = foo()
print(o.__dict__) #{'stasis': 2}
print(vars(o)) #{'stasis': 2}

因此,如果我们想要获得其他对象的__dict__,将获取所有内容(以及更多)。这包括魔术方法、属性和普通绑定方法。因此让我们避免这些:

d = {k: getattr(o, k, '') for k in o.__dir__() if k[:2] != '__' and type(getattr(o, k, '')).__name__ != 'method'}
print(d) #{'stasis': 2, 'classy': 1, 'dyno': 1}

使用属性装饰的方法(动态属性)调用的type将给出返回值的类型,而不是method的类型。为了证明这一点,让我们将其转为JSON字符串:

import json
print(json.dumps(d)) #{"stasis": 2, "classy": 1, "dyno": 1}

如果它是一种方法,它就会崩溃。

太长不看。尝试调用extravar = lambda o: {k: getattr(o, k, '') for k in o.__dir__() if k[:2] != '__' and type(getattr(o, k, '')).__name__ != 'method'},但不包括方法和魔法。


4

我认为展示所提到的各种选项之间的区别是值得的 - 通常一幅图像胜过千言万语。

>>> from pprint import pprint
>>> import inspect
>>>
>>> class a():
    x = 1               # static class member
    def __init__(self):
        self.y = 2      # static instance member
    @property
    def dyn_prop(self): # dynamic property
        print('DYNPROP WAS HERE')
        return 3
    def test(self):     # function member
        pass
    @classmethod
    def myclassmethod(cls): # class method; static methods behave the same
        pass

>>> i = a()
>>> pprint(i.__dict__)
{'y': 2}
>>> pprint(vars(i))
{'y': 2}
>>> pprint(dir(i))
['__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'dyn_prop',
 'myclassmethod',
 'test',
 'x',
 'y']
>>> pprint(inspect.getmembers(i))
DYNPROP WAS HERE
[('__class__', <class '__main__.a'>),
 ('__delattr__',
  <method-wrapper '__delattr__' of a object at 0x000001CB891BC7F0>),
 ('__dict__', {'y': 2}),
 ('__dir__', <built-in method __dir__ of a object at 0x000001CB891BC7F0>),
 ('__doc__', None),
 ('__eq__', <method-wrapper '__eq__' of a object at 0x000001CB891BC7F0>),
 ('__format__', <built-in method __format__ of a object at 0x000001CB891BC7F0>),
 ('__ge__', <method-wrapper '__ge__' of a object at 0x000001CB891BC7F0>),
 ('__getattribute__',
  <method-wrapper '__getattribute__' of a object at 0x000001CB891BC7F0>),
 ('__gt__', <method-wrapper '__gt__' of a object at 0x000001CB891BC7F0>),
 ('__hash__', <method-wrapper '__hash__' of a object at 0x000001CB891BC7F0>),
 ('__init__',
  <bound method a.__init__ of <__main__.a object at 0x000001CB891BC7F0>>),
 ('__init_subclass__',
  <built-in method __init_subclass__ of type object at 0x000001CB87CA6A70>),
 ('__le__', <method-wrapper '__le__' of a object at 0x000001CB891BC7F0>),
 ('__lt__', <method-wrapper '__lt__' of a object at 0x000001CB891BC7F0>),
 ('__module__', '__main__'),
 ('__ne__', <method-wrapper '__ne__' of a object at 0x000001CB891BC7F0>),
 ('__new__', <built-in method __new__ of type object at 0x00007FFCA630AB50>),
 ('__reduce__', <built-in method __reduce__ of a object at 0x000001CB891BC7F0>),
 ('__reduce_ex__',
  <built-in method __reduce_ex__ of a object at 0x000001CB891BC7F0>),
 ('__repr__', <method-wrapper '__repr__' of a object at 0x000001CB891BC7F0>),
 ('__setattr__',
  <method-wrapper '__setattr__' of a object at 0x000001CB891BC7F0>),
 ('__sizeof__', <built-in method __sizeof__ of a object at 0x000001CB891BC7F0>),
 ('__str__', <method-wrapper '__str__' of a object at 0x000001CB891BC7F0>),
 ('__subclasshook__',
  <built-in method __subclasshook__ of type object at 0x000001CB87CA6A70>),
 ('__weakref__', None),
 ('dyn_prop', 3),
 ('myclassmethod', <bound method a.myclassmethod of <class '__main__.a'>>),
 ('test', <bound method a.test of <__main__.a object at 0x000001CB891BC7F0>>),
 ('x', 1),
 ('y', 2)]

总之,如下所述:
  • vars()__dict__只返回实例局部属性;
  • dir()返回所有内容,但仅作为字符串成员名称列表;动态属性未被调用;
  • inspect.getmembers()返回所有内容,作为元组列表(name, value);它实际上运行动态属性,并接受一个可选的predicate参数,可以按值过滤成员。

因此,我的常识性方法通常是在命令行中使用dir(),在程序中使用getmembers(),除非特定的性能考虑。

请注意,为了保持清洁,我没有包括__slots__-如果存在,它是明确放置在那里以进行查询的,并且应直接使用。我也没有涉及到元类,这可能会有点复杂(大多数人都不会使用它们)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接