Python: hacer la clase iterable

He heredado un proyecto con muchas clases grandes que constituyen nada más que objetos de clase (enteros, cadenas, etc.). Me gustaría poder comprobar si un atributo está presente sin necesidad de definir una lista de atributos manualmente.

¿Es posible hacer que una clase de python sea iterable usando la syntax estándar? Es decir, me gustaría poder iterar sobre todos los atributos de una clase usando for attr in Foo: (o incluso if attr in Foo ) sin necesidad de crear primero una instancia de la clase. Creo que puedo hacer esto definiendo __iter__ , pero hasta ahora no he logrado lo que estoy buscando.

He logrado algo de lo que quiero al agregar un método __iter__ así:

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

Sin embargo, esto no logra lo que estoy buscando:

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

Aun así, esto funciona:

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

Agregue el __iter__ a la metaclase en lugar de la propia clase (asumiendo 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 

Para Python 3.x, use

 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 

Puede iterar sobre los atributos no ocultos de la clase con for attr in (elem for elem in dir(Foo) if elem[:2] != '__') .

Una forma menos horrible de deletrear eso es:

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

entonces

 for attr in class_iter(Foo): pass 

Así es como hacemos un objeto de clase iterable. proporcione a la clase un método iter y next (), luego puede iterar sobre los atributos de la clase o sus valores. Puede dejar el método next () si lo desea, o puede definir next () y elevar StopIteration en alguna condición .

p.ej:

 class Book(object): def __init__(self,title,author): self.title = title self.author = author def __iter__(self): for each in self.__dict__.keys(): yield self.__getattribute__(each) >>> book = Book('The Mill on the Floss','George Eliot') >>> for each in book: each ... 'George Eliot' 'The Mill on the Floss' 

esta clase itera sobre el valor de atributo de la clase Libro. Un objeto de clase puede hacerse iterable si se le proporciona también un método getitem . p.ej:

 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 

Ahora, cuando el objeto de la clase BenTen se usa en un bucle for-in, se llama a getitem con un valor de índice sucesivamente más alto, hasta que genera IndexError.

A partir de Python 3.4+, hacer que una clase sea iterable es un poco más fácil usando enum.Enum .

 from enum import Enum class Foo(Enum): bar = "qux" baz = 123 >>> print(*Foo) Foo.bar Foo.baz names = [m.name for m in Foo] >>> print(*names) bar baz values = [m.value for m in Foo] print(*values) >>> qux 123 
 class MetaItetaror(type): def __iter__(cls): return iter( filter( lambda k: not k[0].startswith('__'), cls.__dict__.iteritems() ) ) class Klass: __metaclass__ = MetaItetaror iterable_attr_names = {'x', 'y', 'z'} x = 5 y = 6 z = 7 for v in Klass: print v