is decorator the right thing to use?

George Sakkis george.sakkis at
Sat Sep 27 17:27:13 CEST 2008

On Sep 27, 9:23 am, George Sakkis <george.sak... at> wrote:

> On Sep 27, 1:44 am, "Dmitry S. Makovey" <dmi... at> wrote:
> > I guess my bias is towards more explicit declarations thus
> >  bmethod=ProxyMethod('b',B.bmethod)
> > looks more attractive to me, but I stand to be corrected/educated why is
> > that not the right thing to do?
> I see where you're coming from and I also prefer explicit reflection
> mechanisms instead of strings (e.g. avoid eval/exec as much as
> possible). As I mentioned, the second argument to ProxyMethod is (for
> all sane purposes) redundant, so if you could implement it in a way
> that "bmethod = ProxyMethod('b')" worked, I would be all for it, but
> AFAIK it's not possible without a metaclass.

Just for completeness, here's a metaclass version that uses
ProxyMethod declarations instead of a dict; you'll probably like this

#======= usage =========================
from proxies import Proxy, ProxyMethod

class B(object):
    def __init__(self, val): self.val = val
    def bmethod(self,n): print "B::bmethod", self.val, n
    def bmethod2(self,n,m): print "B::bmethod2", self.val, n, m

class C(object):
    def __init__(self, val): self.val = val
    def cmethod(self,x): print "C::cmethod", self.val, x
    def cmethod2(self,x,y): print "C::cmethod2",self.val, x, y
    cattr = 4

class A(Proxy):
    def __init__(self, b1, b2, c):
        print "init A()"
        # must call Proxy.__init__
        super(A,self).__init__(b1=b1, b2=b2, c=c)

    def amethod(self,a):
        print "A::mymethod",a

    bmethod  = ProxyMethod('b1')
    bmethod2 = ProxyMethod('b2')
    cmethod  = ProxyMethod('c')

a = A(B(10), B(20), C(30))

print "bound proxy calls"
try: a.cmethod2('bar','baz')
except Exception, ex: print ex

print "unbound proxy calls"
A.cmethod(a, 'foo')
try: A.cmethod2(a,'bar','baz')
except Exception, ex: print ex

#======= output ========================================

init A()
A::mymethod foo
bound proxy calls
B::bmethod 10 foo
B::bmethod2 20 bar baz
C::cmethod 30 foo
'A' object has no attribute 'cmethod2'
unbound proxy calls
B::bmethod 10 foo
B::bmethod2 20 bar baz
C::cmethod 30 foo
type object 'A' has no attribute 'cmethod2'

#====== ======================================

class _ProxyMeta(type):
    def __new__(meta, name, bases, namespace):
        for attrname,value in namespace.iteritems():
            if isinstance(value, ProxyMethod) and is None:
       = attrname
        return super(_ProxyMeta,meta).__new__(meta, name, bases,

class ProxyMethod(object):
    def __init__(self, proxy_attr, name=None):
        self._proxy_attr = proxy_attr = name

    def __get__(self, proxy, proxytype):
        if proxy is not None:
            return self.__get_target_attr(proxy)
            return self.__unbound_method

    def __unbound_method(self, proxy, *args, **kwds):
        method = self.__get_target_attr(proxy)
        return method(*args, **kwds)

    def __get_target_attr(self, proxy):
            delegate = getattr(proxy, self._proxy_attr)
            return getattr(delegate,
        except AttributeError:
            raise AttributeError('%r object has no attribute %r' %

class Proxy(object):
    __metaclass__ = _ProxyMeta

    def __init__(self, **attr2delegate):


If you want to eliminate completely specifying attributes with
strings, it's easy to modify the above so that you write instead:

class A(Proxy):
    bmethod  = ProxyMethod(lambda self: self.b1)
    bmethod2 = ProxyMethod(lambda self: self.b2)

This is more verbose for the common case, but it's more flexible in
cases where the callable may be more complex than a plain getattr().
Actually you can support both, it doesn't have to be either/or; just
check whether the argument to ProxyMethod is a callable and if not,
make it:

from operator import attrgetter

class ProxyMethod(object):
    def __init__(self, proxy_attr, name=None):
        if not callable(proxy_attr):
            proxy_attr = attrgetter(proxy_attr)

Remaining Implementation is left as an exercise to the reader ;)


More information about the Python-list mailing list