Python中类方法的区别:绑定、未绑定和静态
这段话在问以下两种类方法有什么区别。
是不是说一个是静态的,另一个不是呢?
class Test(object):
def method_one(self):
print "Called method_one"
def method_two():
print "Called method_two"
a_test = Test()
a_test.method_one()
a_test.method_two()
13 个回答
当你调用一个类里的成员时,Python会自动把这个对象的引用作为第一个参数传进去。这里的变量self
其实没什么特别的意思,它只是个编程习惯。你想叫它gargaloo
也可以。不过,调用method_two
时会出现TypeError
错误,因为Python自动试图把一个参数(指向它父对象的引用)传给一个定义时没有参数的方法。
要让它正常工作,你可以在你的类定义里加上这个:
method_two = staticmethod(method_two)
或者你可以使用@staticmethod
这个函数装饰器。
在Python中,方法其实是个非常简单的概念,只要你理解了描述符系统的基础知识。想象一下下面这个类:
class C(object):
def foo(self):
pass
现在我们来看看这个类在命令行中的表现:
>>> C.foo
<unbound method C.foo>
>>> C.__dict__['foo']
<function foo at 0x17d05b0>
你会发现,当你访问这个类的 foo
属性时,得到的是一个未绑定的方法,但在类的存储(字典)里却有一个函数。为什么会这样呢?原因在于你的类的类实现了一个 __getattribute__
方法,它会处理描述符。听起来复杂,但其实并不难。C.foo
在这个特殊情况下大致等同于以下代码:
>>> C.__dict__['foo'].__get__(None, C)
<unbound method C.foo>
这是因为函数有一个 __get__
方法,使它们成为描述符。如果你有一个类的实例,情况几乎是一样的,只不过 None
是类的实例:
>>> c = C()
>>> C.__dict__['foo'].__get__(c, C)
<bound method C.foo of <__main__.C object at 0x17bd4d0>>
那么,Python为什么要这样做呢?因为方法对象会将函数的第一个参数绑定到类的实例上。这就是为什么会有 self 这个参数。有时候你可能不想让你的类把一个函数变成方法,这时候 staticmethod
就派上用场了:
class C(object):
@staticmethod
def foo():
pass
staticmethod
装饰器会包裹你的类,并实现一个虚假的 __get__
方法,这样返回的就是一个普通的函数,而不是方法:
>>> C.__dict__['foo'].__get__(None, C)
<function foo at 0x17d0c30>
希望这样能解释清楚。
在Python中,绑定方法和未绑定方法是有区别的。
简单来说,当你调用一个成员函数(比如 method_one
)时,这个函数是一个绑定方法。
a_test.method_one()
这会被转换成
Test.method_one(a_test)
也就是说,这实际上是调用一个未绑定的方法。因此,如果你调用你自己版本的 method_two
,就会出现 TypeError
错误。
>>> a_test = Test()
>>> a_test.method_two()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: method_two() takes no arguments (1 given)
你可以通过使用装饰器来改变一个方法的行为。
class Test(object):
def method_one(self):
print "Called method_one"
@staticmethod
def method_two():
print "Called method two"
这个装饰器会告诉内置的默认元类 type
(类的类,具体可以参考 这个问题),不要为 method_two
创建绑定方法。
现在,你可以直接在实例上或在类上调用静态方法:
>>> a_test = Test()
>>> a_test.method_one()
Called method_one
>>> a_test.method_two()
Called method_two
>>> Test.method_two()
Called method_two