Python中的类方法差异:绑定,未绑定和静态

Class method differences in Python: bound, unbound and static

下面的类方法有什么区别?

一个是静态的,另一个不是?

1
2
3
4
5
6
7
8
9
10
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的调用)是绑定函数

1
a_test.method_one()

转换为

1
Test.method_one(a_test)

即对未绑定方法的调用。因此,调用您的method_two版本将失败,因为TypeError

1
2
3
4
5
>>> 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)

可以使用decorator更改方法的行为

1
2
3
4
5
6
7
class Test(object):
    def method_one(self):
        print"Called method_one"

    @staticmethod
    def method_two():
        print"Called method two"

decorator告诉内置的默认元类type(类的类,参见本问题)不要为method_two创建绑定方法。

现在,可以直接在实例或类上调用静态方法:

1
2
3
4
5
6
7
>>> a_test = Test()
>>> a_test.method_one()
Called method_one
>>> a_test.method_two()
Called method_two
>>> Test.method_two()
Called method_two


一旦理解了描述符系统的基本知识,python中的方法就变得非常简单。想象一下下面的课程:

1
2
3
class C(object):
    def foo(self):
        pass

现在让我们看看shell中的类:

1
2
3
4
>>> C.foo
<unbound method C.foo>
>>> C.__dict__['foo']
<function foo at 0x17d05b0>

正如您可以看到的,如果访问类的foo属性,您将返回一个未绑定的方法,但是在类存储(dict)中有一个函数。为什么?原因是类的类实现了解析描述符的__getattribute__。听起来很复杂,但事实并非如此。在这种特殊情况下,C.foo大致等同于此代码:

1
2
>>> C.__dict__['foo'].__get__(None, C)
<unbound method C.foo>

这是因为函数有一个使它们成为描述符的__get__方法。如果您有一个类的实例,它几乎是相同的,只是None是类实例:

1
2
3
>>> c = C()
>>> C.__dict__['foo'].__get__(c, C)
<bound method C.foo of <__main__.C object at 0x17bd4d0>>

为什么Python会这样做?因为方法对象将函数的第一个参数绑定到类的实例。这就是自我的来源。现在,有时您不希望类将函数设为方法,这就是staticmethod的作用所在:

1
2
3
4
 class C(object):
  @staticmethod
  def foo():
   pass

staticmethod修饰器包装类并实现一个虚拟的__get__,它将包装函数返回为函数而不是方法:

1
2
>>> C.__dict__['foo'].__get__(None, C)
<function foo at 0x17d0c30>

希望能解释清楚。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
>>> 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(a.c) # Static method does not have direct access to
>>>                      # class or instance properties.
3
>>> Class.c        # a.c above was passed by value and not by reference.
2
>>> a.c
2
>>> a.c = 5        # The connection between the instance
>>> Class.c        # and its class is weak as seen here.
2
>>> Class.class_method()
3
>>> a.c
5


调用类成员时,Python会自动使用对对象的引用作为第一个参数。变量self实际上毫无意义,它只是一种编码约定。如果你愿意的话,你可以叫它gargaloo。也就是说,调用method_two会引发TypeError,因为python会自动尝试将参数(对其父对象的引用)传递给定义为没有参数的方法。

要使其实际工作,可以将其附加到类定义中:

1
method_two = staticmethod(method_two)

或者可以使用@staticmethod函数修饰器。


方法"2"不起作用,因为您定义的是成员函数,但没有告诉它该函数是什么成员。如果执行最后一行,将得到:

1
2
3
4
>>> a_test.method_two()
Traceback (most recent call last):
  File"<stdin>", line 1, in <module>
TypeError: method_two() takes no arguments (1 given)

如果要为类定义成员函数,则第一个参数必须始终为"self"。


上面Armin Ronacher的准确解释,扩展了他的答案,使像我这样的初学者能够很好地理解:

在一个类中定义的方法,无论是静态方法还是实例方法(还有另一个类型-类方法-这里没有讨论,所以跳过它),其区别在于它们是否以某种方式绑定到类实例。例如,假设方法是否在运行时接收到对类实例的引用

1
2
3
4
5
6
7
8
9
10
class C:
    a = []
    def foo(self):
        pass

