Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pointers to static methods in Python

Why is it that in the following code, using a class variable as a method pointer results in unbound method error, while using an ordinary variable works fine:

class Cmd: 
    cmd = None

    @staticmethod   
    def cmdOne():
        print 'cmd one'

    @staticmethod   
    def cmdTwo():
        print 'cmd two'

def main():
    cmd = Cmd.cmdOne
    cmd() # works fine

    Cmd.cmd = Cmd.cmdOne        
    Cmd.cmd() # unbound error !!

if __name__=="__main__":
    main()

The full error:

TypeError: unbound method cmdOne() must be called with Cmd instance as 
           first argument (got nothing instead)
like image 788
Basel Shishani Avatar asked Apr 26 '13 06:04

Basel Shishani


People also ask

Is it possible to have static methods in Python?

Static methods in Python are extremely similar to python class level methods, the difference being that a static method is bound to a class rather than the objects for that class. This means that a static method can be called without an object for that class.

How do you implement a static method in Python?

We must explicitly tell Python that it is a static method using the @staticmethod decorator or staticmethod() function. Static methods are defined inside a class, and it is pretty similar to defining a regular function. To declare a static method, use this idiom: class C: @staticmethod def f(arg1, arg2, ...): ...

What is the difference between @staticmethod and Classmethod?

@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.

Why pointers are not used in Python?

Python tends to try to abstract away implementation details like memory addresses from its users. Python often focuses on usability instead of speed. As a result, pointers in Python don't really make sense. Not to fear though, Python does, by default, give you some of the benefits of using pointers.


3 Answers

You need to use staticmethod() to convert the function:

Cmd.cmd = staticmethod(Cmd.cmdOne)
like image 50
HYRY Avatar answered Sep 18 '22 18:09

HYRY


You're running into the behavior of "unbound methods" in Python 2.x. Basically, in Python 2.x, when you get an attribute of a class (e.g. in this case Cmd.cmd), and the value is a function, then the class "wraps" the function into a special "unbound method" object, because they assume that attributes of classes that are functions and are not decorated with staticmethod or classmethod are meant to be instance methods (an incorrect assumption in this case). This unbound method expects an argument when called, even though in this case the underlying function does not expect an argument.

This behavior is explained in the language reference:

(in the "Classes" section)

When a class attribute reference (for class C, say) would yield a user-defined function object or [...], it is transformed into an unbound user-defined method object whose im_class attribute is C.

(in the "User-defined methods" section)

When a user-defined method object is created by retrieving a user-defined function object from a class, its im_self attribute is None and the method object is said to be unbound.

[...]

When an unbound user-defined method object is called, the underlying function (im_func) is called, with the restriction that the first argument must be an instance of the proper class (im_class) or of a derived class thereof.

That is what is causing the error you're seeing.

You could explicitly retrieve the underlying function out of the method object and call that (but it's obviously not ideal to need to do this):

Cmd.cmd.im_func()

Note that Python 3.x got rid of unbound methods and your code would run fine on Python 3.x

like image 24
newacct Avatar answered Sep 18 '22 18:09

newacct


I like to view this behaviour from the "bottom up".

A function in Python acts as a "descriptor object". As such, it has a __get__() method.

A read access to a class attribute which has such a __get__() method is "redirected" to this method. A attribute access to the class is executed as attribute.__get__(None, containing_class), while an attribute access to the instance is mapped to attribute.__get__(instance, containing_class).

A function's __get__() method's task is to wrap the function in a method object which wraps away the self parameter - for the case of an attribute access to the instance. This is called a bound method.

On a class attribute access on 2.x, a function's __get__() returns an unbound method wrapper, while, as I learned today, on 3.x, it returns itself. (Note that the __get__() mechanism still exists in 3.x, but a function just returns itself.) That's nearly the same, if you look at how it is called, but an unbound method wrapper additionally checks for the correct type of the self argument.

A staticmethod() call just creates an object whose __get__() call is designed to return the originally given object so that it undoes the described behaviour. That's how HYRY's trick works: the attribute acces undoes the staticmethod() wrapping, the call does it again so that the "new" attribute has the same status as the old one, although in this case, staticmethod() seems to be applied twice (but really isn't).

(BTW: It even works in this weird context:

s = staticmethod(8)
t = s.__get__(None, 2) # gives 8

although 8 is not a function and 2 is not a class.)

In your question, you have two situations:

cmd = Cmd.cmdOne
cmd() # works fine

accesses the class and asks for its cmdOne attribute, a staticmethod() object. This is queried via its __get__() and returns the original function, which is then called. That's why it works fine.

Cmd.cmd = Cmd.cmdOne
Cmd.cmd() # unbound error

does the same, but then assigns this function to Cmd.cmd. The next line is an attribute access - which does, again, the __get__() call to the function itself and thus returns an unbound method, which must be called with a correct self object as first argument.

like image 21
glglgl Avatar answered Sep 21 '22 18:09

glglgl