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()
159954 次浏览

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

为了让它工作,你可以把这个附加到你的类定义:

method_two = staticmethod(method_two)

或者你可以使用@staticmethod 函数修饰符

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

对method_two的调用将抛出一个异常,表示不接受self参数,Python运行时将自动传递给它。

如果你想在Python类中创建一个静态方法,用staticmethod decorator. conf修饰它。

Class Test(Object):
@staticmethod
def method_two():
print "Called method_two"


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(类的类,cf. 这个问题)不要为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

这是一个错误。

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

class Test(object):

当你调用一个类的方法时,它将自己作为第一个参数(因此命名为self), 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不接受任何参数,所以它不会工作,你会得到一个运行时错误。 如果你想要静态方法的等价物,你可以使用类方法。 与Java或c#等语言中的静态方法相比,Python中对类方法的需求要小得多。通常最好的解决方案是在类定义之外的模块中使用一个方法,这些方法比类方法更有效

一旦你了解了描述符系统的基础知识,Python中的方法就非常非常简单了。想象一下下面的类:

class C(object):
def foo(self):
pass

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

>>> 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要这么做呢?因为方法对象将函数的第一个形参绑定到类的实例。这就是自我的来源。有时候你不希望你的类把一个函数变成一个方法,这时就需要用到staticmethod:

 class C(object):
@staticmethod
def foo():
pass

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

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

希望这能解释清楚。

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

请阅读Guido 头等舱的一切清楚地解释了Unbound, Bound方法是如何诞生的。

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

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

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__字典属性保存了对类对象的所有属性和方法的引用,因此

>>> 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)中的参数self是必需的,因为它的方法签名实际上是这个C.__dict__['foo'].__get__(c, C)(如上所述,C是不需要的,因为它可以被找到或改变) 这也是为什么如果你不传递所需的实例参数,你会得到一个TypeError

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

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

来应用
class C(object):
@staticmethod
def foo():
pass

在新包装的CPO foo中缺少上下文不会抛出错误,可以通过以下方式验证:

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

静态方法的用例更多的是命名空间和代码可维护性(将其从类中取出并使其在整个模块中可用等)。

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

method_two的定义无效。当你调用method_two时,你会从解释器得到TypeError: method_two() takes 0 positional arguments but 1 was given

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

未绑定的方法

未绑定方法是尚未绑定到任何特定类实例的方法。

绑定的方法

绑定方法是绑定到类的特定实例的方法。

正如其文档在这里所示,self可以引用不同的东西,这取决于函数是绑定的、未绑定的还是静态的。

看看下面的例子:

class MyClass:
def some_method(self):
return self  # For the sake of the example


>>> MyClass().some_method()
<__main__.MyClass object at 0x10e8e43a0># This can also be written as:>>> obj = MyClass()


>>> obj.some_method()
<__main__.MyClass object at 0x10ea12bb0>


# Bound method call:
>>> obj.some_method(10)
TypeError: some_method() takes 1 positional argument but 2 were given


# WHY IT DIDN'T WORK?
# obj.some_method(10) bound call translated as
# MyClass.some_method(obj, 10) unbound method and it takes 2
# arguments now instead of 1


# ----- USING THE UNBOUND METHOD ------
>>> MyClass.some_method(10)
10

由于我们在最后一次调用中没有使用类实例obj,我们可以说它看起来像一个静态方法。

如果是的话,MyClass.some_method(10)调用和用@staticmethod装饰器修饰的静态函数调用之间的区别是什么?

通过使用装饰器,可以显式地明确使用该方法,而无需首先为其创建实例。通常情况下,人们不会期望在没有实例的情况下使用类成员方法,访问它们可能会导致错误,这取决于方法的结构。

此外,通过添加@staticmethod装饰器,我们也可以通过对象访问它。

class MyClass:
def some_method(self):
return self


@staticmethod
def some_static_method(number):
return number


>>> MyClass.some_static_method(10)   # without an instance
10
>>> MyClass().some_static_method(10)   # Calling through an instance
10

你不能用实例方法来做上面的例子。你可以在第一个参数中存活(就像我们之前所做的那样),但第二个参数将被转换为未绑定的调用MyClass.some_method(obj, 10),它将引发TypeError,因为实例方法接受一个参数,而你无意中试图传递两个参数。

然后,你可能会说,“如果我可以通过实例和类调用静态方法,MyClass.some_static_methodMyClass().some_static_method应该是相同的方法。”是的!

绑定方法=实例方法

解绑定方法=静态方法。