Pointers to static methods in Python

风流意气都作罢 提交于 2019-11-29 11:03:15
glglgl

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 need to use staticmethod() to convert the function:

Cmd.cmd = staticmethod(Cmd.cmdOne)

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

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