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

问题描述 投票:24回答:4

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

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()

这可能吗?我尝试定义一个类方法__iter__,但这不起作用。

python
4个回答
28
投票

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

小.朋友:

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中。你可以使用weakrefs来解决这个问题

import weakref

然后呢

by_id = weakref.WeakValueDictionary()

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

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

[...]
    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对象。


9
投票

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

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

class Foo:
    __metaclass__ = FooMeta
    ...

1
投票

试试这个:

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

fooList = []

然后加:

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

到foo类的init

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

现在你所要做的就是迭代这样的对象数组

for f in fooList:
    f.doSomething()

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)
© www.soinside.com 2019 - 2024. All rights reserved.