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