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

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


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.


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):    passclass B(object):    one = A.oneA.one()A2.one()B.one()$ python2 test.py ('I am class', 'A')('I am class', 'A2')('I am class', 'A')