decorating container types (Python 2.4)

T

timaranz

Hi,

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
 
J

James Stroud

Hi,

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

You will need a metaclass with a __len__ class attribute

class longmeta(type):
def __len__(cls):
return 7

class A(object):
__metaclass__ = longmeta
pass

py> class longmeta(type):
.... def __len__(cls):
.... return 7
....
py> class A(object):
.... __metaclass__ = longmeta
.... pass
....
py> len(A)
7

James

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

http://www.jamesstroud.com
 
J

James Stroud

Hi,

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.

James

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

http://www.jamesstroud.com
 
T

timaranz

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.

James

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

http://www.jamesstroud.com

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?

Cheers
Tim
 
J

James Stroud

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
 
G

George Sakkis

Hi,

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

Unfortunately __getattr__ is not called for special attributes; I'm
not sure if this is by design or a technical limitation. You have to
manually delegate all special methods (or perhaps write a metaclass
that does this for you).

George
 
J

James Stroud

I was thinking that only one longmeta is really needed and its not
necessary to make a new metaclass at each go:

from types import MethodType

class longmeta(type):
def __new__(cls, *args, **kwargs):
if len(args) == 2:
newcls = type.__new__(cls, args[1], (), {})
len_func = args[0].__len__.im_func
newcls.len_dlgt = MethodType(len_func, newcls, cls)
return newcls
else:
return type.__new__(cls, *args, **kwargs)
def __len__(cls):
return cls.len_dlgt()


E.g.:

py> from types import MethodType
py>
py> class longmeta(type):
.... def __new__(cls, *args, **kwargs):
.... if len(args) == 2:
.... newcls = type.__new__(cls, args[1], (), {})
.... len_func = args[0].__len__.im_func
.... newcls.len_dlgt = MethodType(len_func, newcls, cls)
.... return newcls
.... else:
.... return type.__new__(cls, *args, **kwargs)
.... def __len__(cls):
.... return cls.len_dlgt()
....
py> class A(object):
.... value = 5
.... def __len__(self):
.... return A.value
....
py> a = A()
py> len(a)
5
py> B = longmeta(A, 'B')
py> len(B)
5
py> A.value = 16
py> len(B)
16
py> class C(B): pass
....
py> len(C)
16
py> A.value = 42
py> len(C)
42

James

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

http://www.jamesstroud.com
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,970
Messages
2,570,162
Members
46,710
Latest member
bernietqt

Latest Threads

Top