Pointers to static methods in Python

后端 未结 3 365
醉梦人生
醉梦人生 2020-12-19 13:07

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:



        
相关标签:
3条回答
  • 2020-12-19 13:50

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

    Cmd.cmd = staticmethod(Cmd.cmdOne)
    
    0 讨论(0)
  • 2020-12-19 14:04

    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.

    0 讨论(0)
  • 2020-12-19 14:06

    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

    0 讨论(0)
提交回复
热议问题