2012-05-30 3 views
19

У меня есть класс, который отслеживает его экземпляры в переменном классе, что-то вроде этого:Могу ли я перебирать класс в 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

Пожалуйста, покажите свой метод класса '__iter__' и пример его отказа. – Marcin

+5

Помните, однако, что вы никогда не избавитесь от своих экземпляров. Вы должны работать со слабыми ссылками. – glglgl

+1

К счастью, время жизни экземпляров не является проблемой. Но я хотел бы посмотреть, как это должно быть сделано. – xorsyst

ответ

25

Если вы хотите перебрать класс , вам необходимо определить метакласс, поддерживающий итерацию.

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 объекта.

+0

Как насчет слабых ссылок, о которых вы говорили? Я не совсем понял, что вы имели в виду, и этот ответ также не содержит ничего об этом –

+0

См. Это на слабых ссылках: http://docs.python.org/library/weakref.html#weakref.WeakValueDictionary – Marcin

+4

Nice touch , делегируя итерацию обратно методу класса. –

8

Магические методы всегда ищут класс, поэтому добавление __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) 

до init из категории foo

Затем каждый раз, когда вы создаете экземпляр класса Foo, он добавляется в список fooList.

Теперь все, что вам нужно сделать, это перебирать массив объектов, как этот

for f in fooList: 
    f.doSomething() 
1

Вы можете создать список класса, а затем вызвать добавление в методе инициализации следующим образом:

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) 
Смежные вопросы