In a programme that I am writing, I need to loop through all the instances of a class, the way I got around this was by appending every class to a list like below:
allPeople = []
class Person:
def __init__(self, name, age, height):
self.name = name
self.age = age
self.height = height
allPeople.append(self)
Jeff = Person("Jeff", 20, "1.6")
Bob = Person("Bob", 39, "1.4")
Helen = Person("Helen", 19, "1.3")
for person in allPeople:
print(person.name + " is " + str(person.age))
Is this the best way of doing things or is there a built in python function for this?
-
\$\begingroup\$ I have answered this topic here: stackoverflow.com/a/73540891/19822830 \$\endgroup\$Ankur Gaikwad– Ankur Gaikwad2022年08月30日 10:37:07 +00:00Commented Aug 30, 2022 at 10:37
2 Answers 2
Your approach is ok, but I have just one mention (this is based on personal preferences): I'd iterate over a class not a list which is defined outside it. You'll just have to define a metaclass
which supports iteration.
class IterPerson(type):
def __iter__(cls):
return iter(cls._allPeople)
class Person(metaclass=IterPerson):
_allPeople = []
def __init__(self, name, age, height):
self._allPeople.append(self)
self.name = name
self.age = age
self.height = height
if __name__ == '__main__':
Jeff = Person("Jeff", 20, "1.6")
Bob = Person("Bob", 39, "1.4")
Helen = Person("Helen", 19, "1.3")
for person in Person:
print(person.name + " is " + str(person.age))
Magic methods are always looked up on the class, so adding __iter__
to the class won't make it iterable. However the class is an instance of its metaclass, so that is the correct place to define the __init__
method.
Please note that I also put a _
in front of your list. This is how we tell python the list we'll create is private
. I have also used if __name__ == "main":
which is good practice (it is used to execute some code only if the file was run directly, and not imported)
Second, keep this kind of thing as simple as possible. Don't waste a lot of time and energy on something complex. This is a simple problem, keep the code as simple as possible to get the job done.
-
2\$\begingroup\$ Metaclasses are a good way to solve the problem but I'd go further by defining the
__new__
(to create an_instances
list on the new class automatically) and the__call__
(to populate the_instances
list with the created instance automatically) methods on the metaclass so it is both reusable for any class we want to track instanciation of and DRY so the user don't have to remember to put the_allPeople
list and populate it by hand. \$\endgroup\$301_Moved_Permanently– 301_Moved_Permanently2016年04月19日 12:47:41 +00:00Commented Apr 19, 2016 at 12:47 -
\$\begingroup\$ @Mathias could you please elaborate? I tried to implement your suggestions but I can't seem to get it right \$\endgroup\$YeO– YeO2018年09月12日 11:53:53 +00:00Commented Sep 12, 2018 at 11:53
-
1\$\begingroup\$ @YeO ideone.com/mK2g2h \$\endgroup\$301_Moved_Permanently– 301_Moved_Permanently2018年09月12日 12:01:03 +00:00Commented Sep 12, 2018 at 12:01
-
1\$\begingroup\$ While this is clever, I've not seen this "iterate over a class" pattern and it strikes me as unintuitive from the perspective of the client code. Are there examples of this pattern being used out in the wild I can look at to understand the rationale? I'm failing to see the benefit of all the abstraction for something that could basically be an explicit list of dicts/named tuples (or even the simple class) that can be directly iterated over. What is the motivation for hiding
allPeople
and creating a metaclass? Thanks for the clarification. \$\endgroup\$ggorlen– ggorlen2019年08月08日 15:44:48 +00:00Commented Aug 8, 2019 at 15:44
I recommend keeping the collection of instances of a class public until there is compelling reason to hide such information from the client, and I see no such motivation here. Furthermore, I'm not convinced that Person
is a strong candidate for being a class; it has three properties and no methods.
In keeping the code as explicit and simple as possible, I'd prefer a namedtuple
, with a client-code scoped, external list containing all instances that need to be collected:
from collections import namedtuple
Person = namedtuple('Person', 'name age height')
people = [
Person(name='Jeff', age=20, height=1.6),
Person('Bob', 39, 1.4),
Person('Helen', 19, 1.3),
]
for person in people:
print(f"{person.name} is {person.age}")
Here are the benefits of this approach:
- Everything is completely obvious for the client, who retains power over how to use the data, clarity and access to the list itself with all that it entails.
- The documentation for named tuple says "Named tuple instances do not have per-instance dictionaries, so they are lightweight and require no more memory than regular tuples." The data we're dealing here is basically a rudimentary C struct, so all we need is a lightweight named container.
- Less code, fewer classes, reduced complexity.
From a style standpoint, I recommend adhering to PEP-8--variables are snake_case
.