Consider the following snippet.
import types
def deff(s):
print(f"deff called, {s=}")
lam = lambda s: print(f"lam called, {s=}")
class Clbl:
def __call__(s):
print(f"__call__ called, {s=}")
clbl = Clbl()
print(type(deff) == types.FunctionType) # True
print(type(lam) == types.LambdaType) # True
print(type(clbl) == Clbl) # True
class A:
deff = deff
lam = lam
clbl = clbl
a = A()
a.deff() # deff called, s=<__main__.A object at 0x7f8c242d4490>
a.lam() # lam called, s=<__main__.A object at 0x7f8c242d4490>
a.clbl() # __call__ called, s=<__main__.Clbl object at 0x7f8c24146730>
print(a.deff is deff) # False
print(a.lam is lam) # False
print(a.clbl is clbl) # True
print(type(a.deff) == types.FunctionType) # False
print(type(a.lam) == types.LambdaType) # False
print(type(a.clbl) == Clbl) # True
print(type(a.deff) == types.MethodType) # True
print(type(a.lam) == types.MethodType) # True
Why are a.deff
and a.lam
methods but a.clbl
not a method? Why do the expression a.deff()
and a.lam()
pass a
as the first argument to the corresponding functions while a.clbl()
doesn’t?
>Solution :
TL;DR Clbl
doesn’t implement the descriptor protocol; types.FunctionType
does.
All three attempts to access attributes on a
fail to find instance attributes by those names, and so the lookup proceeds to the class. All three lookups succeed on class attributes, at which point it is time to see which, if any of them, define a __get__
attribute.
a.clbl
does not, so you get back the actual Clbl
instance bound to the class attribute.
a.deff
and a.lam
do (there is no significant different in between the two; types.LambdaType
itself is just an alias for types.FunctionType
, because lambda expressions and def
statements both create values of the same type). types.FunctionType.__get__
is what returns a callable method
object, which his what manages the implicit passing of an invoking instance to the underlying function.
In short, a.deff()
is equivalent to
A.__dict__['deff'].__get__(a, A)()
The method
object does three things:
- Maintains a reference
__self__
to the instancea
- Maintains a reference
__func__
to the functionA.deff
- Implements a
__call__
method that callsself.__func__
onself.__self__
and any other arguments passed to themethod
instance.