apostofes
apostofes

Reputation: 3723

What does using `__get__` on a function do?

for the function,

def add(a, b):
  return a + b

when I do,

add.__get__(object(), object)
<bound method add of <object object at 0x7f3c7af42b90>>
add.__get__(list(), list)
<bound method add of []>
add.__get__(bool(), bool)
<bound method add of False>

what is happening here?

Upvotes: 2

Views: 523

Answers (2)

apostofes
apostofes

Reputation: 3723

it appears that,

def f():
  pass

when I do,

hasattr(f, '__get__')

then, it returns True

when I do,

class A:
  pass
a = A()
f.__get__(a, A) # I think the second argument is optional here
# so, even, f.__get__(a) would give same result

it returns,

<bound method f of <__main__.A object at 0x7f9c7568dd10>>

which means that our function is similar to something like this,

class B:
  def func_(self):
    pass
b = B()
b.func_
<bound method B.func_ of <__main__.B object at 0x7f9c7563cc50>>

and then when I call the function on top,

f.__get__(a)()

then, it gives an error,

TypeError: f() takes 0 positional arguments but 1 was given

which means, that I am passing the instance here, similar to func_, where, the first argument self refers to the instance of the class B.

so, the function we have on top, becomes a bound method, and if I change it to,

def f(self):
  print(self)

and then run,

f.__get__(a)()

it gives me,

<__main__.A object at 0x7f9c7568dd10>

one more thing to notice here is that we could bind our function to a class also, which would make it a class method,

f.__get__(A)
<bound method func of <class '__main__.A'>>

where,

f.__get__(A)()

gives,

<class '__main__.A'>

which is similar to,

class C:
  @classmethod
  def x(cls):
    print(cls)
C.x
<bound method C.x of <class '__main__.C'>>

or we could keep our function as a static method, without binding it to an instance or a class.

f.__get__(1)
<bound method func of 1>

where,

f.__get__(1)()

gives,

1

here, f would be a function with the first argument equal to 1.

Upvotes: 0

juanpa.arrivillaga
juanpa.arrivillaga

Reputation: 96236

Functions are descriptors and use the descriptor protocol to bind the instance the method is called on to the first argument. This is how "self works". So, if you look at the Descriptor HOWTO it gives you an example of how it would be implemented in Python:

class Function:
    ...

    def __get__(self, obj, objtype=None):
        "Simulate func_descr_get() in Objects/funcobject.c"
        if obj is None:
            return self
        return MethodType(self, obj)

So, here are examples of calling the returned callable with different types of objects:

>>> def frobnicate(first):
...     return first.foo + 1
...
>>> class Foo:
...     foo = 42
...
>>> class Bar:
...     foo = "bar"
...
>>> class Baz:
...     bar = 42
...
>>> frobnicate.__get__(Foo())()
43
>>> frobnicate.__get__(Bar())()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in frobnicate
TypeError: can only concatenate str (not "int") to str
>>> frobnicate.__get__(Baz())()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in frobnicate
AttributeError: 'Baz' object has no attribute 'foo'

And of course, putting the function in some class namespace makes it act like any other method automatically:

>>> Foo.frobnicate = frobnicate
>>> Foo().frobnicate()
43

Note, you can mentally think of MethodType as just something like:

def bind_first(func, obj):
    def bound(*args, **kwargs):
        return func(obj, *args, **kwargs)
    return bound

Although, if you check the link, it shows a closer implementation (in Python, instead of C):

Methods can be created manually with types.MethodType which is roughly equivalent to:

class MethodType:
    "Emulate PyMethod_Type in Objects/classobject.c"

    def __init__(self, func, obj):
        self.__func__ = func
        self.__self__ = obj

    def __call__(self, *args, **kwargs):
        func = self.__func__
        obj = self.__self__
        return func(obj, *args, **kwargs)

Which is basically doing the same thing as the bind_first function I gave as an example... (although, again, it's using a class and it also creates the __func__ and __self__ arguments that bound-methods have).

Upvotes: 3

Related Questions