Home > Enterprise >  Python: Why do functools.partial functions not become bound methods when set as class attributes?
Python: Why do functools.partial functions not become bound methods when set as class attributes?

Time:11-04

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.

  • Related