5

为什么在下面的代码中,使用类变量作为方法指针会导致未绑定方法错误,而使用普通变量可以正常工作:

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()

完整的错误:

TypeError: unbound method cmdOne() must be called with Cmd instance as 
           first argument (got nothing instead)
4

3 回答 3

3

您需要使用staticmethod()转换功能:

Cmd.cmd = staticmethod(Cmd.cmdOne)
于 2013-04-26T06:09:20.607 回答
3

您在 Python 2.x 中遇到了“未绑定方法”的行为。基本上,在 Python 2.x 中,当您获得一个类的属性(例如,在这种情况下Cmd.cmd),并且值是一个函数时,该类将函数“包装”到一个特殊的“未绑定方法”对象中,因为它们假设类的属性是函数并且没有用实例方法装饰staticmethodclassmethod意味着是实例方法(在这种情况下是不正确的假设)。这个未绑定的方法在调用时需要一个参数,即使在这种情况下底层函数不需要一个参数。

语言参考中解释了此行为:

(在“类”部分)

当类属性引用(例如,对于 C 类)会产生用户定义的函数对象或 [...] 时,它会转换为 im_class 属性为 C 的未绑定的用户定义方法对象。

(在“用户定义的方法”部分)

当通过从类中检索用户定义的函数对象来创建用户定义的方法对象时,其 im_self 属性为 None 并且该方法对象被称为未绑定。

[...]

当调用未绑定的用户定义方法对象时,将调用底层函数 (im_func),但第一个参数必须是正确类 (im_class) 或其派生类的实例。

这就是导致您看到的错误的原因。

您可以从方法对象中显式检索底层函数并调用它(但显然需要这样做并不理想):

Cmd.cmd.im_func()

请注意,Python 3.x摆脱了未绑定的方法,您的代码将在 Python 3.x 上运行良好

于 2013-04-26T20:03:02.833 回答
2

我喜欢从“自下而上”的角度看待这种行为。

Python 中的函数充当“描述符对象”。因此,它有一个__get__()方法。

对具有这种方法的类属性的读取访问__get__()被“重定向”到该方法。对类的属性访问被执行为attribute.__get__(None, containing_class),而对实例的属性访问被映射到attribute.__get__(instance, containing_class)

函数__get__()方法的任务是将函数包装在一个方法对象中,该方法对象包装了self参数 - 对于属性访问实例的情况。这称为绑定方法。

在 2.x 上的类属性访问中,函数__get__()返回一个未绑定的方法包装器,而正如我今天了解到的,在 3.x 上,它返回自身。(请注意,该__get__()机制在 3.x 中仍然存在,但函数只是返回自身。)如果您查看它的调用方式,这几乎是相同的,但未绑定的方法包装器还会检查self参数的正确类型。

staticmethod()调用只是创建一个对象,其调用__get__()旨在返回最初给定的对象,以便它撤消所描述的行为。这就是HYRY 的技巧的工作原理:属性访问取消staticmethod()包装,调用再次执行此操作,以便“新”属性具有与旧属性相同的状态,尽管在这种情况下,staticmethod()似乎被应用了两次(但实际上不是)。

(顺便说一句:它甚至可以在这种奇怪的情况下工作:

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

虽然8不是函数,2也不是类。)

在您的问题中,您有两种情况:

cmd = Cmd.cmdOne
cmd() # works fine

访问该类并要求其cmdOne属性,一个staticmethod()对象。这是通过其查询__get__()并返回原始函数,然后调用该函数。这就是它工作正常的原因。

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

做同样的事情,但随后将此函数分配给Cmd.cmd. 下一行是属性访问——它再次__get__()调用函数本身,因此返回一个未绑定的方法,必须使用正确的self对象作为第一个参数来调用该方法。

于 2013-04-27T05:43:40.487 回答