Python decorator as a staticmethod
Solution 1
This is not how staticmethod
is supposed to be used. staticmethod
objects are descriptors that return the wrapped object, so they only work when accessed as classname.staticmethodname
. Example
class A(object):
@staticmethod
def f():
pass
print A.f
print A.__dict__["f"]
prints
<function f at 0x8af45dc>
<staticmethod object at 0x8aa6a94>
Inside the scope of A
, you would always get the latter object, which is not callable.
I'd strongly recommend to move the decorator to the module scope -- it does not seem to belong inside the class. If you want to keep it inside the class, don't make it a staticmethod
, but rather simply del
it at the end of the class body -- it's not meant to be used from outside the class in this case.
Solution 2
Python classes are created at runtime, after evaluating the contents of the class declaration. The class is evaluated by assigned all declared variables and functions to a special dictionary and using that dictionary to call type.__new__
(see customizing class creation).
So,
class A(B):
c = 1
is equivalent to:
A = type.__new__("A", (B,), {"c": 1})
When you annotate a method with @staticmethod, there is some special magic that happens AFTER the class is created with type.__new__
. Inside class declaration scope, the @staticmethod function is just an instance of a staticmethod object, which you can't call. The decorator probably should just be declared above the class definition in the same module OR in a separate "decorate" module (depends on how many decorators you have). In general decorators should be declared outside of a class. One notable exception is the property class (see properties). In your case having the decorator inside a class declaration might make sense if you had something like a color class:
class Color(object):
def ___init__(self, color):
self.color = color
def ensure_same_color(f):
...
black = Color("black")
class TFord(object):
def __init__(self, color):
self.color = color
@black.ensure_same_color
def get():
return 'Here is your shiny new T-Ford'
Solution 3
Solution does exist!
Problem is that Static method that is trying to be used as decorator is in fact staticmethod object and is not callable.
Solution: staticmethod object has method __get__
which takes any argument and returns real method: python documentation Python 3.5 and up:
class StaticMethod(object):
"Emulate PyStaticMethod_Type() in Objects/funcobject.c"
def __init__(self, f):
self.f = f
def __get__(self, obj, objtype=None):
return self.f
Min solution I came with is:
class A():
def __init__(self):
self.n = 2
@staticmethod
def _returnBaseAndResult(func):
from functools import wraps
@wraps(func)
def wrapper(*args, **kwargs):
self = args[0]
response = func(*args, **kwargs)
return self.n, response
return wrapper
@_returnBaseAndResult.__get__('this can be anything')
def square(self):
return self.n**2
if __name__ == '__main__':
a = A()
print(a.square())
Will print (2, 4)
wkz
Updated on July 09, 2022Comments
-
wkz almost 2 years
I'm trying to write a python class which uses a decorator function that needs information of the instance state. This is working as intended, but if I explicitly make the decorator a staticmetod, I get the following error:
Traceback (most recent call last): File "tford.py", line 1, in <module> class TFord(object): File "tford.py", line 14, in TFord @ensure_black TypeError: 'staticmethod' object is not callable
Why?
Here is the code:
class TFord(object): def __init__(self, color): self.color = color @staticmethod def ensure_black(func): def _aux(self, *args, **kwargs): if self.color == 'black': return func(*args, **kwargs) else: return None return _aux @ensure_black def get(): return 'Here is your shiny new T-Ford' if __name__ == '__main__': ford_red = TFord('red') ford_black = TFord('black') print ford_red.get() print ford_black.get()
And if I just remove the line
@staticmethod
, everything works, but I do not understand why. Shouldn't it needself
as a first argument? -
Cat Plus Plus about 13 yearsNo, that has nothing to do with it.
-
wkz about 13 yearsOk, it is just that ensure_black has zero uses outside this class. I'm happy to just remove the
@staticmethod
, I would just like to know why it works. Without that the staticmethod decorator, is ensure_black still a static method? -
Sven Marnach about 13 years@wkz: No, it's just a plain Python function defined inside the scope of the class
TFord
. Functions inside the class scope aren't special in anyway. They will only be treated specially when accessed via an instance like inford_black.get
. This expression won't simply return the functionget()
as defined inside the class scope but rather a "bound method wrapper" that makes sure the correct instance is passed asself
parameter when the function is called -- see the above link and the example. The example circumvents the special treatment by usingA.__dict__["f"]
. -
Felipe Cruz about 13 yearsof course it has.. if he is using @staticmethod self is not available in his method context.. (although his code do not work because of that)
-
wkz about 13 yearsmarnach: Thank you! That was the answer I was looking for!
-
haridsv over 12 years@SvenMarnach I was doing exactly what OP was doing, so your answers very helpful, thank you.
-
Petri almost 8 yearsTechnically, staticmethod objects do not use descriptors, they are descriptors, assuming the definition of "if any of
__get__
, ... is implemented" stands. -
oulenz over 6 yearsI'm facing a scenario where I want the descriptor to be inside the class, because I want to use the class as a mixin :-/
-
usernumber124153 over 3 yearsI came up with the solution to this problem: stackoverflow.com/a/59481429/7837628