Why are python instance methods callable, but static methods and class methods not callable?
I did the following:
class Test():
class_var = 42
@classmethod
def class_method(cls):
pass
@staticmethod
def static_method():
pass
def instance_method(self):
pass
for attr, val in vars(Test).items():
if not attr.startswith("__"):
print (attr, "is %s callable" % ("" if callable(val) else "NOT"))
The result is:
static_method is NOT callable
instance_method is callable
class_method is NOT callable
class_var is NOT callable
Technically this may be because instance method object might have a particular attribute (not) set in a particular way (possibly __call__
). Why such asymmetry, or what purpose does it serve?
I came across this while learning python inspection tools.
Additional remarks from comments:
The SO answer linked in the comments says that the static/class methods are descriptors , which are not callable. Now I am curious, why are descriptors made not callable, since descriptors are class with particular attributes (one of __get__
, __set__
, __del___
) defined.
Unlike instance methods, static methods aren't bound to an object. In other words, static methods cannot access and modify an object state. In addition, Python doesn't implicitly pass the cls parameter (or the self parameter) to static methods. Therefore, static methods cannot access and modify the class's state.
Classes are callables Functions are the most obvious callable in Python. Functions can be “called” in every programming language. A class being callable is a bit more unique though. In JavaScript the class instantiation syntax (the way we create an “instance” of a class) involves the new keyword.
Class method can access and modify the class state. Static Method cannot access or modify the class state. The class method takes the class as parameter to know about the state of that class. Static methods do not know about class state.
The “int object is not callable” error occurs when you declare a variable and name it with a built-in function name such as int() , sum() , max() , and others. The error also occurs when you don't specify an arithmetic operator while performing a mathematical operation.
Why are descriptors not callable? Basically because they don't need to be. Not every descriptor represents a callable either.
As you correctly note, the descriptor protocol consists of __get__
, __set__
and __del__
. Note no __call__
, that's the technical reason why it's not callable. The actual callable is the return value of your static_method.__get__(...)
.
As for the philosophical reason, let's look at the class. The contents of the __dict__
, or in your case results of vars()
, are basically locals()
of the class
block. If you def
ine a function, it gets dumped as a plain function. If you use a decorator, such as @staticmethod
, it's equivalent to something like:
def _this_is_not_stored_anywhere():
pass
static_method = staticmethod(_this_is_not_stored_anywhere)
I.e., static_method
is assigned a return value of the staticmethod()
function.
Now, function objects actually implement the descriptor protocol - every function has a __get__
method on it. This is where the special self
and the bound-method behavior comes from. See:
def xyz(what):
print(what)
repr(xyz) # '<function xyz at 0x7f8f924bdea0>'
repr(xyz.__get__("hello")) # "<bound method str.xyz of 'hello'>"
xyz.__get__("hello")() # "hello"
Because of how the class calls __get__
, your test.instance_method
binds to the instance and gets it pre-filled as it first argument.
But the whole point of @classmethod
and @staticmethod
is that they do something special to avoid the default "bound method" behavior! So they can't return a plain function. Instead they return a descriptor object with a custom __get__
implementation.
Of course, you could put a __call__
method on this descriptor object, but why? It's code that you don't need in practice; you can almost never touch the descriptor object itself. If you do (in code similar to yours), you still need special handling for descriptors, because a general descriptor doesn't have to be(have like a) callable - properties are descriptors too. So you don't want __call__
in the descriptor protocol. So if a third party "forgets" to implement __call__
on something you consider a "callable", your code will miss it.
Also, the object is a descriptor, not a function. Putting a __call__
method on it would be masking its true nature :) I mean, it's not wrong per se, it's just ... something that you should never need for anything.
BTW, in case of classmethod/staticmethod, you can get back the original function from their __func__
attribute.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With