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

时间:2008-09-22 10:49:43

标签: 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()

13 个答案:

答案 0 :(得分:400)

在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(类的类,参见this question)不为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

答案 1 :(得分:187)

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

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

现在让我们看一下shell中的那个类:

>>> C.foo
<unbound method C.foo>
>>> C.__dict__['foo']
<function foo at 0x17d05b0>

正如您所看到的,如果您访问该类的foo属性,则会返回一个未绑定的方法,但是在类存储(dict)中有一个函数。为什么?这样做的原因是你的类的类实现了一个解析描述符的__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>

希望能够解释它。

答案 2 :(得分:11)

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

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

method_two = staticmethod(method_two)

或者您可以使用@staticmethod function decorator

答案 3 :(得分:11)

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

答案 4 :(得分:4)

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

答案 5 :(得分:3)

上面的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的原因。

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

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

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

新包装的CPO foo中缺少上下文不会引发错误,可以按如下方式进行验证:

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

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

最好尽可能编写静态方法而不是实例方法,除非你需要对方法进行上下文化(比如访问实例变量,类变量等)。一个原因是通过不对对象进行不必要的引用来简化垃圾收集。

答案 6 :(得分:1)

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

如果要在Python类中创建静态方法,请使用staticmethod decorator进行装饰。

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

Test.method_two()

答案 7 :(得分:1)

这是一个错误。

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

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)

答案 8 :(得分:1)

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

答案 9 :(得分:1)

请阅读Guido First Class everything中的这些文档。清楚地解释了Unbound,Bound方法是如何诞生的。

答案 10 :(得分:0)

The definition of method_two is invalid. When you call method_two, you'll get TypeError: method_two() takes 0 positional arguments but 1 was given from the interpreter.

An instance method is a bounded function when you call it like a_test.method_two(). It automatically accepts self, which points to an instance of Test, as its first parameter. Through the self parameter, an instance method can freely access attributes and modify them on the same object.

答案 11 :(得分:0)

未绑定方法

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

绑定方法

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

正如其文档here所述,根据函数的绑定,未绑定或静态,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应该是相同的方法。”是的!

答案 12 :(得分:0)

绑定方法=实例方法

未绑定方法=静态方法。