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()
在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
(一个类的类,比较这个问题 ),不创buildmethod_two
绑定方法。
现在,您可以直接在实例或类上调用静态方法:
>>> a_test = Test() >>> a_test.method_one() Called method_one >>> a_test.method_two() Called method_two >>> Test.method_two() Called method_two
一旦理解了描述符系统的基础知识,Python中的方法是非常非常简单的事情。 想象一下下面的课:
class C(object): def foo(self): pass
现在让我们看一下shell中的那个类:
>>> C.foo <unbound method C.foo> >>> C.__dict__['foo'] <function foo at 0x17d05b0>
正如你可以看到,如果你访问类的foo
属性,你会得到一个未绑定的方法,但是在类存储(dict)中有一个函数。 为什么? 原因是你的类的类实现了parsing描述符的__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做到这一点? 因为方法对象将函数的第一个参数绑定到类的实例。 这就是自我来自的地方。 现在有时候你不希望你的课程使用一种方法,这就是staticmethod
方法的作用:
class C(object): @staticmethod def foo(): pass
staticmethod
装饰器包装你的类,并实现一个虚拟的__get__
返回包装函数作为函数,而不是一个方法:
>>> C.__dict__['foo'].__get__(None, C) <function foo at 0x17d0c30>
希望解释它。
当你调用一个类成员时,Python会自动使用对该对象的引用作为第一个参数。 variablesself
实际上没有任何意义,它只是一个编码惯例。 如果你想的话,你可以把它gargaloo
。 也就是说,调用method_two
会引发TypeError
,因为Python会自动将一个参数(对其父对象的引用)传递给一个被定义为没有参数的方法。
要真正实现它,你可以将它附加到你的类定义:
method_two = staticmethod(method_two)
或者你可以使用@staticmethod
函数装饰器 。
>>> class Class(object): ... def __init__(self): ... self.i = 0 ... def instance_method(self): ... self.i += 1 ... print self.i ... c = 0 ... @classmethod ... def class_method(cls): ... cls.c += 1 ... print cls.c ... @staticmethod ... def static_method(s): ... s += 1 ... print s ... >>> a = Class() >>> a.class_method() 1 >>> Class.class_method() # The class shares this value across instances 2 >>> a.instance_method() 1 >>> Class.instance_method() # The class cannot use an instance method Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unbound method instance_method() must be called with Class instance as first argument (got nothing instead) >>> Class.instance_method(a) 2 >>> b = 0 >>> a.static_method(b) 1 >>> a.static_method(ac) # Static method does not have direct access to >>> # class or instance properties. 3 >>> Class.c # ac above was passed by value and not by reference. 2 >>> ac 2 >>> ac = 5 # The connection between the instance >>> Class.c # and its class is weak as seen here. 2 >>> Class.class_method() 3 >>> ac 5
method_two将不起作用,因为你正在定义一个成员函数,但不告诉它该函数是什么成员。 如果你执行最后一行,你会得到:
>>> a_test.method_two() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: method_two() takes no arguments (1 given)
如果你定义了一个类的成员函数,那么第一个参数必须是“self”。
这是一个错误。
首先,第一行应该是这样的(注意首都)
class Test(object):
每当你调用一个类的方法,它将自己作为第一个参数(因此名称自我)和method_two给这个错误
>>> a.method_two() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: method_two() takes no arguments (1 given)
第二个将不起作用,因为当你调用它像python内部试图调用它作为第一个参数的a_test实例,但你的method_two不接受任何参数,所以它不会工作,你会得到一个运行时错误。 如果你想要一个静态方法的等价物,你可以使用一个类方法。 Python中的类方法比Java或C#等语言中的静态方法要less得多。 通常最好的解决scheme是在类定义之外的模块中使用方法,这些方法比类方法更有效。
对method_two的调用将抛出一个exception,使其不能接受Python运行时将自动传递的self参数。
如果要在Python类中创build静态方法,请使用staticmethod decorator
对其进行staticmethod decorator
。
Class Test(Object): @staticmethod def method_two(): print "Called method_two" Test.method_two()
请阅读Guido First Class的所有文档清楚地解释了Unbound,Bound方法是如何诞生的。
从上面的Armin Ronacher准确的解释,扩大他的答案,以便像我这样的初学者很好理解:
在一个类中定义的方法的区别,无论是静态方法还是实例方法(还有另外一种types的方法 – 这里没有讨论,所以跳过它),都是在于它们是否以某种方式绑定到类实例。 例如,说出该方法在运行时是否接收到对类实例的引用
class C: a = [] def foo(self): pass C # this is the class object Ca # is a list object (class property object) C.foo # is a function object (class property object) c = C() c # this is the class instance
类对象的__dict__
dictionary属性拥有对类对象的所有属性和方法的引用,因此
>>> C.__dict__['foo'] <function foo at 0x17d05b0>
foo的方法可以像上面那样访问。 这里需要注意的一点是,python中的所有内容都是一个对象,所以上面的字典中的引用本身指向其他对象。 让我把它们称为类属性对象 – 或者作为我简要回答的范围内的CPO。
如果一个CPO是一个描述符,那么Python解释器调用CPO的__get__()
方法来访问它所包含的值。
为了确定CPO是否是描述符,python解释器检查它是否实现了描述符协议。 实现描述符协议是实现3种方法
def __get__(self, instance, owner) def __set__(self, instance, value) def __delete__(self, instance)
例如
>>> C.__dict__['foo'].__get__(c, C)
哪里
-
self
是CPO(它可以是list,str,function等的一个实例),由运行时提供 -
instance
是定义此CPO的类的实例(上面的对象'c'),并且需要由我们提供 -
owner
是定义这个CPO的类(上面的类对象'C'),需要由我们提供。 不过这是因为我们正在把它叫做CPO。 当我们在实例上调用它时,我们不需要提供这个,因为运行时可以提供实例或它的类(多态) -
value
是CPO的预期价值,需要由我们提供
并非所有的CPO都是描述符。 例如
>>> C.__dict__['foo'].__get__(None, C) <function C.foo at 0x10a72f510> >>> C.__dict__['a'].__get__(None, C) Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'list' object has no attribute '__get__'
这是因为列表类没有实现描述符协议。
因此, c.foo(self)
中的c.foo(self)
variables是必需的,因为它的方法签名实际上是这个C.__dict__['foo'].__get__(c, C)
(如上所述,C不是必需的,因为它可以被发现或polymorphed)这也是为什么你得到一个TypeError,如果你不通过所需的实例参数。
如果您注意到方法仍然通过类Object C来引用,并且通过将实例对象forms的上下文传递到此函数来实现与类实例的绑定。
这非常棒,因为如果你select不保留上下文或者不绑定实例,所有需要的就是编写一个类来包装描述符CPO,并覆盖它的__get__()
方法,不需要上下文。 这个新类是我们所说的装饰器,通过关键字@staticmethod
来应用
class C(object): @staticmethod def foo(): pass
新包装的CPO foo
没有上下文不会抛出错误,可以validation如下:
>>> C.__dict__['foo'].__get__(None, C) <function foo at 0x17d0c30>
静态方法的用例更多的是命名空间和代码可维护性(将其从一个类中取出并在整个模块中使用)。
除非实际情况需要对方法进行调整(如访问实例variables,类variables等),否则尽可能编写静态方法而不是实例方法。 一个原因是通过不保留对对象的不需要的引用来缓解垃圾收集。