[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