【问题标题】:Difference between methods and functions, in Python compared to C++方法和函数之间的区别,在 Python 中与 C++ 相比
【发布时间】:2014-01-25 17:38:52
【问题描述】:

我正在做 Code Academy 的 Python 教程,我对方法和函数的定义有点困惑。来自教程:

您已经了解我们在(或创建)字符串上使用的一些内置函数,例如.upper().lower()str()len()

来自 C++,我认为 .upper().lower() 将被称为方法和 len()str() 函数。在本教程中,这些术语似乎可以互换使用。

Python 是否像 C++ 那样区分方法和函数?

Difference between a method and a function 不同,我询问的是 Python 的细节。术语“方法”和“功能”似乎并不总是遵循链接问题的已接受答案中给出的定义。

【问题讨论】:

  • 不知道为什么这仍然被标记为重复。这个问题的重点在于 Python 使用这些术语的方式与重复答案中的解释方式不同。
  • 我投票决定重新开放,因为这里有一些好的和有趣的答案,但我认为你的问题的前提很薄弱。您正在将仅在第三方教程中看到的模式推广到整个社区。如果你能证明这种模式出现得更广泛,那将是一个有趣的问题,否则最好只针对本教程的作者。
  • 这个问题专门用于比较 Python 与 C++。不应该因为C++ question 而被关闭。也许是How does the Python data model handle unbound methods, bound methods and function calls? What's the difference?,或者别的什么,或者重新打开。
  • 是的,代码学院的术语很草率。但根据 Krumelur 的回答,仍然存在一些差异。

标签: python function oop methods


【解决方案1】:

需要注意:这个答案似乎已经过时了。检查this

function 是 Python 中的可调用对象,即可以使用 call operator 调用(尽管其他对象可以通过实现 __call__ 来模拟函数)。例如:

>>> def a(): pass
>>> a
<function a at 0x107063aa0>
>>> type(a)
<type 'function'>

方法是一类特殊的函数,可以绑定未绑定

>>> class A:
...   def a(self): pass
>>> A.a
<unbound method A.a>
>>> type(A.a)
<type 'instancemethod'>

>>> A().a
<bound method A.a of <__main__.A instance at 0x107070d88>>
>>> type(A().a)
<type 'instancemethod'>

当然,不能调用未绑定的方法(至少不能直接调用而不传递实例作为参数):

>>> A.a()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method a() must be called with A instance as first argument (got nothing instead)

在 Python 中,在大多数情况下,您不会注意到绑定方法、函数或可调用对象(即实现 __call__ 的对象)或类构造函数之间的区别。它们看起来都一样,只是有不同的命名约定。不过,在引擎盖下,这些对象可能看起来大不相同。

这意味着绑定方法可以用作函数,这是使 Python 如此强大的众多小事之一

>>> b = A().a
>>> b()

这也意味着即使len(...)str(...) 之间存在根本区别(后者是类型构造函数),但在深入挖掘之前您不会注意到区别:

>>> len
<built-in function len>
>>> str
<type 'str'>

【讨论】:

  • 我同意方法是函数的子集。我不会说函数是任何可调用对象。一个类是可调用的,但我不会称它为函数。
  • 方法是与特定类相关联的函数,对吧?
  • 一个方法关联一个类,一个绑定方法关联一个类的实例。
  • @BrenBarn 没错,我改变了措辞。
  • @BrenBarn 嗯,这实际上是真的。我认为,在 Python 3 中,您甚至可以将任何对象作为第一个参数传递。
【解决方案2】:

如果您仍然不了解方法的工作原理,请查看 实施也许可以澄清问题。当一个实例属性 被引用的不是数据属性,它的类被搜索。如果 该名称表示一个有效的类属性,它是一个函数对象,一个 方法对象是通过打包(指向)实例对象来创建的 和刚刚在一个抽象对象中一起找到的函数对象: 这是方法对象。当方法对象被调用时 参数列表,从实例构造一个新的参数列表 对象和参数列表,函数对象被调用 这个新的参数列表。

http://docs.python.org/2/tutorial/classes.html#method-objects

仔细阅读这段摘录。

意思是:

1) 一个实例并不真正拥有一个对象,它是一个将成为其属性的方法。
实际上,实例的__dict__中根本没有“方法”属性(__dict__是对象的命名空间)

2) 当一个“方法”属性被调用时,一个实例似乎有一个“方法”这一事实是由于一个进程,而不是一个实例的命名空间内的方法对象的存在

3) 此外,在类的命名空间中实际上并不存在方法对象。

但是实例是有区别的,因为当这样的调用完成时,一定有什么东西会导致一个真正的方法对象,不是吗?

