我可以在Python中迭代一个类吗?

时间:2012-05-30 10:33:34

标签: python

我有一个类在类变量中跟踪它的实例,如下所示:

class Foo:
    by_id = {}

    def __init__(self, id):
        self.id = id
        self.by_id[id] = self

我希望能够做的是迭代现有的类实例。我可以这样做:

for foo in Foo.by_id.values():
    foo.do_something()

但它会像这样整洁:

for foo in Foo:
    foo.do_something()
这可能吗?我尝试定义了一个classmethod __iter__,但是没有用。

4 个答案:

答案 0 :(得分:28)

如果要迭代,则必须定义支持迭代的元类。

x.py:

class it(type):
    def __iter__(self):
        # Wanna iterate over a class? Then ask that class for iterator.
        return self.classiter()

class Foo:
    __metaclass__ = it # We need that meta class...
    by_id = {} # Store the stuff here...

    def __init__(self, id): # new isntance of class
        self.id = id # do we need that?
        self.by_id[id] = self # register istance

    @classmethod
    def classiter(cls): # iterate over class by giving all instances which have been instantiated
        return iter(cls.by_id.values())

if __name__ == '__main__':
    a = Foo(123)
    print list(Foo)
    del a
    print list(Foo)

正如您最后所看到的,删除实例不会对对象本身产生任何影响,因为它保留在by_id dict中。您可以在

时使用weakref来解决这个问题
import weakref

然后再做

by_id = weakref.WeakValueDictionary()

。这样,只有存在“强”引用时,值才会保留,例如a。在del a之后,只有弱引用指向该对象,因此它们可以被gc'ed。

由于有关WeakValueDictionary()的警告,我建议使用以下内容:

[...]
    self.by_id[id] = weakref.ref(self)
[...]
@classmethod
def classiter(cls):
    # return all class instances which are still alive according to their weakref pointing to them
    return (i for i in (i() for i in cls.by_id.values()) if i is not None)

看起来有点复杂,但要确保获得对象而不是weakref对象。

答案 1 :(得分:9)

总是在类上查找魔术方法,因此向类中添加__iter__将不会使其可迭代。但是,类是其元类的实例,因此元类是定义__iter__方法的正确位置。

class FooMeta(type):
    def __iter__(self):
        return self.by_id.iteritems()

class Foo:
    __metaclass__ = FooMeta
    ...

答案 2 :(得分:1)

试试这个:

您可以创建具有全局范围的列表,在主模块中定义列表,如下所示:

fooList = []

然后添加:

class Foo:
  def __init__(self):
    fooList.append(self)

到foo类的 init

然后,每次创建Foo类的实例时,它都会被添加到fooList列表中。

现在你所要做的就是遍历像这样的对象数组

for f in fooList:
    f.doSomething()

答案 3 :(得分:1)

您可以创建一个类列表,然后在init方法中调用append,如下所示:

class Planet:
  planets_list = []

  def __init__(self, name):
     self.name = name
     self.planets_list.append(self)

用法:

p1 = Planet("earth")
p2 = Planet("uranus")

for i in Planet.planets_list:
    print(i.name)