classification
Title: Can't use _functools.partial() created function as method
Type: enhancement Stage:
Components: Extension Modules Versions: Python 3.4, Python 3.3, Python 2.7
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: Alexander.Belopolsky, Christophe Simonis, anacrolix, belopolsky, eckhardt, ironfroggy, jackdied, jcea, r.david.murray, rhettinger, ssadler
Priority: normal Keywords: patch

Created on 2008-11-16 06:32 by ssadler, last changed 2013-02-25 15:02 by r.david.murray.

Files
File name Uploaded Description Edit
partialbug.py ssadler, 2008-11-16 06:32 Demonstration of bug
issue4331.patch Christophe Simonis, 2010-01-09 20:59 Patch - Add descriptor to partial object + tests
functools.partial-descrget.patch anacrolix, 2012-03-24 18:23 review
Messages (16)
msg75928 - (view) Author: scott sadler (ssadler) Date: 2008-11-16 06:32
Calling a function created by _functools.partial as a method raises an
exception:

"TypeError: method_new() takes exactly n non-keyword arguments (0 given)"

Where method_new is the function passed into partial() and n is the
number of arguments it expects.

This does not happen when using a python version of partial().

Strangely, in the circumstance that I originally encountered the bug,
there was one instance that I was doing this and it _DID WORK_. The
function being passed into partial() was the same as in the place where
it was failing. The only significant difference that I could see was
that the input function to partial() was being imported, rather than
being defined in the same namespace as it was used I was unable to
reproduce it in my test case (attatched).

Tested on 2.6 and 2.5.2
msg75942 - (view) Author: scott sadler (ssadler) Date: 2008-11-16 20:24
A short update, I believe that the reason that it was working in one
instance was because of some abstractions by a base class (Django model,
get_absolute_url).
msg75945 - (view) Author: Calvin Spealman (ironfroggy) Date: 2008-11-16 22:44
I don't think this is any kind of bug, it is simply a product of only 
function objects being decorated automatically as methods. Your python 
version works because it is, in fact, a function. _functools.partial 
objects are not functions, but simply callable objects.
msg75946 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2008-11-16 23:03
Reclassifying as a feature request.
A descriptor could be added to partial()
so that it too would have automatic
method binding just like pure python functions.
msg97470 - (view) Author: Christophe Simonis (Christophe Simonis) Date: 2010-01-09 20:59
I followed the advice of Raymond and implement a descriptor on partial.
msg98490 - (view) Author: Alexander Belopolsky (Alexander.Belopolsky) Date: 2010-01-29 00:25
Christophe,

It looks like your patch goes out of its way to avoid creating nested partials.  This is a worthwhile goal and I think it should be done in partial_new so that partial(partial(f, x), y) returns partial(f, x, y).

If fact, I was surprised to learn that current partial implementation does not behave this way:

>>> partial(partial(f, 1), 2).func
<functools.partial object at 0x100435af8>

Does anyone know the reason for the current behavior?  It is possible that I am missing some subtlety related to keyword arguments.
msg98675 - (view) Author: Alexander Belopolsky (Alexander.Belopolsky) Date: 2010-02-01 19:09
Please see issue7830 for a related patch.
msg99776 - (view) Author: Jack Diederich (jackdied) * (Python committer) Date: 2010-02-22 16:33
I'm having some trouble wrapping my head around this one.  It isn't obvious to me that
my_method(*args):
  print(args)
class A():
  meth = partial(my_method, 'argA')
ob = A()
ob.meth('argB')

should print (<A object at 0x1234>, 'argA', 'argB') and not
('argA', <A object at 0x1234>, 'argB')

The patch seems to prefer the first form but if you are using a partial shouldn't you expect 'argA' to always be the first argument to the partial-ized function?
msg99813 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2010-02-22 19:11
I would expect the second and would view the first as a bug.
msg99956 - (view) Author: Jack Diederich (jackdied) * (Python committer) Date: 2010-02-23 21:35
We talked about it at sprints and the semantics are ambiguous and there are alternatives.

Ambiguous:
  def show_funcs(*args): print(args)
  class A():
    run = partial(1)
  ob = A()
  ob.run(2,3)
