Python 中的类方法区别:绑定、非绑定和静态

2024-12-26 08:43:00
admin
原创
105
摘要:问题描述:以下类方法有何区别?是不是一个是静态的,另一个不是?class Test(object): def method_one(self): print "Called method_one" def method_two(): print "Cal...

问题描述:

以下类方法有何区别?

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

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

解决方案 1:

在 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

解决方案 2:

一旦你理解了描述符系统的基础知识,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 为什么要这样做呢?因为方法对象将函数的第一个参数绑定到类的实例。这就是 self 的由来。现在有时你不希望你的类将函数变成方法,这就是 selfstaticmethod发挥作用的地方:

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

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

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

希望这能解释这一点。

解决方案 3:

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

为了真正让它发挥作用,你可以将其附加到你的类定义中:

method_two = staticmethod(method_two)

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

解决方案 4:

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

解决方案 5:

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

解决方案 6:

上述 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 解释器会调用__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(它可以是列表、字符串、函数等的实例)并由运行时提供

  • 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__'

这是因为列表类没有实现描述符协议。

因此,参数 self inc.foo(self)是必需的,因为它的方法签名实际上就是这个C.__dict__['foo'].__get__(c, C)(如上所述,不需要 C,因为它可以被发现或多态)这也是为什么如果您不传递所需的实例参数就会得到 TypeError 的原因。

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

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

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

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

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

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

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

解决方案 7:

这是一个错误。

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

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:

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

解决方案 9:

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

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

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

Test.method_two()

解决方案 10:

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

解决方案 11:

绑定方法 = 实例方法

未绑定方法 = 静态方法。

解决方案 12:

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

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

解决方案 13:

未绑定的方法

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

绑定方法

绑定方法是与某个类的特定实例绑定的方法。

正如这里所记录的,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_method那么MyClass().some_static_method应该是相同的方法。” 是的!

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1590  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1361  
  信创产品在政府采购中的占比分析随着信息技术的飞速发展以及国家对信息安全重视程度的不断提高,信创产业应运而生并迅速崛起。信创,即信息技术应用创新,旨在实现信息技术领域的自主可控,减少对国外技术的依赖,保障国家信息安全。政府采购作为推动信创产业发展的重要力量,其对信创产品的采购占比情况备受关注。这不仅关系到信创产业的发展前...
信创和国产化的区别   18  
  信创,即信息技术应用创新产业,旨在实现信息技术领域的自主可控,摆脱对国外技术的依赖。近年来,国货国用信创发展势头迅猛,在诸多领域取得了显著成果。这一发展趋势对科技创新产生了深远的推动作用,不仅提升了我国在信息技术领域的自主创新能力,还为经济社会的数字化转型提供了坚实支撑。信创推动核心技术突破信创产业的发展促使企业和科研...
信创工作   18  
  信创技术,即信息技术应用创新产业,旨在实现信息技术领域的自主可控与安全可靠。近年来,信创技术发展迅猛,对中小企业产生了深远的影响,带来了诸多不可忽视的价值。在数字化转型的浪潮中,中小企业面临着激烈的市场竞争和复杂多变的环境,信创技术的出现为它们提供了新的发展机遇和支撑。信创技术对中小企业的影响技术架构变革信创技术促使中...
信创国产化   19  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用