python中有三种以上的方法吗?

Are there more than three types of methods in Python?

我知道Python中至少有3种方法具有不同的第一个参数:

  • 实例方法-实例,即self
  • class方法-class,即cls
  • 静态方法-无
  • 这些经典方法在下面的Test类中实现,包括一个常用方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class Test():

        def __init__(self):
            pass

        def instance_mthd(self):
            print("Instance method.")

        @classmethod
        def class_mthd(cls):
            print("Class method.")

        @staticmethod
        def static_mthd():
            print("Static method.")

        def unknown_mthd():
            # No decoration --> instance method, but
            # No self (or cls) --> static method, so ... (?)
            print("Unknown method.")

    在python 3中,可以安全地调用unknown_mthd,但它在python 2中引发了一个错误:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    >>> t = Test()

    >>> # Python 3
    >>> t.instance_mthd()
    >>> Test.class_mthd()
    >>> t.static_mthd()
    >>> Test.unknown_mthd()

    Instance method.
    Class method.
    Static method.
    Unknown method.

    >>> # Python 2
    >>> Test.unknown_mthd()    
    TypeError: unbound method unknown_mthd() must be called with Test instance as first argument (got nothing instead)

    此错误表明这样的方法不适用于Python2。也许它现在的允许是由于在python 3中消除了未绑定的方法(ref 001)。此外,unknown_mthd不接受args,它可以被一个类(如staticmethod,Test.unknown_mthd()调用)绑定到。但是,它不是一个显式的静态方法(没有修饰符)。

    问题

  • 在Python3的设计中,是否有意采用这种方法(不使用参数,而不显式地作为静态方法进行修饰)?更新的
  • 在经典的方法类型中,unknown_mthd是什么类型的方法?
  • 为什么类可以调用unknown_mthd而不传递参数?
  • 一些初步检查得出了不确定的结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    >>> # Types
    >>> print("i", type(t.instance_mthd))
    >>> print("c", type(Test.class_mthd))
    >>> print("s", type(t.static_mthd))
    >>> print("u", type(Test.unknown_mthd))                            
    >>> print()

    >>> # __dict__ Types, REF 002
    >>> print("i", type(t.__class__.__dict__["instance_mthd"]))
    >>> print("c", type(t.__class__.__dict__["class_mthd"]))
    >>> print("s", type(t.__class__.__dict__["static_mthd"]))
    >>> print("u", type(t.__class__.__dict__["unknown_mthd"]))          
    >>> print()

    i <class 'method'>
    c <class 'method'>
    s <class 'function'>
    u <class 'function'>

    i <class 'function'>
    c <class 'classmethod'>
    s <class 'staticmethod'>
    u <class 'function'>

    第一组类型检查表明unknown_mthd类似于静态方法。第二个建议它类似于实例方法。我不知道这个方法是什么,也不知道为什么要用它来代替传统的方法。对于如何更好地检查和理解它,我将非常感谢。谢谢。

    • 参考001:python 3中的新功能:"未绑定方法"已被删除
    • 参考002:如何在python 3中区分实例方法、类方法、静态方法或函数?
    • 参考文件003:python中@staticmethod的意义是什么?


    一些背景:在Python2中,"常规"实例方法可能会产生两种方法对象,这取决于您是通过实例还是通过类访问它们。如果您执行了inst.meth(其中inst是类的一个实例),您得到了一个绑定方法对象,它跟踪它附加到哪个实例,并将其作为self传递。如果你做了Class.meth(其中Class是类),你得到了一个未绑定的方法对象,它没有固定的self值,但是仍然做了一个检查,以确保当你调用它时,通过了适当类的self

    在python 3中,未绑定的方法被删除。现在执行Class.meth只会给您"普通"的函数对象,根本不需要检查参数。

    Was making a method this way intentional in Python 3's design?

    如果你的意思是,有意删除未绑定的方法,答案是肯定的。您可以在邮件列表中看到guido的讨论。基本上,决定了无绑定方法会增加复杂性,但收益很小。

    Among the classic method types, what type of method is unknown_mthd?

    它是一个实例方法,但已损坏。当您访问它时,会创建一个绑定方法对象,但是由于它不接受任何参数,所以它无法接受self参数,并且无法成功调用。

    Why can unknown_mthd be called by the class without passing an argument?

    在python 3中,未绑定的方法被删除,所以Test.unkown_mthd只是一个普通函数。不需要包装来处理self参数,因此可以将其作为不接受任何参数的普通函数来调用。在python 2中,Test.unknown_mthd是一个未绑定的方法对象,它有一个检查,强制传递适当类的self参数;由于该方法再次不接受任何参数,因此该检查失败。


    Are there more than three types of methods in Python?

    Ok.

    对。您提到的内置类型有三种(实例方法、类方法、静态方法),如果计算@property,则有四种,任何人都可以定义新的方法类型。好的。

    一旦您了解了这样做的机制,就很容易解释为什么可以从Python3的类中调用unknown_mthd。好的。一种新方法

    假设我们想要创建一种新的方法,称之为optionalselfmethod,这样我们就可以做如下的事情:好的。

    1
    2
    3
    4
    class Test(object):
        @optionalselfmethod
        def optionalself_mthd(self, *args):
            print('Optional-Self Method:', self, *args)

    用法如下:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    In [3]: Test.optionalself_mthd(1, 2)
    Optional-Self Method: None 1 2

    In [4]: x = Test()

    In [5]: x.optionalself_mthd(1, 2)
    Optional-Self Method: <test.Test object at 0x7fe80049d748> 1 2

    In [6]: Test.instance_mthd(1, 2)
    Instance method: 1 2

    当对实例调用时,optionalselfmethod的工作方式与普通实例方法类似,但当对类调用时,它总是为第一个参数接收None。如果它是一个普通的实例方法,则必须始终为self参数传递一个显式值,以便它工作。好的。

    那么这是如何工作的呢?如何创建这样的新方法类型?好的。描述符协议

    当python查找一个实例的字段时,即在执行x.whatever时,它会在多个地方进行检查。当然,它检查实例的__dict__,但也检查对象类的__dict__及其基类。在实例dict中,python只是在寻找值,所以如果x.__dict__['whatever']存在,这就是值。然而,在类dict中,python正在寻找实现描述符协议的对象。好的。

    描述符协议是这三种内置方法的工作方式,它是@property的工作方式,它是我们的特殊optionalselfmethod的工作方式。好的。

    基本上,如果类dict有一个正确名称为1的值,python检查它是否有一个__get__方法,并像type(x).whatever.__get__(x, type(x))那样调用它,那么从__get__返回的值将用作字段值。好的。

    例如,一个通常返回3的普通描述符:好的。

    1
    2
    3
    4
    5
    6
    7
    class GetExample:
        def __get__(self, instance, cls):
            print("__get__", instance, cls)
            return 3

    class Test:
       get_test = GetExample()

    用法如下:好的。

    1
    2
    3
    4
    5
    In[22]: x = Test()

    In[23]: x.get_test
    __get__ <__main__.Test object at 0x7fe8003fc470> <class '__main__.Test'>
    Out[23]: 3

    请注意,使用实例和类类型调用描述符。它也可用于课堂:好的。

    1
    2
    3
    In [29]: Test.get_test
    __get__ None <class '__main__.Test'>
    Out[29]: 3

    当描述符用于类而不是实例时,__get__方法本身不获取任何描述符,但仍然获取类参数。好的。

    这允许方法的简单实现:函数只实现描述符协议。当对函数调用__get__时,它返回实例的绑定方法。如果实例是None,则返回原始函数。你可以自己打电话给__get__来看看:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    In [30]: x = object()

    In [31]: def test(self, *args):
        ...:     print(f'Not really a method: self<{self}>, args: {args}')
        ...:    

    In [32]: test
    Out[32]: <function __main__.test>

    In [33]: test.__get__(None, object)
    Out[33]: <function __main__.test>

    In [34]: test.__get__(x, object)
    Out[34]: <bound method test of <object object at 0x7fe7ff92d890>>

    @classmethod@staticmethod是相似的。这些修饰器使用提供不同绑定的__get__方法创建代理对象。类方法的__get__将该方法绑定到实例,静态方法的__get__即使在对实例调用时也不会绑定到任何内容。好的。可选的自方法实现

    我们可以做一些类似的事情来创建一个可以选择绑定到实例的新方法。好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    import functools

    class optionalselfmethod:

      def __init__(self, function):
        self.function = function
        functools.update_wrapper(self, function)

      def __get__(self, instance, cls):
        return boundoptionalselfmethod(self.function, instance)

    class boundoptionalselfmethod:

      def __init__(self, function, instance):
        self.function = function
        self.instance = instance
        functools.update_wrapper(self, function)

      def __call__(self, *args, **kwargs):
        return self.function(self.instance, *args, **kwargs)

      def __repr__(self):
        return f'<bound optionalselfmethod {self.__name__} of {self.instance}>'

    当你用optionalselfmethod修饰一个函数时,该函数被我们的代理替换。此代理保存原始方法并提供返回boudnoptionalselfmethod__get__方法。当我们创建一个boundoptionalselfmethod时,我们告诉它调用的函数和作为self传递的值。最后,调用boundoptionalselfmethod调用原始函数,但将实例或None插入第一个参数中。好的。具体问题

    Was making a method this way (without args while not explicitly
    decorated as staticmethods) intentional in Python 3's design? UPDATED

    Ok.

    我认为这是有意的;但是目的是要消除未绑定的方法。在python 2和python 3中,def总是创建一个函数(您可以通过检查类型的__dict__来看到这一点:即使Test.instance_mthd返回为Test.__dict__['instance_mthd']仍然是。好的。

    在python 2中,function__get__方法总是返回instancemethod,即使通过类访问也是如此。当通过实例访问时,该方法将绑定到该实例。当通过类访问时,该方法被解除绑定,并包含一个机制,用于检查第一个参数是否是正确类的实例。好的。

    在python 3中,function__get__方法在通过类访问时将返回原函数不变,通过实例访问时返回method。好的。

    我不知道确切的理由,但我想,对类级函数的第一个参数进行类型检查被认为是不必要的,甚至是有害的;毕竟Python允许duck输入。好的。

    Among the classic method types, what type of method is unknown_mthd?

    Ok.

    unknown_mthd是一个普通函数,就像任何普通的实例方法一样。它只有在通过实例调用时才会失败,因为当method.__call__试图用绑定实例调用functionunknown_mthd时,它没有接受足够的参数来接收instance参数。好的。

    Why can unknown_mthd be called by the class without passing an
    argument?

    Ok.

    因为它只是一个普通的function,和其他function一样。当用作实例方法时,我只是没有接受足够的参数来正确工作。好的。

    您可以注意到,无论是通过实例还是通过类调用,classmethodstaticmethod的工作方式都相同,而unknown_mthd只在通过类调用时才正常工作,而在通过实例调用时失败。好的。

    >1。如果一个特定的名称在实例dict中同时具有一个值,并且在类dict中同时具有一个描述符,则使用哪一个取决于它是哪种描述符。如果描述符只定义__get__,则使用实例dict中的值。如果描述符也定义了__set__,那么它就是一个数据描述符,描述符总是赢的。这就是为什么您可以在方法的顶部赋值,而不是在@property上赋值;方法只定义__get__,所以您可以在实例dict中将事物放在同一个命名槽中,而@properties定义__set__中,因此即使它们是只读的,即使您以前绕过了属性,也永远无法从实例__dict__中获得值。y查找并在dict中插入一个值,例如x.__dict__['whatever'] = 3好的。好啊。


    @布伦巴恩很好地回答了你的问题。然而,这个答案增加了大量的细节:

    首先,绑定和未绑定方法中的这种更改是特定于版本的,它与新样式或经典类不相关:

    默认为2.x经典类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    >>> class A:
    ...     def meth(self): pass
    ...
    >>> A.meth
    <unbound method A.meth>

    >>> class A(object):
    ...     def meth(self): pass
    ...
    >>> A.meth
    <unbound method A.meth>

    默认情况下为3.x个新样式类

    1
    2
    3
    4
    5
    >>> class A:
    ...     def meth(self): pass
    ...
    >>> A.meth
    <function A.meth at 0x7efd07ea0a60>

    你已经在你的问题中提到过这一点了,把它两次作为提醒并不伤人。

    1
    2
    3
    >>> # Python 2
    >>> Test.unknown_mthd()    
    TypeError: unbound method unknown_mthd() must be called with Test instance as first argument (got nothing instead)

    Moreover, unknown_mthd does not accept args, and it can be bound to a class like a staticmethod, Test.unknown_mthd(). However, it is not an explicit staticmethod (no decorator)

    unknown_meth不接受args,通常是因为定义函数时没有使用任何参数。小心小心,静态方法以及编码的unknown_meth方法在通过类名(如Test.unknown_meth引用它们时不会神奇地绑定到类。在python 3.x中,Test.unknow_meth返回3.x中的简单函数对象,而不是绑定到类的方法。

    1 - Was making a method this way (without args while not explicitly decorated as staticmethods) intentional in Python 3's design? UPDATED

    我不能代表cpython开发人员发言,也不能自称是他们的代表,但根据我作为Python程序员的经验,他们似乎想摆脱一个糟糕的限制,特别是考虑到Python是非常动态的,而不是一种限制语言;为什么要测试传递给类方法的对象类型,然后ce是否将方法限制到类的特定实例?类型测试消除了多态性。如果只返回一个简单的函数,当一个方法通过一个功能上类似于静态方法的类被获取时,你可以把unknown_meth看作是3.x下的静态方法,只要你小心不要通过Test的实例获取它,你就可以走了。

    2- Among the classic method types, what type of method is unknown_mthd?

    3岁以下:

    1
    2
    3
    4
    5
    6
    >>> from types import *
    >>> class Test:
    ...     def unknown_mthd(): pass
    ...
    >>> type(Test.unknown_mthd) is FunctionType
    True

    正如你所看到的,它只是3.x中的一个函数。在2.x下继续上一个会话:

    1
    2
    3
    4
    >>> type(Test.__dict__['unknown_mthd']) is FunctionType
    True
    >>> type(Test.unknown_mthd) is MethodType
    True

    unknown_mthd是一个简单的函数,它位于Test__dict__中,实际上只是一个简单的函数,它位于Test的名称空间字典中。那么,它什么时候成为MethodType的一个实例呢?当您从返回未绑定方法的类本身或返回绑定方法的实例中获取method属性时,它将成为EDOCX1的实例。在3.x中,Test.unknown_mthd是一个简单的函数——FunctionType的实例,Test().unknown_mthdMethodType的实例,它保留了类Test的原始实例,并作为函数调用的第一个隐式参数添加。

    3- Why can unknown_mthd be called by the class without passing an argument?

    同样,因为Test.unknown_mthd只是3.x下的一个简单函数,而在2.x中,unknown_mthd不是一个简单函数,调用时必须将Test的一个实例调用。