is decorator the right thing to use?

Aaron "Castironpi" Brady castironpi at gmail.com
Thu Sep 25 19:52:43 CEST 2008


On Sep 25, 12:19 pm, Bruno Desthuilliers <bruno.
42.desthuilli... at websiteburo.invalid> wrote:
> Dmitry S. Makovey a écrit :
>
>
>
> > Thanks Bruno,
>
> > your comments were really helpful (so was the "improved" version of code).
>
> > My replies below:
>
> > Bruno Desthuilliers wrote:
> >>> So decorators inside of B just identify that those methods will be
> >>> proxied by A. On one hand from logical standpoint it's kind of weird to
> >>> tell class that it is going to be proxied by another class,
>
> >> Indeed - usually, proxied objects shouldn't have to be aware of the
> >> fact. That doesn't mean your variation on the proxy pattern is
> >> necessarily bad design (hard to tell without lot of context anyway...),
> >> but still there's some alarm bell ringing here IMHO - IOW : possibly the
> >> right thing to do, but needs to be double-checked.
>
> > I'm kind of looking at options and not dead-set on decorators, but I can't
> > find any other "elegant enough" solution which wouldn't lead to such tight
> > coupling. The problem I'm trying to solve is not much more complicated than
> > what I have already described
>
> Well... You didn't mention why you need a proxy to start with !-)
>
> > so if anybody can suggest a better approach -
> > I'm all for it.
>
> (snip code)
>
>
>
> >> My point is that:
> >> 1/ you shouldn't have to rewrite a decorator function - with basically
> >> the same code - for each possible proxy class / attribute name pair combo
> >> 2/ making the decorator an attribute of the proxy class makes
> >> dependencies clearer (well, IMHO at least).
>
> > agreed on all points
>
> >> I'm still a bit uneasy wrt/ high coupling between A and B, and if I was
> >> to end up with such a design, I'd probably take some times to be sure
> >> it's really ok.
>
> > that is the question that troubles me at this point - thus my original post
> > (read the subject line ;) ).  I like the clarity decorators bring to the
> > code and the fact that it's a solution pretty much "out-of-the-box" without
> > need to create something really-really custom, but I'm worried about tight
> > coupling and somewhat backward logic that they would introduce (the way I
> > envisioned them).
>
> Well... The canonical solution for delegation in Python is using
> __getattr__. Your problem - according to this post and your answer to
> Diez - is that your proxy may have to
> 1/ delegate to more than one object
> 2/ don't necessarily delegate each and any attribute access
>
> I can envision one solution using both __getattr__ and a simple decorator:
>
> def proxy(func):
>     func._proxied = True
>     return func
>
> class A(object):
>      def __init__(self, delegates):
>          self._delegates = delegates
>      def __getattr__(self, name):
>          for d in self.__delegate:
>              func = getattr(d, name)
>              if callable(func) and getattr(func, '_proxied', False):
>                  return func
>          raise AttributeError(
>                'object %s has no attribute '%s' % (self.__class__, name)
>                )
>
> class B(object):
>      def __init__(self):
>          self.val='bval'
>
>      @proxy
>      def bmethod(self,a):
>          print "B::bmethod"
>          print a, self.val
>
>      @proxy
>      def bmethod2(self,a):
>          print "B::bmethod2"
>          print a, self.val
>
> class C(object):
>      def __init__(self):
>          self.val='bval'
>
>      @proxy
>      def cmethod(self,a):
>          print "B::bmethod"
>          print a, self.val
>
>      @proxy
>      def cmethod2(self,a):
>          print "B::bmethod2"
>          print a, self.val
>
> a = A([B(), C()])
>
> # not tested...
>
> This solves most of the coupling problems (B and C still have to make
> clear which methods are to be proxied, but at least they need not know
> which class will be used as proxy), and makes sure only 'allowed' method
> calls are delegated. But I wouldn't call it a perfect solution neither.
> If you do have more than one object having method xxx, only the first
> one will match... And let's not talk about the lookup penalty.
>
> There's a possible variant that avoids the call to __getattr__ (in
> short: attaching delegation instancemethods to A instance in the
> initializer for each proxied method in delegates), but that wont solve
> the problem of potential name clashes.
>
> My 2 cents...

You should write it like this:

class B(object):
     @A.proxy
     def bmethod(self,a):

Making 'proxy' a class method on A.  In case different A instances (do
you have more than one BTW?) proxy different objects, you could make
it a plain old method.

a= A()
class B(object):
     @a.proxy
     def bmethod(self,a):

I recommend this solution so that if you add a method to a B instance
later, 'a' can be notified simply.:

b.meth3= a.proxy( meth3 )

The big problem with that is if 'a' has 'b' in its constructor.  You
can reverse that, since B 'knows' about it proxy object quite a bit
anyway.

What you've said implies that you only have one B instance, or only
one per A instance.  Is this correct?

I agree that __setattr__ is the canonical solution to proxy, but you
have stated that you want each proxied method to be a member in the
proxy class.




More information about the Python-list mailing list