I want to assign a static method to a class variable in Python, and below is what my code looks like.
class Klass:
classVariable = None
@staticmethod
def method():
print "method called"
Klass.classVariable = Klass.method
Klass.method()
Klass.classVariable()
This gave me an error at the last line,
TypeError: unbound method method() must be called with Klass instance as first argument (got nothing instead).
But when I change the static method to class method it works. Can anyone give me any idea of why this is the case?
First, we need to know a little about python descriptors...
For this answer, it should be enough to know the following:
self
to pass) is implemented via the function's __get__
method and the built-in descriptor protocol.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))
It looks like (on python2.x), staticmethod
is implemented such that its __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, its __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?
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()
@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), its __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.