C # this is the class object
C.a # 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属性包含对类对象的所有属性和方法的引用,因此

1
2
>>> C.__dict__['foo']
<function foo at 0x17d05b0>

方法foo可以如上所述访问。这里需要注意的一点是,Python中的所有内容都是一个对象,因此上面字典中的引用本身就是指向其他对象的。让我把它们称为类属性对象——或者在我的简短回答范围内称为CPO。

如果cpo是描述符,那么python解释器调用cpo的__get__()方法来访问它包含的值。

为了确定CPO是否是描述符,python解释器检查它是否实现了描述符协议。实现描述符协议就是实现3种方法

1
2
3
def __get__(self, instance, owner)
def __set__(self, instance, value)
def __delete__(self, instance)

例如

1
>>> C.__dict__['foo'].__get__(c, C)

在哪里?

  • self是cpo(它可以是list、str、function等的实例),由运行时提供
  • instance是定义该cpo的类的实例(上面的对象‘c’),需要由我们明确提供。
  • owner是定义这个cpo的类(上面的类对象‘c’),需要我们提供。然而,这是因为我们在CPO上打电话。当我们在实例上调用它时,我们不需要提供这个,因为运行时可以提供实例或其类(多态性)。
  • value是CPO的预期价值,需要我方提供。

不是所有的cpo都是描述符。例如

1
2
3
4
5
6
>>> 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.__dict__['foo'].__get__(c, C)(如上所述,不需要c,因为它可以被发现或变形)这也是为什么如果不传递所需的实例参数,就会出现类型错误的原因。

如果您注意到该方法仍然通过类对象C引用,并且通过将实例对象形式的上下文传递到此函数中来实现与类实例的绑定。

这非常棒,因为如果您选择不保留上下文或不绑定到实例,那么所需要的就是编写一个类来包装描述符cpo,并重写其__get__()方法来不需要上下文。这个新类就是我们称之为decorator的类,它通过关键字@staticmethod应用。

1
2
3
4
class C(object):
  @staticmethod
  def foo():
   pass

新包装的cpo foo中没有上下文不会引发错误,可以按以下方式进行验证:

1
2
>>> C.__dict__['foo'].__get__(None, C)
<function foo at 0x17d0c30>

静态方法的用例更多的是名称间距和代码可维护性(将其从类中去掉,并使其在整个模块中都可用,等等)。

如果可能的话,最好是编写静态方法而不是实例方法,除非您当然需要将这些方法(如访问实例变量、类变量等)进行上下文化。一个原因是通过不保留对对象的不需要的引用来简化垃圾收集。


请从guido头等舱阅读这些文档,一切都清楚地解释了如何产生未绑定、绑定的方法。


第二个方法不起作用,因为当您像python那样调用它时,内部会尝试使用a_test实例作为第一个参数来调用它,但是您的方法_2不接受任何参数,因此它不起作用,您将得到一个运行时错误。如果需要静态方法的等价物,可以使用类方法。在Python中,类方法比Java或C语言中的静态方法要少得多。通常最好的解决方案是在模块中使用一个方法,在类定义之外,这些方法比类方法更有效。


这是一个错误。

首先,第一行应该是这样的(注意大写字母)

1
class Test(object):

每当调用类的方法时,它将自身作为第一个参数(因此名为self),方法ou two给出此错误

1
2
3
4
>>> a.method_two()
Traceback (most recent call last):
File"<stdin>", line 1, in <module>
TypeError: method_two() takes no arguments (1 given)

对方法"二"的调用将引发一个异常,即不接受自己的参数,python运行时将自动传递该参数。

如果要在python类中创建静态方法,请用staticmethod decorator来修饰它。

1
2
3
4
5
6
Class Test(Object):
  @staticmethod
  def method_two():
    print"Called method_two"

Test.method_two()


method_two的定义无效。当你打电话给method_two时,你会从翻译那里得到TypeError: method_two() takes 0 positional arguments but 1 was given

实例方法是一个有界函数,当您像a_test.method_two()一样调用它时。它自动接受self作为其第一个参数,该参数指向Test的一个实例。通过self参数,实例方法可以自由地访问属性并在同一对象上修改它们。