Should this print (self, 1, 2, 3) or (1, self, 2, 3)?  And what about
  partial(ob.run, 2)(3)

Alternatives: partial is a convenience function not an optimization (it doesn't offer a speedup.  So you can write a lambda or named function that has the exact semantics you want without suffering a speed penalty.

So unless there are a lot of good use cases with obvious behavior, we should refuse the temptation to guess and leave partial as-is.
msg99957 - (view) Author: Jack Diederich (jackdied) * (Python committer) Date: 2010-02-23 21:37
correction:
  run = partial(1)
should have been
  run = partial(show_funcs, 1)
msg156712 - (view) Author: Matt Joiner (anacrolix) Date: 2012-03-24 18:23
I've attached a patch that implements the descriptor protocol for functools.partial with minimum changes.
msg181582 - (view) Author: Ulrich Eckhardt (eckhardt) Date: 2013-02-07 08:36
Just for the record, the behaviour is documented, unfortunately in the very last line of the functools documentation: "Also, partial objects defined in classes behave like static methods and do not transform into bound methods during instance attribute look-up."

Concerning how exactly they should behave during that lookup, I'd use the least surprising variant, namely that they are not treated differently from other functions: The first parameter is implicitly "self".
msg181658 - (view) Author: Matt Joiner (anacrolix) Date: 2013-02-08 04:02
What's preventing this from being committed and closed?
msg182943 - (view) Author: Ulrich Eckhardt (eckhardt) Date: 2013-02-25 14:19
There is at least one thing that is missing in the patch, it lacks the necessary tests. The partialbug.py demonstrates the issue, it could be used as a base. However, even then, there is still one thing that is problematic: The fact that partial() returns something that behaves like a static method is documented and changing that is not backward compatible.

I still think that something like this should become part of Python though. Jack Diederich argues that you can use lambda to achieve the same, but that is not always true. If you want to bind an argument to the current value of a variable instead of a constant, lambda fails. You need the closure created by a function call to bind those variables inside a local function. Having a dedicated function for that is IMHO preferable to people copying the Python-only equivalent of partial() to achieve the same effect or even inventing their own.
msg182946 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2013-02-25 15:02
See also issue 11470.
History
Date User Action Args
2013-02-25 15:02:11r.david.murraysetmessages: + msg182946
2013-02-25 14:19:14eckhardtsetmessages: + msg182943
2013-02-08 09:20:10Ramchandra Aptesetversions: + Python 3.3, Python 3.4, - Python 3.0
2013-02-08 04:02:49anacrolixsetmessages: + msg181658
2013-02-07 08:36:05eckhardtsetnosy: + eckhardt
messages: + msg181582
2012-03-24 18:23:40anacrolixsetfiles: + functools.partial-descrget.patch

messages: + msg156712
2012-03-24 16:14:44anacrolixsetnosy: + anacrolix
2011-12-11 01:28:07jceasetnosy: + jcea
2010-02-23 21:37:15jackdiedsetmessages: + msg99957
2010-02-23 21:35:44jackdiedsetmessages: + msg99956
2010-02-22 19:11:42r.david.murraysetnosy: + r.david.murray
messages: + msg99813
2010-02-22 16:33:51jackdiedsetmessages: + msg99776
2010-02-18 21:09:22jackdiedsetnosy: + jackdied
2010-02-01 19:09:23Alexander.Belopolskysetmessages: + msg98675
2010-01-29 00:25:25Alexander.Belopolskysetnosy: + Alexander.Belopolsky
messages: + msg98490
2010-01-09 20:59:31Christophe Simonissetfiles: + issue4331.patch
keywords: + patch
messages: + msg97470
2009-03-05 20:23:48Christophe Simonissetnosy: + Christophe Simonis
2008-12-03 05:03:56belopolskysetnosy: + belopolsky
2008-11-16 23:03:10rhettingersettype: behavior -> enhancement
messages: + msg75946
nosy: + rhettinger
versions: + Python 3.0, Python 2.7, - Python 2.6, Python 2.5
2008-11-16 22:44:28ironfroggysetnosy: + ironfroggy
messages: + msg75945
2008-11-16 20:24:31ssadlersetmessages: + msg75942
2008-11-16 06:32:58ssadlercreate