decorating container types (Python 2.4)

James Stroud jstroud at mbi.ucla.edu
Fri Oct 12 05:04:07 EDT 2007


timaranz at gmail.com wrote:
> On Oct 12, 12:19 pm, James Stroud <jstr... at mbi.ucla.edu> wrote:
>> timar... at gmail.com wrote:
>>> I have a container class A and I want to add functionality to it by
>>> using a decorator class B, as follows:
>>> class A(object):
>>>     def __len__(self):
>>>         return 5
>>> class B(object):
>>>     def __init__(self, a):
>>>         self._a = a
>>>     def __getattr__(self, attr):
>>>         return getattr(self._a, attr)
>>>     def other_methods(self):
>>>         blah blah blah
>>> I was expecting len(B) to return 5 but I get
>>> AttributeError: type object 'B' has no attribute '__len__'
>>> instead.
>>> I was expecting len() to call B.__len__() which would invoke
>>> B.__getattr__ to call A.__len__ but __getattr__ is not being called.
>>> I can work around this, but I am curious if anyone knows _why_
>>> __getattr__ is not being called in this situation.
>>> Thanks
>>> Tim
>> The why part is that __len__ is an unbound method of the class, not an
>> unbound method of the class's metaclass. Also, your code above makes
>> absolutely no association between classes A and B, which is the most
>> fundamental reason.
> 
> Just so I'm clear on this: Are you saying that the problem with my
> original code is that len() is equivalent to type(x).__len__(x) that
> looks for a class attribute rather than an instance attribute as my
> code requires?
> 
> I don't own class A and they are generated by function calls rather
> than instantiated directly, that's why I'm decorating them instead of
> inheriting.  The crux of the (academic) question is why does len()
> expect '__len__' to be a class attribute instead of an instance
> attribute, and why is this a good idea?

You are taking the length of the *class* (whatever that means) with 
"len(B)", so B must have a method __len__() bound to itself, which would 
be an unbound method of B's class (which I am calling the metaclass of 
B, since B is a class--a class's class is a metaclass). So you want the 
unbound __len__() method of A to be the unbound __len__() method of the 
metaclass of B. This is easier done than said:

def long_class_factory(cls, name):
   class _longmeta(type):
     __len__ = cls.__len__.im_func
   return _longmeta(name, (), {})


E.g.:

py> def long_class_factory(cls, name):
...   class _longmeta(type):
...     __len__ = cls.__len__.im_func
...   return _longmeta(name, (), {})
...
py> class A(object):
...     def __len__(self):
...         return 5
...
py> B = long_class_factory(A, 'B')
py> B
<class '__main__.B'>
py> len(B)
5

Of course you can now inherit from B:

class C(B):
   pass

E.g.:

py> class C(B): pass
...
py> len(C)
5

James

--
James Stroud
UCLA-DOE Institute of Genomics and Proteomics
Box 951570
Los Angeles, CA 90095

http://www.jamesstroud.com



More information about the Python-list mailing list