50

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 need self as a first argument?

wkz
  • 2,203
  • 1
  • 15
  • 21
  • 1
    Why do you think you need a `@staticmethod`? It seems clear that you don't understand what that means. Static methods are not bound to an instance of an object, so they have no `self` argument (and no access to instance variables). – Nick Bastin Jun 20 '11 at 13:56
  • 11
    @Nick: The decorator `ensure_black` doesn't need an access to `self`. It only needs to access `func`. – Sven Marnach Jun 20 '11 at 14:00
  • @Sven: Good point - I was thrown by the face that `def get()` also wasn't using `self`, so was confused as to how `_aux` could be. – Nick Bastin Jun 22 '11 at 03:36
  • Could you have a look at https://stackoverflow.com/questions/51260036/is-my-in-class-decorator-not-pythonic-enough-or-pycharm-not-smart-enough-in-lint/51260639?noredirect=1#comment89499456_51260639 and post how you made it to work without the constructor? – serv-inc Jul 10 '18 at 08:45

4 Answers4

45

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.

serv-inc
  • 35,772
  • 9
  • 166
  • 188
Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • 1
    Ok, 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? – wkz Jun 20 '11 at 14:09
  • 5
    @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 in `ford_black.get`. This expression won't simply return the function `get()` as defined inside the class scope but rather a "bound method wrapper" that makes sure the correct instance is passed as `self` parameter when the function is called -- see the above link and the example. The example circumvents the special treatment by using `A.__dict__["f"]`. – Sven Marnach Jun 20 '11 at 14:14
  • marnach: Thank you! That was the answer I was looking for! – wkz Jun 20 '11 at 14:19
  • @SvenMarnach I was doing exactly what OP was doing, so your answers very helpful, thank you. – haridsv Nov 25 '11 at 01:07
  • Technically, staticmethod objects do not **use** descriptors, they **are** descriptors, assuming the definition of "if any of `__get__`, ... is implemented" stands. – Petri Jul 14 '16 at 06:31
  • I'm facing a scenario where I want the descriptor to be inside the class, because I want to use the class as a mixin :-/ – oulenz Nov 03 '17 at 15:42
  • I came up with the solution to this problem: https://stackoverflow.com/a/59481429/7837628 – usernumber124153 Feb 17 '21 at 05:27
9

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'
jfocht
  • 1,704
  • 1
  • 12
  • 16
4

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)

-1

ensure_black is returning a _aux method that isn't decorated by @staticmethod

You can return a non-static method to a static_method

http://docs.python.org/library/functions.html#staticmethod

Felipe Cruz
  • 940
  • 5
  • 14