Python:使类可迭代

50 投票
6 回答
38541 浏览
提问于 2025-04-16 14:25

我接手了一个项目,这个项目里有很多很大的类,里面全是一些基本的数据类型,比如整数、字符串等等。我想检查一个属性是否存在,但不想手动去定义一个属性的列表。

我想知道,能不能让一个Python的自己可以被遍历,也就是说,我希望能用for attr in Foo:(或者甚至用if attr in Foo)来遍历这个类的所有属性,而不需要先创建这个类的实例。我觉得可以通过定义__iter__来实现,但到目前为止我还没能完全做到我想要的效果。

我通过添加一个__iter__方法实现了一部分功能,代码如下:

class Foo:
    bar = "bar"
    baz = 1
    @staticmethod
    def __iter__():
        return iter([attr for attr in dir(Foo) if attr[:2] != "__"])

不过,这并没有完全达到我的目的:

>>> for x in Foo:
...     print(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'classobj' object is not iterable

尽管如此,这个方法是有效的:

>>> for x in Foo.__iter__():
...     print(x)
bar
baz

6 个回答

8

你可以通过下面的方式来遍历这个类中不隐藏的属性:for attr in (elem for elem in dir(Foo) if elem[:2] != '__')

还有一种更简单的写法是:

def class_iter(Class):
    return (elem for elem in dir(Class) if elem[:2] != '__')

然后

for attr in class_iter(Foo):
    pass
14

这是让一个类的对象可以被遍历的方法。你只需要给这个类提供一个iter方法和一个next()方法,这样就可以遍历类的属性或者它们的值。如果你愿意,可以不写next()方法,或者你可以定义next()方法,并在某些条件下抛出StopIteration异常。

比如:

class Book(object):
      def __init__(self,title,author):
          self.title = title
          self.author = author

      def __iter__(self):
          for each in self.__dict__.values():
              yield each

>>> book  = Book('The Mill on the Floss','George Eliot')
>>> for each in book: each
...
'George Eliot'
'The Mill on the Floss'

这个类可以遍历Book类的属性值。通过提供一个getitem方法,也可以让类的对象变得可遍历。

比如:

class BenTen(object):
    def __init__(self, bentenlist):
        self.bentenlist = bentenlist
        
    def __getitem__(self,index):
        if index <5:
            return self.bentenlist[index]
        else:
            raise IndexError('this is high enough')

>>> bt_obj = BenTen([x for x in range(15)])
>>>for each in bt_obj:each
...
0
1
2
3
4

现在,当BenTen类的对象在for-in循环中使用时,getitem方法会被调用,索引值会逐渐增大,直到抛出IndexError异常。

71

__iter__ 方法加到元类上,而不是直接加到类里(假设你在用 Python 2.x):

class Foo(object):
    bar = "bar"
    baz = 1
    class __metaclass__(type):
        def __iter__(self):
            for attr in dir(self):
                if not attr.startswith("__"):
                    yield attr

如果你在用 Python 3.x,就用下面的:

class MetaFoo(type):
    def __iter__(self):
        for attr in dir(self):
            if not attr.startswith("__"):
                yield attr

class Foo(metaclass=MetaFoo):
    bar = "bar"
    baz = 1

撰写回答