2

对于以下代码:

import os.path

class Test:

   @classmethod
   def foo(cls):
       cls.exist = os.path.exists
       print type(cls.exist)
       print type(os.path.exists)

def main():
   Test.foo()

我得到的输出为:

<type 'instancemethod'>
<type 'function'>

我只是将函数 os.path.exist 分配给类变量 cls.exist。但是当我打印两个变量时,我得到一个作为实例方法,另一个作为函数。为什么 ?

4

2 回答 2

1

一、直观解释:

当您将一个函数分配给一个类时,它会被转换为一个未绑定的方法。

未绑定方法是一种神奇的东西,它为类的每个实例创建一个绑定方法。

绑定方法是一种神奇的东西,self它绑定了特殊的参数。

(这在 3.x 中略有不同,因为没有未绑定的方法;函数本身就是为类的每个实例创建绑定方法的神奇事物。但您显然是在询问 2.x。)

所以:

>>> class C(object): pass
>>> def foo(self): print(self)
>>> C.foo = foo
>>> c = C()

现在你可以调用c.foo()了,self参数会自动填充为c。但是你不能在C.foo()没有参数的情况下调用,就像你不能foo()在没有参数的情况下调用一样。

>>> c.foo()
<__main__.C object at 0x108df7e50>
>>> C.foo()
TypeError: unbound method foo() must be called with C instance as first argument (got nothing instead)
>>> foo()
TypeError: foo() takes exactly 1 argument (0 given)

当然,没有什么能阻止你打电话C.foo()foo()使用明确的self论据。你通常不会这样做,但它工作正常:

>>> C.foo(1)
1
>>> foo(1)
1

如果您查看函数、未绑定方法和绑定方法,它们很容易区分:

>>> foo, C.foo, c.foo
(<function __main__.foo>, <unbound method C.foo>, <bound method C.foo of <__main__.C object at 0x108df7e50>>)
>>> type(foo), type(C.foo), type(c.foo)
(function, instancemethod, instancemethod)

如果您只是想要一种解决方法,您可以使用staticmethod. 这需要一个函数并创建一个东西,当它分配给一个类时,它就像一个函数而不是一个未绑定的方法:

>>> bar = staticmethod(foo)
>>> C.bar = staticmethod(foo)
>>> c = C()
>>> bar, C.bar, c.bar
(<staticmethod at 0x109e990f8>, <function __main__.foo>, <function __main__.foo>)

现在,您可以在类或实例上将其作为常规函数调用,而无需使用魔法self方法:

>>> C.bar(1)
1
>>> c.bar(1)
1
>>> bar(1)
TypeError: 'staticmethod' object is not callable

但这种直观的解释并不是真的。它总是会引导你找到正确的答案,但这并不是事情的真正运作方式。

要真正理解这一点,您需要了解描述符的工作原理。

当您将函数作为属性分配给类时,不会发生任何神奇的事情。类字典中存储的只是普通的旧函数:

>>> C.__dict__['foo']
<function __main__.foo>

当你创建一个实例时,它不会在实例字典中放任何东西,因为属性查找会自动回退到类字典:

>>> c.__dict__['foo']
KeyError: 'foo'

真正的魔法发生在查找时。每当您查找任何属性(方法或其他)时,解释器不仅会从字典中返回对象,它还会调用该对象的__get__方法,传递实例(或者None,如果在类对象上调用)和类。

普通函数具有__get__根据需要生成绑定或未绑定方法的方法:

>>> C.__dict__['foo'].__get__(None, C)
<unbound method C.foo>
>>> C.__dict__['foo'].__get__(None, C) == C.foo
True
>>> C.__dict__['foo'].__get__(c, C)
<bound method C.foo of <__main__.C object at 0x108df7e50>>
>>> C.__dict__['foo'].__get__(c, C) == c.foo
True

您可以手动执行相同的操作并获得相同的结果:

>>> types.MethodType(foo, None, C) == C.foo
True
>>> types.MethodType(foo, c, C) == c.foo
True

现在你大概可以猜到是什么staticmethod样子了:它有一个__get__方法只返回原始函数,不管你传递什么。

处理可设置的数据属性等有点复杂,但实际上,这几乎就是全部,如果你理解了这一点,你就几乎理解了 Python 中的所有魔力。您可以自己构建staticmethodclassmethodproperty,从头开始构建类等。

于 2013-04-17T19:19:01.487 回答
1

您可能将其分配为类变量,但它绑定到类:

Traceback (most recent call last):
  File "<stdin>", line 12, in <module>
TypeError: unbound method exists() must be called with Test instance as first argument (got str instance instead)

您需要制作existastaticmethod以使其“独立于”类:

cls.exist = staticmethod(os.path.exists)

现在,两者都是同一类型:

<type 'function'>
<type 'function'>

你实际上可以称之为:

>>> Test.exist('foo')
False
于 2013-04-17T18:24:01.607 回答