I was reading about how functions become bound methods when being set as class atrributes. I then observed that this is not the case for functions that are wrapped by functools.partial
. What is the explanation for this?
Simple example:
from functools import partial
def func1():
print("foo")
func1_partial = partial(func1)
class A:
f = func1
g = func1_partial
a = A()
a.f() # TypeError: func1() takes 0 positional arguments but 1 was given
a.g() # prints "foo"
I kind of expected them both to behave in the same way.
CodePudding user response:
The trick that allows functions to become bound methods is the __get__
magic method.
To very briefly summarize that page, when you access a field on an instance, say foo.bar
, Python first checks whether bar
exists in foo
's __dict__
(or __slots__
, if it has one). If it does, we return it, no harm done. If not, then we look on type(foo)
. However, when we access the field Foo.bar
on the class Foo
through an instance, something magical happens. When we write foo.bar
, assuming there is no bar
on foo
's __dict__
(resp. __slots__
), then we actually call Foo.bar.__get__(foo, Foo)
. That is, Python calls a magic method asking the object how it would like to be retrieved.
This is how properties are implemented, and it's also how bound methods are implemented. Somewhere deep down (probably written in C), there's a __get__
function on the type function
that binds the method when accessed through an instance.
functools.partial
, despite looking a lot like a function, is not an instance of the type function
. It's just a random class that happens to implement __call__
, and it doesn't implement __get__
. Why doesn't it? Well, they probably just didn't think it was worth it, or it's possible nobody even considered it. Regardless, the "bound method" trick applies to the type called function
, not to all callable objects.
Another useful resource on magic methods, and __get__
in particular: https://rszalski.github.io/magicmethods/#descriptor
CodePudding user response:
The type function
implements the __get__
method:
>>> import types
>>> types.FunctionType.__get__
<slot wrapper '__get__' of 'function' objects>
partial
does not.
>>> from functools import partial
>>> partial.__get__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'functools.partial' has no attribute '__get__'. Did you mean: '__ge__'?
The __get__
method is what makes a.f
evaluate to a value of type method
instead of A.f
. Without __get__
, a.g
is equivalent to A.g
.