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