所谓的类的“方法”属性,为了表述方便,实际上是一个函数对象,是类命名空间中的属性。
也就是说,一个pair (identifier of the function, function)是一个类的__dict__的成员,这个属性允许解释器在一个类的时候构造一个方法对象。方法调用被执行。

4) 同样,当一个“方法”属性被调用时,一个类似乎有一个“方法”这一事实是由于一个进程,而不是一个类的命名空间内的方法对象的存在

编辑我不太确定;见最后

5) 方法对象(不是“方法”对象;我的意思是真正的对象是真正的方法`,摘录中描述的内容)是在调用时创建的,它以前不存在。
它是一种包装器:它将指向实例对象和方法所基于的函数对象的指针打包。

所以,方法是基于函数的。这个函数对我来说是持有所说“方法”的类的真正属性,因为这个函数确实属于该类的命名空间(__dict__):当__dict__ 是时,这个函数被描述为&lt;function ......&gt;印刷。
可以使用别名im_func__func__ 从方法对象访问此函数(请参见下面的代码)

.

我相信这些概念不是很广为人知和理解的。但是下面的代码证明了我所说的。

class A(object):
    def __init__(self,b=0):
        self.b = b
    print 'The __init__ object :\n',__init__

    def addu(self):
        self.b = self.b + 10
    print '\nThe addu object :\n',addu


print '\nThe A.__dict__  items :\n',
print '\n'.join('  {0:{align}11}  :  {1}'.format(*it,align='^')
                for it in A.__dict__.items())
a1 = A(101)
a2 = A(2002)

print '\nThe a1.__dict__  items:'
print '\n'.join('  {0:{align}11}  :  {1}'.format(*it,align='^')
                for it in a1.__dict__.items())

print '\nThe a2.__dict__  items:'
print '\n'.join('  {0:{align}11}  :  {1}'.format(*it,align='^')
                for it in a2.__dict__.items())

print '\nA.addu.__func__ :',A.addu.__func__
print id(A.addu.__func__),'==',hex(id(A.addu.__func__))
print

print 'A.addu :\n  ',
print A.addu,'\n  ',id(A.addu),'==',hex(id(A.addu))

print 'a1.addu :\n  ',
print a1.addu,'\n  ',id(a1.addu),'==',hex(id(a1.addu))
print 'a2.addu :\n  ',
print a2.addu,'\n  ',id(a2.addu),'==',hex(id(a2.addu))

a2.addu()
print '\na2.b ==',a2.b

print '\nThe A.__dict__  items :\n',
print '\n'.join('  {0:{align}11}  :  {1}'.format(*it,align='^')
                for it in A.__dict__.items())

结果

The __init__ object :
<function __init__ at 0x011E54B0>

The addu object :
<function addu at 0x011E54F0>

The A.__dict__  items :
  __module__   :  __main__
     addu      :  <function addu at 0x011E54F0>
   __dict__    :  <attribute '__dict__' of 'A' objects>
  __weakref__  :  <attribute '__weakref__' of 'A' objects>
    __doc__    :  None
   __init__    :  <function __init__ at 0x011E54B0>

The a1.__dict__  items:
       b       :  101

The a2.__dict__  items:
       b       :  2002

A.addu.__func__ : <function addu at 0x011E54F0>
18765040 == 0x11e54f0

A.addu :
   <unbound method A.addu> 
   18668040 == 0x11cda08
a1.addu :
   <bound method A.addu of <__main__.A object at 0x00CAA850>> 
   18668040 == 0x11cda08
a2.addu :
   <bound method A.addu of <__main__.A object at 0x011E2B90>> 
   18668040 == 0x11cda08

a2.b == 2012

The A.__dict__  items :
  __module__   :  __main__
     addu      :  <function addu at 0x011E54F0>
   __dict__    :  <attribute '__dict__' of 'A' objects>
  __weakref__  :  <attribute '__weakref__' of 'A' objects>
    __doc__    :  None
   __init__    :  <function __init__ at 0x011E54B0>

.

编辑

有些事情困扰着我,我不知道这个主题的深层内容:

以上代码表明A.addua1.addua2.addu都是同一个方法对象,具有唯一标识。
然而,A.addu 被称为未绑定方法,因为它没有任何关于特定实例的信息,
a1.addua2.addu 被称为绑定方法,因为每个方法都有指定实例的信息,该实例必须与方法的操作相关。
从逻辑上讲,对我来说,这意味着这三种情况的方法应该不同。

但是这三个的身份相同,而且这个身份与该方法所基于的函数的身份不同。
它得出的结论是,该方法实际上是一个存在于内存中的对象,并且它不会从一个实例的一次调用更改为另一个实例的另一个调用。

然而,打印类的命名空间__dict__,即使在创建实例和调用“方法”addu()之后,这个命名空间也不会暴露一个新对象可以识别为不同于addu 函数的方法对象。

这是什么意思?
它给我的印象是,方法对象一被创建,就不会被销毁,而是存在于内存 (RAM) 中。
但它隐藏起来,只有构成解释器功能的过程才知道如何以及在哪里找到它。
这个隐藏的对象,真正的方法对象,必须能够更改对必须应用函数的实例的引用,或者如果它作为未绑定方法被调用,则引用None。在我看来就是这样,但这只是头脑风暴的假设。

有人知道这次审讯的事情吗?


要回答这个问题,调用 .upper.lower functions 被认为是正确的,因为实际上它们是基于函数作为类的每个方法的。

但是,下面的结果很特殊,可能是因为它们是内置的方法/函数,而不是我的代码中的用户方法/函数。

x = 'hello'
print x.upper.__func__

结果

    print x.upper.__func__
AttributeError: 'builtin_function_or_method' object has no attribute '__func__'

【讨论】:

  • 我刚刚意识到A.addu 给出&lt;unbound method A.addu&gt;type(A.addu 给出&lt;type 'instancemethod'&gt; ---WHILE--- A.__dict__['addu'] 给出&lt;function addu at 0x011E54F0&gt;type(A.__dict__['addu']) 给出@987654353 @ 。根据到达对象的方式确实存在差异。使用 . 点符号会触发隐藏机制的动作,这就是我的回答和这两个结果所反映的。这些方法是真正神秘的东西,在 Python 内部机制的秘密黑暗中运作。我的看法
  • 首先感谢您的出色回答。关于您的疑问:每次访问 a1.addu 时都会创建一个新的(临时)对象,然后直接销毁。巧合的是,这些对象具有相同的 id(因为它们是在相同的内存地址创建的)。但是,如果您保留对已创建对象的引用,您将看到不同的 id:id(a1.addu) == id(a1.addu) 是 True,因为临时对象是一个接一个地创建和销毁并且重复使用相同的内存,但 a1.addu is a1.addu 是 False,因为临时对象是同时活着并且有不同的地址。
【解决方案3】:

在下面的类定义中:

class MyClass:
    """A simple example class"""
    def f(self):
        return 'hello world'
  • 班级我的班级
  • 函数f()
  • 方法无(实际上,不适用)

让我们创建一个上述类的实例。我们将通过将class object, i.e. MyClass() 分配给var x 来做到这一点

  x = MyClass()

这里,

  • 功能
  • 方法x.f()

不要忘记,当我们将 x 分配给 MyClass() 时,function object MyClass.f 用于定义(内部)method object x.f

【讨论】:

    【解决方案4】:

    基本上,是的,Python 确实区分了它们,但在 Python 中,通常将方法视为函数的子集。方法与类或实例相关联,而“独立函数”则不是。一种方法也是一种功能,但可能有一些功能不是方法。

    正如 Jon Clements 在他的评论中提到的那样,区别并不像在 C++ 中那么确定。独立函数可以在运行时“转换”为方法,并且可以将方法分配给变量,使得它们的行为与独立函数没有什么不同。所以方法和函数的界限是可渗透的。

    【讨论】:

    • 但是提到的函数/方法中的点运算符是否仍然将它们与字符串对象相关联?例如 len() 是一个以字符串对象作为参数的独立函数吗?
    • 是的,带点的是方法,是的len,是函数。
    • @Q-bertsuit “方法与类或实例相关联,而“独立函数”则不是。” 但是,出现在命名空间__dict__ 的类是函数,而不是方法。 ,,,, “方法也是函数” 方法不仅仅是函数,因为它是函数和实例的封装。请看我在回答中描述的内容。你怎么看?
    • @eyquem 虽然我很感激您在回答中付出的时间和精力,但这让我有点不知所措。我刚开始学习 Python,现在我很高兴知道方法和函数的定义比我习惯的要复杂。感谢您的时间! +1
    【解决方案5】:

    this answer获取sn-p

    以下是变化

    • 未绑定的方法(绑定到类对象的方法)不再可用。
    • 未绑定的方法 -> 函数
    • 类型 instancemethod 已从 Python 3 中移除

    更多关于this answer的方法

    关于Python docs上的方法

    更多关于Python docs的课程

    import sys
    print(sys.version)
    # 3.9.0rc2 (tags/v3.9.0rc2:2bd31b5, Sep 17 2020, 00:58:12) [MSC v.1927 64 bit (AMD64)]
    
    class A:
      def a(self): pass
    
    print(A.a)
    # <unbound method A.a>
    # <function A.a at 0x00000200FBE121F0>
    
    print(type(A.a))
    # <type 'instancemethod'>
    # <class 'function'>
    
    print(A().a)
    # <bound method A.a of <__main__.A instance at 0x107070d88>>
    # <bound method A.a of <__main__.A object at 0x00000200FBB12640>>
    
    print(type(A().a))
    # <type 'instancemethod'>
    # <class 'method'>
    

    【讨论】:

      【解决方案6】:

      简短回答: 方法是有界函数。当函数绑定到一个对象时,函数就变成了方法。

      要了解python中函数和方法的区别你需要了解

      1. 一切都是python中的对象。甚至函数也是一个对象 我搜索了 python.org 以找到 python 中对象的确切定义。但我找不到确切的定义。

        在你的 python 控制台中定义一个整数

        my_int = 1
        my_int.__class__
        

        输出:

        <class 'int'>
        

        定义一个 lambda 函数

        my_lambda = lambda x:x+1
        my_lambda.__class__
        

        输出

        <class 'function'>
        

        定义一个函数。

        def mycounter(x):
            return x+1
        mycounter.__class__
        

        输出

        <class 'function'>
        

        由此我们可以推测python中的一切都是对象。偶函数也是一个对象。

      2. 类对象和实例对象是不同的。定义类时,会创建类对象。当您实例化一个类时,会创建实例对象。

        在 python 控制台中定义一个类。

        class MyClass:
            """A simple example class"""
            i = 12345
        
            def f(self):
                return 'hello world'
        

        在您的 python 控制台中,您可以检查 MyClass 对象是否已创建

        MyClass.__class__
        

        输出

        <class 'type'>
        

        创建类的实例对象

        mc = MyClass()
        mc.__class__
        

        输出

        <class 'MyClass'>
        

        检查实例对象的属性 i

        mc.i
        

        输出

        12345
        

        检查类对象的属性 i

        MyClass.i
        

        输出

        12345
        

        检查实例对象的属性 f。

        mc.f
        

        输出

        <bound method MyClass.f of <MyClass object at 0x7fbea6c18d00>>
        

        检查类对象的属性 f。

        MyClass.f
        

        输出

        <function MyClass.f at 0x7f1730f9e1f0>
        

        我们可以看到,对于Class对象,f是一个函数对象,比如实例对象,f是一个绑定的方法对象。

        在实例对象上调用 f()

        mc.f()
        

        输出

        'hello world'
        

        在类对象上调用 f()。

        MyClass.f()
        

        输出

        Traceback (most recent call last):
          File "<console>", line 1, in <module>
        TypeError: f() missing 1 required positional argument: 'self'
        

        调用类对象f函数时传递一个实例对象。

        MyClass.f(mc)
        

        输出

        'hello world'
        

      从上面的例子我们可以说-

      1. MyClass 是 Class 对象,mc 是实例对象,它们彼此不同。
      2. 当函数绑定到一个对象时,函数就变成了方法。这些对象不必只是类实例对象。它们可以是任何其他 python 对象。

      参考资料:

      1. 根据 function 的 python 文档-

      块引用 方法是一个“属于”对象的函数,名为 obj.methodname,其中 obj 是某个对象(这可能是一个表达式),methodname 是由对象类型定义的方法的名称。不同的类型定义不同的方法。不同类型的方法可以同名,不会造成歧义。

      1. 根据 class definition 的 python 文档-

      当输入一个类定义时,会创建一个新的命名空间,并将其用作局部范围——因此,对局部变量的所有赋值都会进入这个新的命名空间。特别是,函数定义在这里绑定了新函数的名称。当一个类定义正常离开时(通过结束),一个类对象被创建。

      1. 根据 class objects 的 python 文档

      类对象支持两种操作:属性引用和实例化。

      1. 根据 instance objects 的 python 文档

      实例对象理解的唯一操作是属性引用。有效的属性名有两种:数据属性和方法。

      方法是“属于”一个对象的函数。 (在 Python 中,术语方法并不是类实例所独有的:其他对象类型也可以具有方法。例如,列表对象具有称为 append、insert、remove、sort 等的方法。但是,在下面的讨论中,除非另有明确说明,否则我们将专门使用术语方法来表示类实例对象的方法。)

      根据定义,作为函数对象的类的所有属性都定义了其实例的相应方法。实例对象的有效方法名称取决于其类。根据定义,作为函数对象的类的所有属性都定义了其实例的相应方法。所以在我们的例子中,x.f 是一个有效的方法引用,因为 MyClass.f 是一个函数, 但 x.i 不是,因为 MyClass.i 不是。但是 x.f 与 MyClass.f 不同——它是一个方法对象,而不是一个函数对象。

      【讨论】:

        猜你喜欢
        • 2010-10-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-02-03
        • 2017-12-28
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多