Reputation: 3
Problem
Let's say I have a hierarchy of classes where each class has an associated list of class-specific items, e.g.
class Thing:
Items = ['Exist']
class Being(Thing):
Items = ['Live', 'Die']
class Human(Being):
Items = ['Read' 'Write']
I'd like to define a function GetAllItems() that for any class in the hierarchy, will return its own items and those of its superclasses, i.e:
Thing.GetAllItems() # ==> ['Exist']
Being.GetAllItems() # ==> ['Live', 'Die', 'Exist']
Human.GetAllItems() # ==> ['Read', 'Write', 'Live', 'Die', 'Exist']
It doesn't have to be static methods, but after all, the items that I want to be able to "accumulate" are fundamentally Class-level properties.
What I tried
I was thinking that I could write some kind of simple recursive function, along the lines of:
class A:
Items = [A1, A2, A3]
def GetAllItems(): # not sure how to do this for a static method
return getattr(type(self),'Items') + super().GetAllItems()
and then have this function work in all the derived classes, i.e. B(A), C(B), etc, but that doesn't really work.
The closest I got was to specify a different Items variable in each class, and then provide the (non-static) GetAllItems() function for each class like so:
class A:
ItemsA = [A1, A2, A3]
def GetAllItems():
return A.ItemsA # this is the "root" function
class B(A):
ItemsB = [B1, B2, B3]
def GetAllItems():
return B.ItemsB + super().GetAllItems()
class C(B):
ItemsC = [C1, C2, C3]
def GetAllItems():
return C.ItemsC + super().GetAllItems()
While it's not the end of the world to add a function like this to each derived class, I do have a lot of classes, and it would be nice to be able to do this with a single piece of code somehow without copying and pasting basically the same thing everywhere... Plus I still don't fully understand how super() works, so I feel like there's maybe a more elegant solution to this.
*** UPDATE ***
Just wanted to share a version that seems to work in my simple case (no multiple inheritance)
class Thing:
Items = ['Exist']
@classmethod
def GetAllItems(cls):
return cls.Items
class Being(Thing):
Items = ['Live', 'Die']
@classmethod
def GetAllItems(cls):
return cls.Items + cls.__base__.GetAllItems()
and then all classes derived from Being don't need any additional GetAllItems() declarations. I think this could work for a simple inheritance chain, i.e. B(A), C(B), D(C), etc -- but for more general cases, like multiple inheritance, I think @D. Foley has the right recommendation based on cls.mro()
Upvotes: 0
Views: 77
Reputation: 1079
You can solve this problem elegantly using Python's mro() (Method Resolution Order) to accumulate the Items attributes from all superclasses dynamically. Here's a clean solution:
class Thing:
Items = ['Exist']
@classmethod
def GetAllItems(cls):
all_items = []
for base in reversed(cls.mro()): # Traverse the MRO in reverse order (from base to derived)
if hasattr(base, 'Items'):
all_items.extend(base.Items)
return all_items
class Being(Thing):
Items = ['Live', 'Die']
class Human(Being):
Items = ['Read', 'Write']
# Testing
print(Thing.GetAllItems()) # ['Exist']
print(Being.GetAllItems()) # ['Exist', 'Live', 'Die']
print(Human.GetAllItems()) # ['Exist', 'Live', 'Die', 'Read', 'Write']
Explanation:
Using cls.mro(): This retrieves the method resolution order (MRO) of the class, which lists the class and its base classes in inheritance order. Iterating in Reverse Order: We iterate in reverse so that base class items appear first. Class Method (@classmethod): Allows calling GetAllItems() on the class itself without needing an instance. Checking for Items: The check hasattr(base, 'Items') ensures only classes with an Items attribute contribute to the list.
Why This Works:
No need to manually redefine GetAllItems() in every subclass. Automatically works for any class hierarchy without modifications. Uses a single method in the base class that dynamically resolves items.
Upvotes: 4