Python: Assigning staticmethod to class variable gives error

∥☆過路亽.° 提交于 2019-12-06 03:55:01

Backstory (descriptor protocol)

First, we need to know a little about python descriptors...

For this answer, it should be enough to know the following:

  1. Functions are descriptors.
  2. Binding behavior of methods (i.e. how a method knows what self to pass) is implemented via the function's __get__ method and the built-in descriptor protocol.
  3. When you put a descriptor foo on a class, accessing the descriptor actually calls the .__get__ method. (This is really just a generalization of statement 2)

In other words:

class Foo(object):
    val = some_descriptor

When I do:

result = Foo.val

Python actually does:

Foo.val.__get__(None, Foo)

When I do:

f = Foo()
f.val

python does:

f = Foo()
type(f).val.__get__(f, type(f))

Now the good stuff.

It looks like (on python2.x), staticmethod is implemented such that it's __get__ method returns a regular function. You can see this by printing the type of Klass.method:

print type(Klass.method)  # <type 'function'>

So what we've learned is that the method returned by Klass.method.__get__ is just a regular function.

When you put that regular function onto a class, it's __get__ method returns an instancemethod (which expects a self argument). This isn't surprising ... We do it all the time:

class Foo(object):
    def bar(self):
        print self

Is no different to python than:

def bar(self):
    print self

class Foo(object):
    pass

Foo.bar = bar

except that the first version is a lot easier to read and doesn't clutter your module namespace.

So now we've explained how your staticmethod turned into an instance method. Is there anything we can do about it?

Solution

When you put the method onto the class, designate it as a staticmethod again and it will work out Ok.

class Klass(object):  # inheriting from object is a good idea.
    classVariable = None

    @staticmethod
    def method():
        print("method called")

Klass.classVariable = staticmethod(Klass.method)  # Note extra staticmethod
Klass.method()
Klass.classVariable()

Appendix -- Re-implementation of @staticmethod

If you're a little but curious how you might implement staticmethod to not have this problem -- Here's an example:

class StaticMethod(object):
    def __init__(self, fn):
        self.fn = fn

    def __get__(self, inst, cls):
        return self

    def __call__(self, *args, **kwargs):
        return self.fn(*args, **kwargs)


class Klass(object):
    classVariable = None

    @StaticMethod
    def method():
        print("method called")

Klass.classVariable = Klass.method
Klass.method()
Klass.classVariable()
Klass().method()
Klass().classVariable()

The trick here is that my __get__ doesn't return a function. It returns itself. When you put it on a different class (or the same class), it's __get__ will still just return itself. Since it is returning itself from __get__, it needs to pretend to be a function (so it can be called after "__gotten__") so I implement a custom __call__ method to do the right thing (pass through to the delegate function and return the result).

Please note, I'm not advocating that you use this StaticMethod instead of staticmethod. It'll be less efficient and not as introspectible (and probably confusing for your code readers). This is only for educational purposes.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!