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等),否则尽可能编写静态方法而不是实例方法。 一个原因是通过不保留对对象的不需要的引用来缓解垃圾收集。