But I still insist that the behavior is not acceptable, I make another mini example:
class B:
def __call__(self, x):
return x
fn_b = B()
fn_c = lambda x: x
class X:
fn_b = fn_b
fn_c = fn_c
assert fn_b(1) == fn_c(1)
assert X.fn_b(1) == X.fn_c(1)
assert X().fn_b(1) == X().fn_c(1) # ERROR: TypeError: <lambda>() takes 1 positional argument but 2 were given
# true behavior:
# x = X()
# assert x.fn_b() == x
When we define a variable and assign the variable a value, may be any value, I think We should never treat the variable as a class-method MERELY because the variable is a function!
in the above case, fn_c could be imported from other module.
AND: X().fn_b(1) is OK X().fn_c(1) is an ERROR, strange and unacceptable, NOT symmetric
when we x.fn_b, we are just searching a attribute named fn_b, if attribute not found, we would continue our searching from the class of the instance. It is straightforward.
A static method returns the original function when retrieved via a class or instance attribute and that original function has no awareness that it was being used as a static method before. The correct way to do this would be A.t1 = staticmethod(B.t1).
To demonstrate what’s going on, first make the lambda and static/class method versions at module level and then assign all of them to the class.
class A: pass
a_inst = A()
lamb = lambda x: x
slamb = staticmethod(lamb)
clamb = classmethod(lamb)
A.lamb = lamb
A.slamb = slamb
A.clamb = clamb
# This __get__ call is what happens when you access class attributes
assert A.lamb == lamb.__get__(None, A)
assert A.slamb == slamb.__get__(None, A)
assert A.clamb == clamb.__get__(None, A)
# And this __get__ call for instance attributes
assert a_inst.lamb == lamb.__get__(a_inst, A)
assert a_inst.slamb == slamb.__get__(a_inst, A)
assert a_inst.clamb == clamb.__get__(a_inst, A)
So the value you retrieve from the class or instance is not necessarily the original object you assigned if the class of the object has a __get__ method. This is useful for a number of things, for example the descriptor guide contains a demonstration of this to implement a pure Python equivalent to staticmethod.
As I perceive it, the interesting part is that a callable object will not be treated the same way as a real function object - they will be left unbounded.