Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is @staticmethod not preserved across classes, when @classmethod is?

Tags:

Take the following example script:

class A(object):     @classmethod     def one(cls):         print("I am class")      @staticmethod     def two():         print("I am static")   class B(object):     one = A.one     two = A.two   B.one() B.two() 

When I run this script with Python 2.7.11 I get:

I am class Traceback (most recent call last):   File "test.py", line 17, in <module>     B.two() TypeError: unbound method two() must be called with B instance as first argument (got nothing instead) 

It appears that the @classmethod decorator is preserved across the classes, but @staticmethod is not.

Python 3.4 behaves as expected:

I am class I am static 

Why does Python2 not preserve @staticmethod, and is there a workaround?

edit: taking two out of a class (and retaining @staticmethod) seems to work, but this still seems strange to me.

like image 472
Alex Forbes Avatar asked Dec 02 '16 16:12

Alex Forbes


People also ask

What is the difference between @classmethod and @staticmethod decorators?

@staticmethod function is nothing more than a function defined inside a class. It is callable without instantiating the class first. It's definition is immutable via inheritance. @classmethod function also callable without instantiating the class, but its definition follows Sub class, not Parent class, via inheritance.

What is the difference between @staticmethod and @classmethod in Python?

The static method does not take any specific parameter. Class method can access and modify the class state. Static Method cannot access or modify the class state. The class method takes the class as parameter to know about the state of that class.

What does @classmethod do in Python?

In Python, the @classmethod decorator is used to declare a method in the class as a class method that can be called using ClassName. MethodName() . The class method can also be called using an object of the class. The @classmethod is an alternative of the classmethod() function.

What does @staticmethod do in Python?

The @staticmethod is a built-in decorator that defines a static method in the class in Python. A static method doesn't receive any reference argument whether it is called by an instance of a class or by the class itself.


2 Answers

classmethod and staticmethod are descriptors, and neither of them are doing what you expect, not just staticmethod.

When you access A.one, it's creating a bound method on A, then making that an attribute of B, but because it's bound to A, the cls argument will always be A, even if you call B.one (this is the case on both Python 2 and Python 3; it's wrong everywhere).

When you access A.two, it's returning the raw function object (the staticmethod descriptor doesn't need to do anything special aside from preventing binding that would pass self or cls, so it just returns what it wrapped). But that raw function object then gets attached to B as an unbound instance method, because without the staticmethod wrapping, it's just like you'd defined it normally.

The reason the latter works in Python 3 is that Python 3 has no concept of unbound methods. It has functions (which if accessed via an instance of a class become bound methods) and bound methods, where Python 2 has functions, unbound methods and bound methods.

Unbound methods check that they're called with an object of the correct type, thus your error. Plain functions just want the correct number of arguments.

The staticmethod decorator in Python 3 is still returning the raw function object, but in Python 3, that's fine; since it's not a special unbound method object, if you call it on the class itself, it's just like a namespaced function, not a method of any sort. You'd see the problem if you tried to do:

B().two() 

though, because that will make a bound method out of that instance of B and the two function, passing an extra argument (self) that two does not accept. Basically, on Python 3, staticmethod is a convenience to let you call the function on instances without causing binding, but if you only ever call the function by referencing the class itself, it's not needed, because it's just a plain function, not the Python 2 "unbound method".

If you had some reason to perform this copy (normally, I'd suggest inheriting from A, but whatever), and you want to make sure you get the descriptor wrapped version of the function, not whatever the descriptor gives you when accessed on A, you'd bypass the descriptor protocol by directly accessing A's __dict__:

class B(object):     one = A.__dict__['one']     two = A.__dict__['two'] 

By directly copying from the class dictionary, the descriptor protocol magic is never invoked, and you get the staticmethod and classmethod wrapped versions of one and two.

like image 164
ShadowRanger Avatar answered Sep 24 '22 22:09

ShadowRanger


DISCLAIMER: This is not really an answer, but it doesn't fit into a comment format either.

Note that with Python2 @classmethod is NOT correctly preserved across classes either. In the code below, the call to B.one() works as though it was invoked through class A:

$ cat test.py  class A(object):     @classmethod     def one(cls):         print("I am class", cls.__name__)  class A2(A):     pass  class B(object):     one = A.one   A.one() A2.one() B.one()  $ python2 test.py  ('I am class', 'A') ('I am class', 'A2') ('I am class', 'A') 
like image 37
Leon Avatar answered Sep 25 '22 22:09

Leon