[Tutor] Difference(s) betweenPython 3 static methods with and without @staticmethod?

Steven D'Aprano steve at pearwood.info
Mon Aug 7 09:36:35 EDT 2017


On Sun, Aug 06, 2017 at 06:35:10PM -0500, boB Stepp wrote:

> py3: class MyClass:
> ...     def my_method():
> ...             print('This is my_method in MyClass!')
> ...
> py3: class MyOtherClass:
> ...     @staticmethod
> ...     def my_other_method():
> ...             print('This is my_other_method in MyOtherClass!')
> ...
> py3: MyClass.my_method()
> This is my_method in MyClass!
> py3: MyOtherClass.my_other_method()
> This is my_other_method in MyOtherClass!

A small comment: since the methods have different names, there's no 
reason why they couldn't both be in the same class. 

It is hard to see the distinction between an ordinary method and a 
static method from this example. What you are seeing is, by accident, 
the equivalent of just writing:

def my_method():
    print('This is my_method in MyClass!')

outside of the class and calling the bare function. Notice that despite 
the name, my_method is not *actually* usable as a method. It has no 
"self" parameter.


> I see no difference in result, whether I use the @staticmethod decorator or not.

Indeed you would not, because you are calling MyClass.my_method, 
directly from the class object. If you created an instance first, and 
then called the methods, you would see a big difference!

obj = MyClass()
obj.my_method()  # fails with TypeError, as there is no "self" parameter

while the staticmethod would succeed.

So what's going on here?

When you call MyClass.my_method, Python looks up my_method on the class 
object, equivalent to:

    thing = getattr(MyClass, 'my_method')

before calling thing(), which then prints the message.

This is where is gets complex... Python performs some special magic when 
you look up attributes, according to "the descriptor protocol". Don't 
worry about how it works -- think of it as black magic for now. But the 
bottom line is, if the look up returns something like a string or an int 
or a list, no descriptor magic happens, but if it returns a function 
(something you created with "def"), something not far removed from this 
happens:

    # (instance) method descriptor
    if thing is a function:
        if the source was a class # like "MyClass.method":
            # This was different in Python 2.
            return thing, unchanged
        else:  # source was an instance, "MyClass().method"
            wrap thing in a method object that receives "self"
            return the method object

Why does it bother? Because that way Python can automatically fill in 
the instance as "self".

Example: today we write:

    s = "Hello world!"
    t = s.upper()

But without this method magic, or something similar, we would have to 
write:

    s = "Hello world!"
    t = str.upper(s)  # need to manually provide "self"

or even:

    s = "Hello world!"
    t = s.upper(s)

in order for the upper() method to receive the instance s as the "self" 
parameter.

Despite the name, your example "my_method" isn't a method and doesn't 
require "self". If you try to pass one in, you get a TypeError because 
the function doesn't take any arguments at all. But normally methods 
need to know what instance they are working on, in other words, they 
need to know "self".

So when you call MyClass.my_method, calling from the class, Python just 
gives you the raw function object, unchanged. Normally that means you 
would need to provide "self" yourself. This is called an "unbound 
method", it's unbound because it doesn't know what instance to work on.

Example:

    str.upper

is the equivalent of an unbound method. It is incomplete, because it 
doesn't know what string to uppercase. You have to provide one for it to 
work on:

    str.upper("spam")

But if you call directly from an instance, Python knows what string to 
use as "self", and you don't need to do any more:

    "spam".upper()


Unbound methods have their uses. In Python 2, they were actual custom 
method objects, but in Python 3 it was decided that this was pointless 
and instead the bare function is returned instead. But conceptually, the 
base function is an unbound method.

So normal methods convert a function to a method which automatically 
provides "self", but only if called from the instance (self). Otherwise 
they return the function, which needs you to manually provide self.

In your example, by accident (I assume?) you forgot to include "self", 
so the fact that no self was provided didn't stop the function from 
working.

Now, how about class methods? Class methods are just like ordinary 
(instance) methods, except instead of "self", they receive the class 
object as first parameter. There aren't many good examples of class 
methods, and so they aren't common. But here's one:

    dict.fromkeys

Most dict methods (update, pop, popitem, clear, etc.) need to know which 
specific dict to work on. But fromkeys is different: it does not care 
what instance you use. Both of these will work exactly the same:

    {}.fromkeys(['one', 'two', 'three'])
    {100: 'A', 200: 'B', 300: 'C'}.fromkeys(['one', 'two', 'three'])

The instance is so unimportant that fromkeys can be equally called from 
the dict class itself:

    dict.fromkeys(['one', 'two', 'three'])

Instead of the instance, "self", fromkeys receives the class, "cls". In 
this case, it will be dict, but if you subclass:

class MyDict(dict):
    pass

MyDict.fromkeys(['one', 'two', 'three'])

then the fromkeys method sees *your* class, MyDict, instead of dict. To 
make your own functions that behave like fromkeys, you decorate them 
with the `classmethod` built-in:

class MyClass(object):
    @classmethod
    def method(cls, arg):
        ...

When you use classmethod, the behaviour you see is something like:

    # classmethod descriptor
    if thing is a function:
        wrap thing in a classmethod object that receives "cls"
        return the classmethod object


Finally we come to static methods, which use the `staticmethod` 
decorator. Static methods sometimes are confusing, because:

(1) There are very, very few good uses for static methods in Python. If 
you think you need a static method, you probably could just use a 
regular module-level function.

(2) In Java, "static method" refers to something which is more like 
Python's *class methods*, so people with Java experience sometimes use 
the wrong one.

What does staticmethod do? Basically, it's just a protective decorator 
which prevents the default instance method behaviour from happening. It 
does something like:

    # staticmethod descriptor
    if thing is a function:
        return thing, unchanged


> In "Python Pocket Reference, 5th ed." by Mark Lutz, on page 151 under
> the entry "staticmethod(function)" the author states, "... Use the
> @staticmethod functiion decorator in version 2.4 and later ... In
> Python 3.X only, this built-in is not required for simple functions in
> classes called only through class objects (and never through instance
> objects)."  So what I am understanding is that I only need use the
> @staticmethod decorator if I am using Python versions 2.4 through 2.7
> (new-style classes).  Is my understanding correct?

What Mark is saying here is that in Python 2, if you want the static 
method functionality, you have to use the staticmethod decorator:

class MyClass(object):
    @staticmethod
    def func(arg):  # no self, no cls
        ...


But in Python 3, you can leave it out, *provided* you never call

instance = MyClass()
instance.func(x)  # TypeError

but only 

MyClass.func(x)


I don't know why he bothered to mention that. The fact that this works 
at all is an accident, not something to rely on.




-- 
Steve


More information about the Tutor mailing list