views:

87

answers:

3

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?

+2  A: 

Its because class Sub hasn't been initiated yet, do it in its instance like

>>> s=Sub()
>>> dir(s)
['GET', '__doc__', '__init__', '__module__', 'get']
>>>
S.Mark
A: 

Create a derived constructor in your derived class which sets the attribute.

Snake
Then I would repeat myself. But it works after instantiation, as 'S.Mark' has shown.
deamon
+1  A: 

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.

Thomas Wouters