在Python中迭代对象属性

247

我有一个Python对象,它有多个属性和方法。我想迭代对象属性。

class my_python_obj(object):
    attr1='a'
    attr2='b'
    attr3='c'

    def method1(self, etc, etc):
        #Statements
我想以动态方式生成一个字典,包含所有对象属性及其当前值,这样如果我稍后添加另一个属性,就不必记得同时更新我的函数。
在php中,变量可以用作键,但在Python中,对象是不可索引的,如果我使用点符号表示属性,它将创建一个名为我的变量名称的新属性,这不是我的意图。
只是为了让事情更清晰:
def to_dict(self):
    '''this is what I already have'''
    d={}
    d["attr1"]= self.attr1
    d["attr2"]= self.attr2
    d["attr3"]= self.attr3
    return d
def to_dict(self):
    '''this is what I want to do'''
    d={}
    for v in my_python_obj.attributes:
        d[v] = self.v
    return d

更新: 在这里,属性指的只是该对象的变量,而不是方法。


3
这个链接可能会对你有所帮助,它讲述了如何在Python中列举对象的属性。 - sean
@sean 尤其是,https://dev59.com/v3M_5IYBdhLWcg3wvFzJ#1251789 是正确的方法;你可以使用可选的 predicate 参数来传递一个可调用对象,它会过滤掉(绑定?)函数(这样就可以去掉方法)。 - Hank Gay
珀西恩,这个回答能解决你的问题吗?如何在Python中枚举对象的属性? - Davis Herring
7个回答

340

假设您有这样一个类:

>>> class Cls(object):
...     foo = 1
...     bar = 'hello'
...     def func(self):
...         return 'call me'
...
>>> obj = Cls()

调用对象上的dir方法会返回该对象的所有属性,包括Python特殊属性。虽然有些对象属性是可调用的,比如方法。

>>> dir(obj)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'bar', 'foo', 'func']

你可以使用列表推导式来过滤掉特殊方法。

>>> [a for a in dir(obj) if not a.startswith('__')]
['bar', 'foo', 'func']

或者,如果你更喜欢使用 map/filter。

>>> filter(lambda a: not a.startswith('__'), dir(obj))
['bar', 'foo', 'func']

如果你想要过滤掉方法,你可以使用内置的 callable 作为检查。

>>> [a for a in dir(obj) if not a.startswith('__') and not callable(getattr(obj, a))]
['bar', 'foo']

你还可以使用以下方法检查类与其实例对象之间的差异。

>>> set(dir(Cls)) - set(dir(object))
set(['__module__', 'bar', 'func', '__dict__', 'foo', '__weakref__'])

3
@Meitham @Pablo 主要问题在于,您不应该需要这样做。您感兴趣的属性应该是包容性的而不是排他性的。正如您已经看到的那样,您正在包含方法,任何试图排除它们的尝试都将是有缺陷的,因为它们将涉及到一些麻烦的东西,比如callabletypes.MethodType。如果我添加一个名为“_foo”的属性来存储某些中间结果或内部数据结构会发生什么?如果我只是毫无意义地向类中添加一个属性,比如一个name,现在突然被包括进去了,会发生什么? - Julian
4
以上代码会选择_fooname,因为它们现在是对象的属性。如果您不想包括它们,可以在列表推导式条件中排除它们。上述代码是Python中常见的习语和检查Python对象的流行方式。 - Meitham
50
实际上,对于这个目的,obj.__dict__(可能)更好。 - Dave
6
dir() 只有在传递元类(一个极端情况)或使用 @DynamicClassAttribute 装饰的属性的类(另一个极端情况)时才会“欺骗”您。在这两种情况下,调用 dirs() 包装的 inspect.getmembers() 解决此问题。然而,对于标准对象,该解决方案的列表推导方法过滤掉非可调用对象绝对足够了。一些 Python 程序员将其称为“坏主意”让我感到困惑,但......每个人都有自己的想法,我想? - Cecil Curry
8
你也可以使用vars(obj)代替dir(obj),它会返回 __dict__。参见:https://dev59.com/uV4d5IYBdhLWcg3wFPQ7#27181165 - Anton Tarasenko
显示剩余11条评论

79

通常情况下,您需要在您的类中添加一个__iter__方法,并遍历对象属性,或者将这个混合类放在您的类中。

class IterMixin(object):
    def __iter__(self):
        for attr, value in self.__dict__.iteritems():
            yield attr, value

你的班级:

>>> class YourClass(IterMixin): pass
...
>>> yc = YourClass()
>>> yc.one = range(15)
>>> yc.two = 'test'
>>> dict(yc)
{'one': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 'two': 'test'}

只有在yc = YourClass()之后定义onetwo才能正常工作。该问题询问如何循环遍历YourClass()中现有的属性。此外,继承IterMixin类可能并不总是可行的解决方案。 - XoXo
5
vars(yc) 不需要继承另一个类就能得到相同的结果。 - Nathan
1
我之前的评论不太准确;vars(yc) 几乎相同,但它返回的字典是实例自己的 __dict__,因此修改它将反映在实例上。如果您不小心,这可能会导致问题,因此上述方法有时可能更好(尽管复制字典可能更容易)。另外,请注意,虽然上面返回的字典不是实例的 __dict__,但值是相同的,因此修改任何可变值仍将反映在实例的属性中。 - Nathan

71

正如一些答案/评论中已经提到的那样,Python对象已经存储了它们属性的字典(方法不包括在内)。这可以通过__dict__访问,但更好的方式是使用vars(虽然输出相同)。请注意,修改此字典将修改实例上的属性!这非常有用,但也意味着您应该小心使用此字典。以下是一个快速示例:

