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:
You need to use staticmethod()
to convert the function:
Cmd.cmd = staticmethod(Cmd.cmdOne)
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.
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