Confused with methods

Alex Martelli aleaxit at yahoo.com
Mon Feb 7 09:24:10 EST 2005


Diez B. Roggisch <deetsNOSPAM at web.de> wrote:
   ...
> > If there a good reason that the __get__ of a boundmethod does not
> > create a new boundmethod wrapper over the first boundmethod?
> 
> I already gave you the good reason:

Hmmm, not sure the code below is ``a good reason'' to avoid changing the
__get__ behavior of boundmethods come Python 3.0 time.  Assume all
classes are newstyle (they'll be, in 3.0), not that it matters here (I
think):

> class A:
>    def callback(self, arg):
>        print self, arg
> 
> def callback(arg):
>     print arg
> 
> class DoSomethingWithCallback:
>     def __init__(self, cb):
>         self.cb = cb

Here we're setting an INSTANCE attribute,

>     def run(self):
>         for i in xrange(100):
>              self.cb(i)

...and here we're fetching it back.  So, it doesn't matter if the cb
argument is a descriptor of whatever variant -- it must be callable, and
that's all we ask of it.  IOW, cb.__get__ -- whether it exists at all,
and what it does -- just doesn't *matter*; cb.__call__ is all that does
matter.

> u = DoSomethingWithCallback(A().callback)
> v = DoSomethingWithCallback(callback)
> 
> # would crash if your suggestion worked
> u.run()
> v.run()

I don't think there would be any crash, because the idea is about
changing some __get__ behavior, NOT any __call__ behavior, and no
__get__ is involved in this example.

If strong use cases exist for a boundmethod's __get__ keeping its
current behavior (apart from the obvious backwards compatibility issues
which mean that any change will have to wait for 3.0), I don't think
this can be one.
 
 
> Bound methods aren't a general purpose currying scheme - they exist sorely
> for the OO-style implicit first argument. That they exist at all is a great
> difference to e.g. C++, where you can't pass callbacks around that are
> instance methods. 

This is historically true.  However, this historical fact is not
necessarily a reason for keeping the current lower-power behavior of
boundmethods, when 3.0 allows breaking backwards compatibility.

 
> If you are after currying - look at the cookbook, there are recipes for
> that.

Sure, I wrote some of them;-).  But since we're talking about future
prospects, I think the PEP on partial application is more relevant than
the current pure-Python workarounds.  They _are_ workarounds, btw --
partial application, aka currying, is an important general idea; it can
be implemented in Python, but it's not directly part of it.  The PEP's
idea is to have that concept directly implemented.

Actually, I believe that if you call new.instancemethod you CAN already
get that effect -- let's see...:

>>> import new
>>> curry = new.instancemethod
>>> def f(a, b, c): print a, b, c
... 
>>> x = curry(curry(f, 23, object), 45, object)
>>> x(67)
23 45 67

Yep -- just fine (in __call__ terms).  Not so in __get__ terms:

>>> class X(object): pass
... 
>>> X.zap = x
>>> y = X()
>>> y.zap()
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: f() takes exactly 3 arguments (2 given)


So, for generic currying (in the original sense: prebinding the FIRST
argument of any callable), new.instancemethod is OK (even though you
have to pass a third argument of 'object', no big deal), and you can
call it repeatedly.  We're only missing a descriptor with a __get__ that
does this appropriately when you set such a ``curried'' callable as a
class attribute.

I'm not sure if the best approach is to change boundmethods' own
__get__, or to have another descriptor for the purpose:

>>> class binder(object):
...   def __init__(self, callable): self.callable = callable
...   def __get__(self, obj, cls): return curry(self.callable, obj, cls)
... 
>>> X.zap = binder(x)
>>> y.zap()
23 45 <__main__.X object at 0x402dfa8c>

This ``binder'' approach would have the advantage of allowing ANY
callable whatsoever, e.g.:

>>> X.__divmod__ = lambda self, other: (self, other)
>>> divmod(y, 23)
(<__main__.X object at 0x402dfa8c>, 23)
>>> X.dm = binder(divmod)
>>> y.dm(23)
(<__main__.X object at 0x402dfa8c>, 23)
>>> 

Of course, a custom metaclass could easily wrap 'binder' around any
class attributes which are callable but aren't descriptors, to get the
same effect more transparently.  Hmmmm -- the tradeoffs aren't clear to
me at this time.  That's exactly what the PEP process is for... ensure
that any tradeoffs ARE discussed in depth before any change is made.


Alex



More information about the Python-list mailing list