add methods in subclasses within the super class constructor
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 d开发者_Go百科o 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 GET
instance attribute of a specific instance, not the class. The bound method becomes part of the instance's data:
>>> s.__dict__
{'GET': <bound method Sub.get of <__main__.Sub object at 0xb70896cc>>}
Notice how the method is there under the key GET
, but not under get
. GET
is an instance attribute, but get
is not. This is subtly different in a number of ways: the method doesn't exist in the class object, so you can't do Sub.GET(instance)
to call Sub
's GET
method, even though you can do Sub.get(instance)
. Secondly, if you have a subclass of Sub that defines its own GET
method but not its own get
method, the instance attribute would hide the subclass GET
method with the bound get
method 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 as pickle
) 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:
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
and Sub.GET
really are aliases, and overriding the one and not the other in a 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 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.
Its because class Sub hasn't been initiated yet, do it in its instance like
>>> s=Sub()
>>> dir(s)
['GET', '__doc__', '__init__', '__module__', 'get']
>>>
Create a derived constructor in your derived class which sets the attribute.
精彩评论