[Python-Dev] PEP 309 enhancements

Nick Coghlan ncoghlan at iinet.net.au
Sat Feb 26 07:50:06 CET 2005


Moving a discussion from the PEP309 SF tracker (Patch #941881) to here, since 
it's gone beyond the initial PEP 309 concept (and the SF tracker is a lousy 
place to have a design discussion, anyway).

The discussion started when Steven Bethard pointed out that partial objects 
can't be used as instance methods (using new.instancemethod means that the 
automatically supplied 'self' argument ends up in the wrong place, after the 
originally supplied arguments).

This problem is mentioned only indirectly in the PEP (pointing out that a 
version which prepended later arguments, rather than appending them might be 
useful). Such a class wouldn't solve the problem anyway, as it is only the 
*first* argument we want to give special treatment.

Keyword arguments won't help us, since the 'self' argument is always positional.

The initial suggestion was to provide a __get__ method on partial objects, which 
forces the insertion of the reference to self at the beginning of the argument 
list instead of at the end:

     def __get__(self, obj, type=None):
         if obj is None:
             return self
         return partial(self.fn, obj, *self.args, **self.kw)

However, this breaks down for nested partial functions - the call to the nested 
partial again moves the 'self' argument to after the originally supplied 
argument list. This can be addressed by automatically 'unfolding' nested 
partials (which should also give a speed benefit when supplying arguments 
piecemeal, since building incrementally or all at once will get you to the same 
place):

     def __init__(*args, **kw):
         self = args[0]
         try:
             func = args[1]
         except IndexError:
             raise TypeError("Expected at least 2 arguments, got %s" % len(args))
         if isinstance(func, partial):
             self.fn = func.fn
             self.args = func.args + args[2:]
             d = func.kw.copy()
             d.update(kw)
             self.kw = d
         else:
             self.fn, self.args, self.kw = (func, args[2:], kw)

At this point, the one thing you can't do is use a partial function as a *class* 
method, as the classmethod implementation doesn't give descriptors any special 
treatment.

So, instead of the above, I propose the inclusion of a callable 'partialmethod' 
descriptor in the functional module that takes the first positional argument 
supplied at call time and prepends it in the actual function call (this still 
requires automatic 'unfolding'in order to work correctly with nested partial 
functions):

class partialmethod(partial):
     def __call__(self, *args, **kw):
         if kw and self.kw:
             d = self.kw.copy()
             d.update(kw)
         else:
             d = kw or self.kw
         if args:
             first = args[:1]
             rest = args[1:]
         else:
             first = rest = ()
         return self.fn(*(first + self.args + rest), **d)

     def __get__(self, obj, type=None):
         if obj is None:
             return self
         return partial(self.fn, obj, *self.args, **self.kw)

Using a function that simply prints its arguments:

Py> class C:
...   a = functional.partialmethod(f, 'a')
...   b = classmethod(functional.partialmethod(f, 'b'))
...   c = staticmethod(functional.partial(f, 'c'))
...   d = functional.partial(functional.partialmethod(f, 'd', 1), 2)
...
Py> C.e = new.instancemethod(functional.partialmethod(f, 'e'), None, C)
Py> C().a(0)
((<__main__.C instance at 0x00A95710>, 'a'), {}, 0)
Py> C().b(0)
(<class __main__.C at 0x00A93FC0>, 'b', 0)
Py> C().c(0)
('c', 0)
Py> C().d(0)
('d', 1, 2, 0)
Py> C().e(0)
(<__main__.C instance at 0x00A95710>, 'e', 0)

Notice that you *don't* want to use partialmethod when creating a static method.

Cheers,
Nick.

-- 
Nick Coghlan   |   ncoghlan at email.com   |   Brisbane, Australia
---------------------------------------------------------------
             http://boredomandlaziness.skystorm.net


More information about the Python-Dev mailing list