Reputation: 17
I am trying to create a class which gets given a function, which will then be run from that instance. However, when I tried to use staticmethod
, I discovered that there is a difference between using the decorator and just passing staticmethod
a function.
class WithDec():
def __init__(self):
pass
@staticmethod
def stat(val):
return val + 1
def OuterStat(val):
return val + 1
class WithoutDec():
def __init__(self, stat):
self.stat = staticmethod(stat)
With these two classes, the following occurs.
>>> WithDec().stat(2)
3
>>> WithoutDec(OuterStat).stat(2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'staticmethod' object is not callable
What is going on, and what can I do to stop it.
Upvotes: 1
Views: 466
Reputation: 110166
Yes - In this case, you just have to add the function as an attribute of the instance, it will work as expected, no need for any decorators:
def OuterStat(val):
return val + 1
class WithoutDec():
def __init__(self, stat):
self.stat = stat
The thing is: there is a difference if a function is an attribute of the class or an attribute of the instance. When it is set inside an instance method with self.func = X
, it becomes an instance attribute - Python retrieves it the way it was stored, with no modifications, and it is simply another reference to the original function that can be called.
When a function is stored as a class attibute, instead, the default behavior is that it is used as an instance method: upon retrieving the function from an instance, Python arranges things so that self
will be injected as the first argument to that function. In this case, the decorators @classmethod
and @staticmethod
exist to modify this behavior (injetct the class for classmethod
or make no injection for staticmethod
).
The thing is that staticmethod
does not return a function - it returns a descriptor to be used as a class attribute, so that when the decorated function is retrieved from a class, it works as a plain function.
(Internal detail: all 3 behaviors: instance method, classmethod and staticmethod are implementing by having an appropriate __get__
method on the object that is used as an attribute to the class).
NB: There were some discussions in making "staticmethod" to become itself "callable", and simply call the wrapped function - I just checked it made it into Pythonn 3.10 beta 1. This means that your example code will work as is for Python 3.10 - nonetheless, the staticmethod call there is redundant, as stated in the beggining of this answer, and should not be used.
Upvotes: 0
Reputation: 530882
Static methods still work through the descriptor protocol, meaning that when it is a class attribute, accessing it via an instance still means that the __get__
method will be called to return an object that actually gets called. That is,
WithDec().stat(2)
is equivalent to
w = WithDec()
w.stat(2)
which is equivalent to
WithDec.stat.__get__(w, WithDec)(2)
However, the descriptor protocol is not invoked when the static method is an instance attribute, as is the case with WithoutDec
. In that case
WithoutDec().stat(2)
tries to call the literal staticmethod
instance stat
, not the the function returned by stat.__get__
.
What you wanted was to use staticmethod
to create a class attribute, just not via decorator syntax:
class WithoutDec():
def stat(val):
return val + 1
stat = staticmethod(stat)
You first bind stat
to a regular function (it's not really an instance method until you try to use it as an instance method), then replace the function with a staticmethod
instance wrapping the original function.
Upvotes: 1
Reputation: 1
I found a very inspiring solution on this thread. Indeed your code is not very pythonic, and attributes a static method to an attribute of an instance of your class. The following code works:
class WithoutDec():
stat = None
@staticmethod
def OuterStat(val):
return val + 1
then you call:
my_without_dec = WithoutDec()
my_without_dec.stat = WithotuDec.OuterStat
my_without_dec.stat(2)
later if you want to create a new method, you call:
def new_func(val):
return val+1
WithoutDec.newStat = staticmethod(new_func)
my_without_dec.stat = WithoutDec.newStat
my_without_dec.stat(2)
Upvotes: 0
Reputation: 12203
The problem is that you are trying to use staticmethod()
inside __init__
, which is used to create an instance of the class, instead of at the class level directly, which defines the class, its methods and its static methods.
This code works:
def OuterStat(val):
return val + 1
class WithoutDec():
stat = staticmethod(OuterStat)
>>> WithoutDec.stat(2)
3
Note that trying to create an instance of WithoutDec
with its own, different, version of stat, is contrary to the meaning of a method being static.
Upvotes: 0