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   |   [EMAIL PROTECTED]   |   Brisbane, Australia
---------------------------------------------------------------
            http://boredomandlaziness.skystorm.net
_______________________________________________
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com

Reply via email to