LastedApple3
LastedApple3

Reputation: 17

Difference between using the decorator and the function with staticmethod

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

Answers (4)

jsbueno
jsbueno

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

chepner
chepner

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

proFineDo
proFineDo

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

joanis
joanis

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

Related Questions