25

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.

martineau
  • 119,623
  • 25
  • 170
  • 301
Alex Forbes
  • 3,039
  • 2
  • 19
  • 22

2 Answers2

17

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.

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • Many thanks for the informative answer! The use case is a test suite: https://github.com/al4/python-checkrunner/blob/master/tests/test_checkrunner.py I wanted to reuse the functions and have them grouped in a class to avoid PyCharm complaining about applying decorators outside of a class. The best approach, I think, is to manually call staticmethod with the "sub" class. – Alex Forbes Dec 02 '16 at 17:46
  • 1
    @AlexForbes: In that case, I'd implement a class of the common functions and use it as a mixin in the classes that need them. `class CommonTests(object): ... define one, two ...`, then mix it in via multiple inheritance: `class A(unittests.TestCase, CommonTests): ... A specific tests ...`, then `class B(unittests.TestCase, CommonTests): ... B specific tests ...`. Now all the methods of CommonTests are inherited automatically in `A` and `B` without needing to assign them by name individually. The mixin class doesn't inherit from `TestCase` so it won't be tested on its own. – ShadowRanger Dec 02 '16 at 18:03
5

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')
Leon
  • 31,443
  • 4
  • 72
  • 97