Add methods to subclasses in the superclass constructor
I want to add methods (more specifically: aliases) automatically to Python subclasses. If the subclass defines a method named "get", I want to add an alias for the "GET" method to the subclass's dictionary.
In order not to repeat myself, I would like to define this modification procedure in the base class. But if I check the base class __init__
method, there is no such method as it is defined in the subclass. This will become clearer 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 this in a base class?
a source to share
Your class __init__
method adds the associated method as an attribute to instances of your class. This is not exactly the same as adding an attribute to a class. Methods usually work by storing functions in the class as attributes, and then creating method objects, as these functions are retrieved as attributes from the class (creating unbound methods that only know the class they belong to) or instance (creating bound methods that know their copy.)
How is this different from what you are doing? Well, you are assigning an GET
instance attribute to a specific instance, not a class. The bound method becomes part of the instance data:
>>> s.__dict__
{'GET': <bound method Sub.get of <__main__.Sub object at 0xb70896cc>>}
Note that the method is under the key GET
, but not under GET
. GET
- an instance attribute, but GET
not. This is slightly different in different ways: the method does not exist in the class object, so you cannot do Sub.GET(instance)
to invoke the method Sub
GET
, even if you can Sub.GET(instance)
. Second, if you have a Sub subclass that defines its own method GET
, but not its own method GET
, the instance attribute will hide the subclass GET
's method using the bound methodGET
from the base class. Third, 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 retains a link to the bound method. Usually the constraint methods are not partially persisted on the instance to avoid this. Circular references are generally not a big problem because we now have a cyclic-gc ( gc
) module that takes care of them, but it may not always be able to clean up reference loops (like when your class also has __del__
). Finally, storing objects of bound methods usually makes your instances unserializable: most serializers (for example pickle
) cannot handle bound methods.
You don't have to worry about any of these problems, but if you do, there will be a better approach to what you are trying to do: metaclasses. Instead of assigning bound methods to instance attributes when instantiating, you can assign normal functions to class attributes when creating a class:
class MethodAliasingType(type):
def __init__(self, name, bases, attrs):
# attrs is the dict of attributes that was used to create the
# class 'self', modifying it has no effect on the class.
# So use setattr() to set the attribute.
for k, v in attrs.iteritems():
if not hasattr(self, k.upper()):
setattr(self, k.upper(), v)
super(MethodAliasingType, self).__init__(name, bases, attrs)
class Base(object):
__metaclass__ = MethodAliasingType
class Sub(Base):
def get(self):
pass
Now Sub.get
both Sub.get
are indeed aliases, and overriding one and not the other in the subclass works as expected.
>>> Sub.get
<unbound method Sub.get>
>>> Sub.GET
<unbound method Sub.get>
>>> Sub().get
<bound method Sub.get of <__main__.Sub object at 0xb708978c>>
>>> Sub().GET
<bound method Sub.get of <__main__.Sub object at 0xb7089a6c>>
>>> Sub().__dict__
{}
(Of course, if you don't want to override one and not the other in order to work, you can just make it a mistake in your metaclass.) You can do the same as the metaclass with class decorators (in Python 2.6 and more later), but that would mean requiring that the class decorator on every decorator subclass of the base class not inherit.
a source to share