How to conserve list structure when working with Composition in Python?
For instance,
class Population:
'''Defines a population of Colonies'''
def __init__(self, Nc, ...):
self.number_of_colonies = Nc
colonies: list[Colony]
self.colonies = list(Colony(...) for i in range(0, Nc))
class Colony:
'''Defines a Colony'''
def __init__(self, ...):
self.propertyA = 1
myPop = Population(10)
Therefore myPop
is a list of 10 colonies, which I access by myPop.colonies
.
Now, I want a list of propertyA
across all 10 colonies.
Is there a way to conserve the structure of colony and write myPop.colonies.propertyA
to return [1,1,1,1,1,1,1,1,1,1]
?
Basically it'd be nice to have a way around writing [colony.propertyA for colony in myPop.colonies]
as it appear very frequently in my code.
I'm reading about composition and composites with multiple compositions in Python, but I haven't found the 'A.B.C' structure.
You can't do this out of the box, because .colonies
part is a list
. It has access to methods any other list
has, which means something like propertyA
isn't really defined.
There are several ways to enable this kind of syntax and I'll present 2 of them, both with some upsides and downsides.
class Population:
'''Defines a population of Colonies'''
def __init__(self, Nc):
self.number_of_colonies = Nc
self.colonies = ColonyList(Colony() for _ in range(0, Nc))
class Colony:
'''Defines a Colony'''
def __init__(self):
self.propertyA = 1
class ColonyList(list[Colony]):
@property
def propertyA(self):
return [el.propertyA for el in self]
myPop = Population(10)
print(myPop.colonies.propertyA)
This explicitly defines a propertyA
on the list object, meaning only this specific attribute will be able to be accessed this way. This requires more handling for additional attributes, but is less error-prone, as the property is explicitly created.
Another, slightly similiar (with list
subclass) approach is to implement __getattr__
method that will handle all attributes that don't share names with list
methods, but has a greater chance of attempting to get a value of attribute that does not exist:
class ColonyList(list[Colony]):
def __getattr__(self, name):
return [getattr(el, name) for el in self]