is decorator the right thing to use?

George Sakkis george.sakkis at gmail.com
Sat Sep 27 11:27:13 EDT 2008


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

> On Sep 27, 1:44 am, "Dmitry S. Makovey" <dmi... at makovey.net> 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
better:

#======= 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))
a.amethod('foo')

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

print "unbound proxy calls"
A.bmethod(a,'foo')
A.bmethod2(a,'bar','baz')
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'

#====== proxies.py ======================================

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


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

    def __get__(self, proxy, proxytype):
        if proxy is not None:
            return self.__get_target_attr(proxy)
        else:
            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):
        try:
            delegate = getattr(proxy, self._proxy_attr)
            return getattr(delegate, self.name)
        except AttributeError:
            raise AttributeError('%r object has no attribute %r' %
                                 (proxy.__class__.__name__,
self.name))


class Proxy(object):
    __metaclass__ = _ProxyMeta

    def __init__(self, **attr2delegate):
        self.__dict__.update(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 ;)

George



More information about the Python-list mailing list