Automatic delegation in Python 3

  • Thread starter Steven D'Aprano
  • Start date
S

Steven D'Aprano

Delegation in old-style classes worked fine:

# Python 2.6.... def __init__(self, x):
.... self.__dict__['x'] = x
.... def __getattr__(self, name):
.... return getattr(self.x, name)
.... def __setattr__(self, name, value):
.... setattr(self.x, name, value)
....
obj = Delegate({})
obj[1] = None
obj
{1: None}



But when I try the equivalent recipe with a new-style class, it behaves
differently:
.... def __init__(self, x):
.... self.__dict__['x'] = x
.... def __getattr__(self, name):
.... return getattr(self.x, name)
.... def __setattr__(self, name, value):
.... setattr(self.x, name, value)
....<__main__.Delegate2 object at 0x8f6130c>


Okay, I get that one... because I'm inheriting from object, __getattr__
picks up object's __str__ method and uses that.

But then there's this:

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'Delegate2' object does not support item assignment


But these work:
{1: None}


What's going on here? I *think* this has something to do with special
double-underscore methods being looked up on the class, not the instance,
for new-style classes, but I'm not entirely sure.

Unfortunately, I need to use delegation, not inheritance, and I need to
use a new-style class, since I will be using Python 3. How can I do
automatic delegation in Python 3? Is my only hope to give up on the
elegance of automatic delegation, and code all the special methods as
manual delegation?

class Delegate2(object):
def __setitem__(self, key, value):
self.x[key] = value
# and so on for everything else I care about...
 
T

Terry Reedy

Delegation in old-style classes worked fine:

# Python 2.6... def __init__(self, x):
... self.__dict__['x'] = x
... def __getattr__(self, name):
... return getattr(self.x, name)
... def __setattr__(self, name, value):
... setattr(self.x, name, value)
...
obj = Delegate({})
obj[1] = None

Since you are not setting a attribute, I was initially surprised that
this worked. Then I remembered that this is executed for old-style
classes as
obj.__getattr__('__setitem__')(1,None)
and that __setattr__ above is not involved.

This is possible because old-style user classes were a world apart form
builtin types. Once user-classes were integrated into the builting
class/type hierarchy, they had to follow the same rules as the latter.
One problem is that classes are also instances of their metaclass. This
is somewhat explained in 3.3.8 'Special method lookup' (which I have
read at least 3 times before understanding).

As you discovered, this is not a complete win. The bottom line of 3.3.8:

"Bypassing the __getattribute__() machinery in this fashion provides
significant scope for speed optimisations within the interpreter, at the
cost of some flexibility in the handling of special methods (the special
method must be set on the class object itself in order to be
consistently invoked by the interpreter)."

You are seeing the cost.
Unfortunately, I need to use delegation, not inheritance, and I need to
use a new-style class, since I will be using Python 3. How can I do
automatic delegation in Python 3? Is my only hope to give up on the
elegance of automatic delegation, and code all the special methods as
manual delegation?

Based on the above, it seems so. But I would search a bit more for
delegation and proxy object and python 3 to see what others have done.
 
T

Thomas Jollans

What's going on here? I *think* this has something to do with special
double-underscore methods being looked up on the class, not the instance,
for new-style classes, but I'm not entirely sure.

Yes, special methods are looked up on the type. So you have to make sure the
type has the methods.
Unfortunately, I need to use delegation, not inheritance, and I need to
use a new-style class, since I will be using Python 3. How can I do
automatic delegation in Python 3? Is my only hope to give up on the
elegance of automatic delegation, and code all the special methods as
manual delegation?

Well, yes, you have to implement all the required special methods in the
Delegate class. But there's no reason you have to do it manually. (I've never
actually used this in the wild, but it looks like it works)
.... T = type(o)
.... class Delegate:
.... def __getattr__(self, name):
.... return getattr(o, name)
.... def __setattr__(self, name, value):
.... setattr(self, name, value)
.... def makewrapper(method):
.... def wrapper(self, *args, **kwargs):
.... return method(o, *args, **kwargs)
.... return wrapper
.... for methodname in dir(T):
.... method = getattr(T, methodname)
.... if methodname not in ('__getattr__', '__setattr__',
.... '__init__', '__new__', '__class__'):
.... try:
.... setattr(Delegate, methodname, makewrapper(method))
.... except: pass
.... return Delegate()
....
D = makeDelegate({'a': 1})
D {'a': 1}
D['a'] 1
D['a'] = 2
D {'a': 2}
D.get('b')
D['b'] = True
D.get('b') True
 
S

Steven D'Aprano

]
Based on the above, it seems so. But I would search a bit more for
delegation and proxy object and python 3 to see what others have done.


That's what I was afraid of. Oh well.

Thanks to those who answered.
 

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

No members online now.

Forum statistics

Threads
473,756
Messages
2,569,535
Members
45,007
Latest member
OrderFitnessKetoCapsules

Latest Threads

Top