I want to add methods (more specifically: method aliases) automatically to Python subclasses. If the subclass defines a method named ‘get’ I want to add a method alias ‘GET’ to the dictionary of the subclass.
To not repeat myself I’d like to define this modifation routine in the base class. But if I check in the base class __init__ method, there is no such method, since it is defined in the subclass. It will become more clear with some source code:
class Base:
def __init__(self):
if hasattr(self, "get"):
setattr(self, "GET", self.get)
class Sub(Base):
def get():
pass
print(dir(Sub))
Output:
['__doc__', '__init__', '__module__', 'get']
It should also contain 'GET'.
Is there a way to do it within the base class?
Your class’s
__init__method adds a bound method as an attribute to instances of your class. This isn’t exactly the same as adding the attribute to the class. Normally, methods work by storing functions in the class, as attributes, and then creating method objects as these functions are retrieved as attributes from either the class (creating unbound methods which only know the class they belong to) or the instance (creating bound methods, which know their instance.)How does that differ from what you’re doing? Well, you assign to the
GETinstance attribute of a specific instance, not the class. The bound method becomes part of the instance’s data:Notice how the method is there under the key
GET, but not underget.GETis an instance attribute, butgetis not. This is subtly different in a number of ways: the method doesn’t exist in the class object, so you can’t doSub.GET(instance)to callSub‘sGETmethod, even though you can doSub.get(instance). Secondly, if you have a subclass of Sub that defines its ownGETmethod but not its owngetmethod, the instance attribute would hide the subclassGETmethod with the boundgetmethod from the baseclass. Thirdly it creates a circular reference between the bound method and the instance: the bound method has a reference to the instance, and the instance now stores a reference to the bound method. Normally bound methods are not stored on the instance partly to avoid that. Circular references are usually not a big issue, because we nowadays have the cyclic-gc module (gc) that takes care of them, but it can’t always clean up reference cycles (for instance, when your class also has a__del__method.) And lastly, storing bound method objects generally makes your instances unserializable: most serializers (such aspickle) can’t handle bound methods.You may not care about any of these issues, but if you do, there’s a better approach to what you’re trying to do: metaclasses. Instead of assigning bound methods to instance attributes as you create instances, you can assign normal functions to class attributes as you create the class:
Now,
Sub.getandSub.GETreally are aliases, and overriding the one and not the other in a subclass works as expected.(Of course, if you don’t want overriding the one and not the other to work, you can simply make this an error in your metaclass.) You can do the same thing as the metaclass using class decorators (in Python 2.6 and later), but it would mean requiring the class decorator on every subclass of Base — class decorators aren’t inherited.