类方法中定义的类方法和变量

时间:2013-04-17 18:12:47

标签: python class

以下代码:

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。 但是当我打印两个变量时,我得到一个作为实例方法而另一个作为函数。 为什么?

2 个答案:

答案 0 :(得分: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)

您需要exist成为staticmethod,才能使其与班级“独立”:

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

现在,两者属于同一类型:

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

你实际上可以称之为:

>>> Test.exist('foo')
False

答案 1 :(得分:1)

首先,直观的解释:

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

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

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

(这在3.x中略有不同,因为没有未绑定的方法;函数本身就是为类的每个实例创建绑定方法的神奇事物。但是你明确要求2.x。)< / p>

所以:

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

但这种直观的解释并非如此。它总会引导你找到正确的答案,但事情并非如此。

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

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

>>> 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,从头开始构建类等。