Recipe6.6.Delegating Special Methods in Proxies


Recipe 6.6. Delegating Special Methods in Proxies

Credit: Gonçalo Rodrigues

Problem

In the new-style object model, Python operations perform implicit lookups for special methods on the class (rather than on the instance, as they do in the classic object model). Nevertheless, you need to wrap new-style instances in proxies that can also delegate a selected set of special methods to the object they're wrapping.

Solution

You need to generate each proxy's class on the fly. For example:

class Proxy(object):     """ base class for all proxies """     def _ _init_ _(self, obj):         super(Proxy, self)._ _init_ _(obj)         self._obj = obj     def _ _getattr_ _(self, attrib):         return getattr(self._obj, attrib) def make_binder(unbound_method):     def f(self, *a, **k): return unbound_method(self._obj, *a, **k)     # in 2.4, only: f._ _name_ _ = unbound_method._ _name_ _     return f known_proxy_classes = {  } def proxy(obj, *specials):     ''' factory-function for a proxy able to delegate special methods '''     # do we already have a suitable customized class around?     obj_cls = obj._ _class_ _     key = obj_cls, specials     cls = known_proxy_classes.get(key)     if cls is None:         # we don't have a suitable class around, so let's make it         cls = type("%sProxy" % obj_cls._ _name_ _, (Proxy,), {  })         for name in specials:             name = '_ _%s_ _' % name             unbound_method = getattr(obj_cls, name)             setattr(cls, name, make_binder(unbound_method))         # also cache it for the future         known_proxy_classes[key] = cls     # instantiate and return the needed proxy     return cls(obj)

Discussion

Proxying and automatic delegation are a joy in Python, thanks to the _ _getattr_ _ hook. Python calls it automatically when a lookup for any attribute (including a methodPython draws no distinction there) has not otherwise succeeded.

In the old-style (classic) object model, _ _getattr_ _ also applied to special methods that were looked up as part of a Python operation. This required some care to avoid mistakenly supplying a special method one didn't really want to supply but was otherwise handy. Nowadays, the new-style object model is recommended for all new code: it is faster, more regular, and richer in features. You get new-style classes when you subclass object or any other built-in type. One day, some years from now, Python 3.0 will eliminate the classic object model, as well as other features that are still around only for backwards-compatibility. (See http://www.python.org/peps/pep-3000.html for details about plans for Python 3.0almost all changes will be language simplifications, rather than new features.)

In the new-style object model, Python operations don't look up special methods at runtime: they rely on "slots" held in class objects. Such slots are updated when a class object is built or modified. Therefore, a proxy object that wants to delegate some special methods to an object it's wrapping needs to belong to a specially made and tailored class. Fortunately, as this recipe shows, making and instantiating classes on the fly is quite an easy job in Python.

In this recipe, we don't use any advanced Python concepts such as custom metaclasses and custom descriptors. Rather, each proxy is built by a factory function proxy, which takes as arguments the object to wrap and the names of special methods to delegate (shorn of leading and trailing double underscores). If you've saved the "Solution"'s code in a file named proxy.py somewhere along your Python sys.path, here is how you could use it from an interactive Python interpreter session:

>>> import proxy >>> a = proxy.proxy([  ], 'len', 'iter')   # only delegate _ _len_ _ & _ _iter_ _ >>> a                                    # _ _repr_ _ is not delegated <proxy.listProxy object at 0x0113C370> >>> a._ _class_ _ <class 'proxy.listProxy'> >>> a._obj [  ] >>> a.append                             # all non-specials are delegated <built-in method append of list object at 0x010F1A10>

Since _ _len_ _ is delegated, len(a) works as expected:

>>> len(a) 0 >>> a.append(23) >>> len(a) 1

Since _ _iter_ _ is delegated, for loops work as expected, as does intrinsic looping performed by built-ins such as list, sum, max, . . . :

>>> for x in a: print x ... 23 >>> list(a) [23] >>> sum(a) 23 >>> max(a) 23

However, since _ _getitem_ _ is not delegated, a cannot be indexed nor sliced:

>>> a._ _getitem_ _ <method-wrapper object at 0x010F1AF0> >>> a[1] Traceback (most recent call last):   File "<interactive input>", line 1, in ? TypeError: unindexable object

Function proxy uses a "cache" of classes it has previously generated, the global dictionary known_proxy_classes, keyed by the class of the object being wrapped and the tuple of special methods' names being delegated. To make a new class, proxy calls the built-in type, passing as arguments the name of the new class (made by appending 'Proxy' to the name of the class being wrapped), class Proxy as the only base, and an "empty" class dictionary (since it's adding no class attributes yet). Base class Proxy deals with initialization and delegation of ordinary attribute lookups. Then, factory function proxy loops over the names of specials to be delegated: for each of them, it gets the unbound method from the class of the object being wrapped, and sets it as an attribute of the new class within a make_binder closure. make_binder deals with calling the unbound method with the appropriate first argument (i.e., the object being wrapped, self._obj).

Once it's done preparing a new class, proxy saves it in known_proxy_classes under the appropriate key. Finally, whether the class was just built or recovered from known_proxy_classes, proxy instantiates it, with the object being wrapped as the only argument, and returns the resulting proxy instance.

See Also

Recipe 6.5 for more information about automatic delegation; Recipe 6.9 for another example of generating classes on the fly (using a class statement rather than a call to type).



Python Cookbook
Python Cookbook
ISBN: 0596007973
EAN: 2147483647
Year: 2004
Pages: 420

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net