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


242

以下类方法有什么区别?

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

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

18
除method_two()定义外,没有其他区别是无效的,并且其调用失败。
anatoly techtonik 2012年

14
@techtonik:method_two的定义没错!它在不正确/无效的规范中被调用,即带有一个额外的参数。
0xc0de 2013年

1
你们都是实例方法,而不是类方法。您可以通过应用定义来创建类方法@classmethod。应该调用第一个参数来cls代替,self它将接收类对象而不是类的实例:Test.method_three()并且a_test.method_three()是等效的。
Lutz Prechelt'8

为什么要创建不带self参数的函数定义?是否有强大的用例?
alpha_989

Answers:


412

在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

17
我支持这个答案,它优于我的答案。做得好Torsten :)
自由空间

24
在python 3中不推荐使用未绑定的方法。相反,只有一个功能。
13年

@boldnik,为什么说不赞成使用未绑定的方法?文档中仍然存在静态方法:docs.python.org/3/library/functions.html#staticmethod
alpha_989

195

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

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

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

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

如您所见,如果您访问foo类的属性,则会返回一个未绑定的方法,但是在类存储(字典)中有一个函数。为什么?这样做的原因是您的类的类实现了__getattribute__解析描述符的a。听起来很复杂,但事实并非如此。 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>

希望能解释一下。


12
staticmethod装饰包装类(...)这句话是有点误导作为被包装类是类方法foo,而不是其中的类foo定义。
Piotr Dobrogost 2013年

12

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

为了使它真正起作用,可以将其附加到类定义中:

method_two = staticmethod(method_two)

或者您可以使用@staticmethod 功能装饰器


4
您的意思是“ @staticmethod函数装饰器语法”。
tzot

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

2
Class.instance_method() # The class cannot use an instance method它可以使用。只需手动传递实例:Class.instance_method(a)
warvariuc 2012年

@warwaruk在那儿,看一下下面的TyeError线。
kzh 2012年

是的,我后来看到了。仍然,imo,说“类不能使用实例方法”是不正确的,因为您只在下面一行做了。
warvariuc 2012年

@kzh,谢谢您的解释。当您调用时a.class_method(),它似乎a.c已更新为1,因此调用会将变量Class.class_method()更新Class.c2。但是,分配时a.c=5,为什么没有Class.c更新到5
alpha_989

@ alpha_989 python首先直接在ovjects实例上直接查找属性,如果不存在,则默认情况下会在其类上查找属性。如果您对此还有其他疑问,请随时在此处提出一个问题并将其链接,我很乐意为您提供进一步的帮助。
kzh

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


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

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

可以通过上面的方法访问foo方法。这里要注意的重要一点是python中的所有内容都是一个对象,因此上面字典中的引用本身指向其他对象。让我称它们为“类属性对象”-或简称为CPO。

如果CPO是描述符,则python解释器将调用__get__()CPO 的方法以访问其包含的值。

为了确定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.__dict__['foo'].__get__(c, C)(如上所述,不需要C,因为它可以被发现或多态),这也是为什么如果不传递所需的实例参数就会得到TypeError的原因。

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

这非常棒,因为如果您选择不保留上下文或不绑定到实例,则所需要做的只是编写一个类来包装描述符CPO并重写其__get__()方法以不需要上下文。这个新类称为装饰器,通过关键字应用@staticmethod

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

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

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

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

只要有可能,最好编写静态方法而不是实例方法,除非您当然需要使方法复杂化(例如访问实例变量,类变量等)。原因之一是通过不保留对对象的不必要引用来简化垃圾回收。


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)

1

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


如果我定义一个函数 Class Test(object): @staticmethod def method_two(): print(“called method_two”) ,是一个用例,那是我想让函数成为类的一部分,但又不希望用户直接访问函数的情况。因此method_two可以由Test实例中的其他函数调用,但不能使用调用a_test.method_two()。如果我使用def method_two(),此方法适用于此用例吗?还是有更好的方法来修改函数定义,以使其按上述用例使用?
alpha_989

1

对method_two的调用将因不接受self参数而引发异常,Python运行时将自动传递该参数。

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

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

Test.method_two()


0

的定义method_two无效。致电时method_two,您将获得TypeError: method_two() takes 0 positional arguments but 1 was given翻译服务。

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


0

未绑定方法

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

绑定方法

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

如此处所述,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应该是相同的方法。” 是!


0

绑定方法=实例方法

未绑定方法=静态方法。


请在回答中添加一些进一步的解释,以便其他人可以从中学习。例如,看看这个问题的其他答案
Nico Haase
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.