J'ai hérité d'un projet avec beaucoup de grandes classes ne constituant que des objets de classe (entiers, chaînes, etc.). J'aimerais pouvoir vérifier si un attribut est présent sans qu'il soit nécessaire de définir une liste d'attributs manuellement.
Est-il possible de faire un python class iterable en utilisant la syntaxe standard? Autrement dit, j'aimerais pouvoir parcourir tous les attributs d'une classe à l'aide de for attr in Foo:
(ou même de if attr in Foo
) sans avoir à créer d'abord une instance de la classe. Je pense pouvoir le faire en définissant __iter__
, mais je n’ai pas encore tout à fait réussi ce que je cherchais.
J'ai réalisé une partie de ce que je veux en ajoutant une méthode __iter__
comme ceci:
class Foo:
bar = "bar"
baz = 1
@staticmethod
def __iter__():
return iter([attr for attr in dir(Foo) if attr[:2] != "__"])
Cependant, cela ne donne pas tout à fait ce que je recherche:
>>> for x in Foo: ... print(x) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'classobj' object is not iterable
Néanmoins, cela fonctionne:
>>> for x in Foo.__iter__(): ... print(x) bar baz
Ajoutez le __iter__
à la métaclasse au lieu de la classe elle-même (en supposant que 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
Pour Python 3.x, utilisez
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
Vous pouvez parcourir les attributs non masqués de la classe avec for attr in (elem for elem in dir(Foo) if elem[:2] != '__')
.
Une façon moins horrible d'épeler c'est:
def class_iter(Class):
return (elem for elem in dir(Class) if elem[:2] != '__')
puis
for attr in class_iter(Foo):
pass
c'est ainsi que nous faisons un objet de classe itérable. fournissez à la classe une méthode iter et une méthode next (), puis vous pourrez parcourir les attributs de la classe ou leurs valeurs.Vous pouvez laisser la méthode next () si vous le souhaitez ou vous pouvez définir next () et StopIteration sous certaines conditions.
par exemple:
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'
cette classe itère sur la valeur d'attribut de la classe Book. Un objet de classe peut être rendu itérable en lui fournissant également une méthode getitem . par exemple:
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
désormais, lorsque l'objet de la classe BenTen est utilisé dans une boucle for-in, getitem est appelé avec une valeur d'index successivement supérieure, jusqu'à ce qu'il lève IndexError.
Depuis Python 3.4+, rendre une classe itérable est un peu plus facile avec 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