class A():
    def __init__(self, x=3, y=2, z=5):
        self.x = x
        self._y = y
        self.__z__ = z

    def f(self):
        pass

a = A()
print(vars(a))
# {'x': 3, '_y': 2, '__z__': 5}
# all of the attributes of `a` but no methods!

# note how the dictionary is always up-to-date
a.x = 10
print(vars(a))
# {'x': 10, '_y': 2, '__z__': 5}

# modifying the dictionary modifies the instance attribute
vars(a)["_y"] = 20
print(vars(a))
# {'x': 10, '_y': 20, '__z__': 5}

使用dir(a)是一种奇怪的方法,如果不是彻底错误的话,来解决这个问题。如果你确实需要迭代类的所有属性和方法(包括__init__等特殊方法),那么它非常好。然而,这似乎并不是你想要的,即使被接受的回答也通过应用一些脆弱的过滤器来尝试删除方法并仅留下属性,而这很容易失败,你可以看到这会对上面定义的A类产生影响。

(在一些答案中使用了__dict__,但它们都定义了不必要的方法,而没有直接使用它。只有一个评论建议使用vars)。


1
我还没有弄清楚vars()方法处理的情况,即类A具有成员变量,其类型为用户定义的类B的另一个对象。vars(a)似乎会调用类型B的成员变量的__repr__()方法。据我所知,repr()应该返回一个字符串。但是当我调用vars(a)时,这个调用似乎应该返回一个嵌套字典,而不是一个带有B的字符串表示的字典。 - plafratt
2
如果您有a.b作为一些自定义类B,那么vars(a)["b"] is a.b,就像人们所期望的一样;没有其他东西真正有意义(将a.b视为a.__dict__["b"]的语法糖)。如果您有d = vars(a)并调用repr(d),则它将调用repr(d["b"])作为其自己的repr字符串的一部分,因为只有类B真正知道它应该如何表示为字符串。 - Nathan
我尝试在psutil返回的svmem对象上使用vars(),但失败了,提示该对象没有__dict__成员。虽然dir()可以工作,但我可以看到它的输出中没有__dict__psutil文档建议使用._fields。只是想指出一个vars()失败的情况,尽管我喜欢使用它的想法。 - Kadir A. Peker
@KadirA.Peker 这是真的,__dict__ 需要被定义才能正常工作。大多数非内置 Python 类应该都有这个属性,但对于更注重性能的类,它们可能会定义 __slots__。调用 dir 总是可以工作的,但是你需要过滤掉方法和诸如 __doc__ 之类的东西。你还可以像 {name: getattr(a, name) for name in a.__slots__} 这样做,对于一些已定义了 __slots__ 的对象 a,而不是使用 __dict__。(像元组这样的对象既没有 __dict__ 也没有 __slots__ - Nathan

4

Python中的对象会将它们的属性(包括函数)存储在一个名为__dict__的字典中。您可以使用它来直接访问属性,但通常不建议这样做。如果您只需要一个列表,则还可以调用dir(obj),它返回一个可迭代的所有属性名称的列表,然后可以将其传递给getattr

然而,需要对变量名称进行任何操作通常都是不好的设计。为什么不将它们保留在集合中呢?

class Foo(object):
    def __init__(self, **values):
        self.special_values = values

你可以使用for key in obj.special_values:来遍历键。

你能再解释一下我如何使用集合来实现我想要的吗? - Pablo Mescher
1
我不会动态地向对象添加属性。我拥有的变量将长期保持不变,但是我认为能够实现我打算做的事情会很好。 - Pablo Mescher

2
class SomeClass:
    x = 1
    y = 2
    z = 3
    
    def __init__(self):
        self.current_idx = 0
        self.items = ["x", "y", "z"]
            
    def next(self):
        if self.current_idx < len(self.items):
            self.current_idx += 1
            k = self.items[self.current_idx-1]
            return (k, getattr(self, k))
        else:
            raise StopIteration
            
    def __iter__(self):
        return self

那么,只需将其作为可迭代对象调用即可。
s = SomeClass()
for k, v in s:
    print k, "=", v

这似乎太复杂了。如果没有其他选择,我宁愿坚持现在的做法。 - Pablo Mescher

0

适用于Python 3.6

class SomeClass:

    def attr_list(self, should_print=False):

        items = self.__dict__.items()
        if should_print:
            [print(f"attribute: {k}    value: {v}") for k, v in items]

        return items

10
你能否在你的帖子中添加一些解释,以便非专业的Python程序员也可以从你的答案中受益? - not2qubit

0

正确的答案是你不应该这样做。如果你想要这种类型的东西,可以使用字典,或者需要显式地向某个容器添加属性。你可以通过学习装饰器来自动化这个过程。

顺便说一下,在你的例子中,method1同样是一个很好的属性。


3
是的,我知道我不应该这样做,但这有助于我理解事物是如何工作的。我来自 PHP 背景,过去经常这样做。 - Pablo Mescher
你说服了我。保持 to_dict() 方法的更新比迄今为止任何答案都要简单得多。 - Pablo Mescher
2
还有人会避免教条主义的建议吗?如果不小心使用动态编程,它会让你受伤,但是语言中的功能是可以使用的。 - Henrik Vendelbo
抱歉,我们在这里是为了回答问题而不是评判它们。 - Bernd Wechner
我来这里的目的不是为了那个,也许其他人是为了那个;我在这里是为了帮助人们编写好的程序,而不是毫无意义地回答问题,而不考虑它们是否有助于人们朝着这个方向前进。不过,你可以随意使用这个网站。 - Julian

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