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

时间:2020-03-06 14:32:03  来源:igfitidea点击:

以下类方法有什么区别?

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

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会自动使用对该对象的引用作为第一个参数。变量" 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"装饰它。

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"(类的类,请参见此问题)不要为" 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

静态方法装饰器包装了类,并实现了一个虚拟的__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