Tal Einat added the comment:

It seems we're looking at Delegators and Percolators from increasingly
different points of view. Let's back up a little.


I see a Delegator object as a transparent proxy to its "delegate". This
means that attribute access is automatically delegated to the delegate,
unless it is explicitly overridden by the Delegator. That's it.

Some use cases for such a transparent proxy are:
* override only specific attributes/methods of an object
* allow replacement of an object which is referenced in several places
without having to update every reference

(Caching is just an implementation detail, whose only purpose is to
facilitate changing a Delegator's delegate.)


As for Percolator, it really "is a" delegator -- it delegates attribute
access to the bottom of the chain, unless it is explicitly overridden.
True, in a "normal" Delegator this overriding can only be done in one
place, and in a Percolator it can also happen in any of the chain's
links. But the concept is identical -- it is a transparent proxy for an
underlying object.

IMO chaining Percolators makes just as much sense as chaining Delegators
-- you're just chaining proxies. How each proxy works internally doesn't
really matter (as long as they work :).


Now, it seems to me that you aren't looking at Delegators and
Peroclators as transparent proxies at all. Specifically, what you wrote
implies that in order to proxy a callable, one should explicitly define
an __call__ method in their Delegator class/instance. But this is
exactly the opposite of the behavior with any other method/attribute,
where I can implicitly have the underlying attribute used by not
defining it in the Delegator. This is Delegator is for!


I'm attaching a Python file which will hopefully show how __call__ is
out of sync with the rest of Delegator's behavior. In its context,
"forwarded" means explicitly defined by a Delegator. "intercepted" means
that except for the interceptor and catcher, the method is not defined
(i.e. by the passers). Please take a moment to run it.


I should note that the situation is similar with other "magic" methods,
e.g. len(). This seems to make Python a bit less dynamic that I would
expect. Aside from implementation considerations such as speed, I'm not
sure I see why this is the way it is, e.g. why dynamically giving a
__call__ attribute to an instance shouldn't make it callable. I'll do
some more searching and reading on this.

Even though, I still think being able to delegate/percolate callables is
important enough to warrant such a change. After all, at the bottom
line, if the underlying object is callable then it will be called, and
if not then an appropriate exception will be raised. Isn't that the
Right Thing?

Added file: http://bugs.python.org/file8637/Delegators3.py

__________________________________
Tracker <[EMAIL PROTECTED]>
<http://bugs.python.org/issue1252>
__________________________________
import traceback
from Delegator import Delegator

class NamedDelegator(Delegator):
    "A bit of infrastructure..."
    def __init__(self, name):
        Delegator.__init__(self)
        self.name = name

    def _forward(self, to):
        return self.name + ' -> ' + to

    def _catch(self):
        return self.name


class Catcher(NamedDelegator):
    "Just catches everything"
    forwarded = intercepted = quietly_forwarded = quietly_intercepted = \
                __call__ = NamedDelegator._catch

class Passer(NamedDelegator):
    "Just forwards everything"
    def forwarded(self):
        return self._forward(self.delegate.forwarded())

    def intercepted(self):
        return self._forward(self.delegate.intercepted())

    # forwarding quietly means letting Delegator take care of it

class Interceptor(NamedDelegator):
    "Forwards or intercepts, according to method names"
    def forwarded(self):
        return self._forward(self.delegate.forwarded())

    quietly_intercepted = NamedDelegator._catch
    intercepted = NamedDelegator._catch
    __call__ = NamedDelegator._catch

class CallableDelegator(Delegator):
    def __call__(self, *args, **kwargs):
        return self.delegate.__call__(*args, **kwargs)

# Set up a chain
bottom = b0 = Catcher("Catcher")
b1 = Passer("Passer 1")
b1.setdelegate(b0)
b2 = Interceptor("Interceptor 2")
b2.setdelegate(b1)
top = b3 = Passer("Passer 3")
b3.setdelegate(b2)

# See what happens

print '"forwarded" is only implemented by the bottom ("Catcher"):'
print 'top.forwarded() =', top.forwarded()
print
print 'but "intercepted" is also implemented by b2 ("Interceptor 2"):'
print 'top.intercepted() =', top.intercepted()
print

print 'this is how it looks without printing the chains:'
print 'top.quietly_forwarded() =', top.quietly_forwarded()
print 'top.quietly_intercepted() =', top.quietly_intercepted()
print

print "now let's try something similar with __call__:"
Callable = CallableDelegator()
Callable.setdelegate(top)
print 'Callable() =', Callable()
NonCallable = Delegator()
NonCallable.setdelegate(top)
try:
    print 'NonCallable() =', NonCallable()
except:
    print
    traceback.print_exc()
print

print 'But this works!'
print 'NonCallable.delegate.__call__() =', NonCallable.delegate.__call__()

_______________________________________________
Python-bugs-list mailing list 
Unsubscribe: 
http://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to