有没有超过三种类型的Python方法?

2024-04-27 14:04:30 发布

您现在位置:Python中文网/ 问答频道 /正文

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

  1. 实例方法-实例,即self
  2. 类方法-类,即cls
  3. 静态方法-无

这些经典方法在下面的Test类中实现,其中包括一个常用方法:

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.")

在Python3中,unknown_mthd可以安全地调用,但在Python2中它会引发一个错误:

^{pr2}$

这个错误表明python2不打算使用这种方法。也许它现在的允许是由于Python3(REF 001)中取消了未绑定的方法。而且,unknown_mthd不接受arg,它可以被一个类(如staticmethod,Test.unknown_mthd())绑定到。但是,它不是显式的staticmethod(没有decorator)。在

问题

  1. 在Python3的设计中,以这种方式创建方法(没有参数,但没有显式地修饰为staticmethods)是有意的吗?更新
  2. 在经典的方法类型中,unknown_mthd是什么类型的方法?在
  3. 为什么类在不传递参数的情况下可以调用unknown_mthd?在

一些初步检查结果不确定:

>>> # 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与静态方法类似。第二个建议它类似于实例方法。我不知道这个方法是什么,也不知道为什么要用它来代替经典的方法。如果您能给我一些关于如何更好地检查和理解它的建议,我将不胜感激。谢谢。在


Tags: 方法instancetestselfdeftypestaticfunction
2条回答

@BrenBarn回答你的问题做得很好。然而,这个答案增加了很多细节:

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

默认为2.X经典类

>>> 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个新样式类

^{pr2}$

你已经在你的问题中提到了这一点,作为提醒,提两次也没什么坏处。在


>>> # 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不接受参数,通常是因为您定义了没有参数的函数,因此它不接受任何参数。请小心,当您通过类名引用静态方法以及编码的unknown_meth方法时,它们不会神奇地绑定到类(例如,Test.unknown_meth)。在python3.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是一种非常动态的,而不是一种限制性语言;为什么要测试传递给类方法的对象类型和因此,将方法限制为类的特定实例?类型测试消除了多态性。如果你只是返回一个简单的函数,当一个方法通过一个函数行为类似于静态方法的类获取时,你可以认为unknown_meth是3.X下的静态方法,只要你小心不要通过Test的实例来获取它,你就可以走了。在

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

3.X以下:

^{4}$

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

>>> type(Test.__dict__['unknown_mthd']) is FunctionType
True
>>> type(Test.unknown_mthd) is MethodType
True

unknown_mthd是一个存在于Test__dict__中的简单函数,实际上只是一个存在于Test名称空间字典中的简单函数。那么,它何时成为MethodType的实例?好吧,当您从返回未绑定方法的类本身或返回绑定方法的实例获取method属性时,它将成为MethodType的实例。在3.X中,Test.unknown_mthdFunctionType的一个简单函数实例,Test().unknown_mthd是{}的一个实例,它保留了类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的实例。在

Are there more than three types of methods in Python?

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

一旦您理解了这样做的机制,就很容易解释为什么在python3中可以从类调用unknown_mthd。在

一种新的方法

假设我们想创建一个新的方法类型,将其称为optionalselfmethod,这样我们就可以这样做:

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

用法如下:

^{pr2}$

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

这是怎么回事?你怎么能像这样创建一个新的类型?在

描述符协议

当Python查找一个实例的字段时,即当您查找x.whatever时,它会在多个位置进行检查。当然,它会检查实例的__dict__,但它也会检查对象类的__dict__及其基类。在dict实例中,Python只是在寻找值,所以如果x.__dict__['whatever']存在,那么就是这个值。但是,在dict类中,Python正在寻找实现the Descriptor Protocol的对象。在

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

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

例如,一个通常返回3:

class GetExample:
    def __get__(self, instance, cls):
        print("__get__", instance, cls)
        return 3

class Test:
   get_test = GetExample()

用法如下:

^{4}$

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

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

{13>仍然是一个描述符,而不是类的自变量。在

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

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和{}相似。这些修饰符使用提供不同绑定的__get__方法创建代理对象。类方法的__get__将方法绑定到实例,而静态方法的__get__不绑定到任何东西,即使在实例上调用也是如此。在

可选的Self方法实现

我们可以做一些类似的事情来创建一个新的方法,这个方法可以选择性地绑定到一个实例上。在

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修饰函数时,该函数将替换为我们的代理。此代理保存原始方法并提供一个__get__方法,该方法返回boudnoptionalselfmethod。当我们创建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

我相信这是有意的;但是目的本来是为了消除无约束的方法。在python2和python3中,defalways都会创建一个函数(通过检查类型的__dict__可以看到这一点:即使Test.instance_mthd返回为<unbound method Test.instance_mthd>Test.__dict__['instance_mthd']仍然是{})。在

在Python2中,function__get__方法总是返回一个instancemethod,即使是通过类访问的时候。当通过实例访问时,方法绑定到该实例。当通过类访问时,该方法是非绑定的,并且包含一个检查tha的机制第一个参数是正确类的实例。在

在Python3中,function__get__方法在通过类访问时返回原始函数,在通过实例访问时返回method。在

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

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

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

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

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

您可能会注意到,classmethod和{}无论是通过实例还是通过类调用它们的工作方式都是相同的,而{}只有在通过类调用时才能正常工作,而在通过实例调用时失败。在

1。如果一个特定的名称在实例dict中有一个值,并且在dict类中有一个描述符,那么使用哪一个取决于它是什么样的描述符。如果描述符只定义__get__,则使用实例dict中的值。如果描述符也定义了__set__,那么它就是一个数据描述符,描述符总是获胜。这就是为什么您可以在一个方法之上而不是@property;方法只定义__get__,这样你就可以把东西放在实例dict中的同一个命名槽中,而@properties定义{},因此即使它们是只读的,也永远不会从实例__dict__中获得值,即使您以前绕过了属性查找并粘贴了一个值例如x.__dict__['whatever'] = 3

相关问题 更多 >