在Python中是否有超过三种类型的方法?
在Python中是否有超过三种类型的方法?
Python中至少有3种不同的方法,它们的第一个参数不同:
- 实例方法 - 实例,即
self
- 类方法 - 类,即
cls
- 静态方法 - 无参数
下面的Test
类中实现了这些经典方法,包括一个普通方法:
class Test(): def __init__(self): pass def instance_mthd(self): print("实例方法.") @classmethod def class_mthd(cls): print("类方法.") @staticmethod def static_mthd(): print("静态方法.") def unknown_mthd(): # 没有装饰器 --> 实例方法,但是 # 没有self(或cls) --> 静态方法,所以... (?) print("未知方法.")
在Python 3中,可以安全地调用unknown_mthd
,但在Python 2中会引发错误:
>>> t = Test()
>>> # Python 3
>>> t.instance_mthd()
>>> Test.class_mthd()
>>> t.static_mthd()
>>> Test.unknown_mthd()
实例方法.
类方法.
静态方法.
未知方法.
>>> # Python 2
>>> Test.unknown_mthd()
TypeError: unbound method unknown_mthd() must be called with Test instance as first argument (got nothing instead)
这个错误表明在Python 2中不打算使用这样的方法。也许它现在被允许是因为Python 3中取消了无绑定方法(REF 001)。此外,unknown_mthd
不接受参数,它可以像静态方法一样由类来调用,即Test.unknown_mthd()
。然而,它不是一个显式的静态方法(没有装饰器)。
问题
- 在Python 3的设计中,这种方式(没有参数并且没有显式装饰为静态方法)是有意的吗?已更新
- 在经典方法类型中,
unknown_mthd
是哪种类型的方法? - 为什么可以通过类来调用
unknown_mthd
而不传递参数?
一些初步的检查得出了没有定论的结果:
>>> # 类型 >>> 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__ 类型,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() ic s u i c s u
第一组类型检查表明unknown_mthd
与静态方法类似。第二组表明它类似于实例方法。我不确定这个方法是什么,以及为什么应该使用它而不是经典方法。我希望能得到一些建议,以便更好地检查和理解它。谢谢。
在Python 2中,“常规”实例方法可以产生两种类型的方法对象,取决于您是通过实例还是通过类来访问它们。如果您使用inst.meth
(其中inst
是该类的实例),则会得到一个绑定的方法对象,它会跟踪附加到哪个实例,并将其作为self
传递。如果您使用Class.meth
(其中Class
是该类),则会得到一个未绑定的方法对象,它没有固定的self
值,但仍会进行检查,以确保在调用它时传递了适当类的self
。
在Python 3中,删除了未绑定的方法。现在,使用Class.meth
只会给您一个“普通”的函数对象,根本没有参数检查。
对于Python 3的设计来说,这种创建方法的方式是故意的吗?如果您的意思是删除未绑定的方法是否是故意的,答案是肯定的。您可以在邮件列表中找到Guido的讨论。基本上,他们决定删除未绑定的方法,因为它们增加了复杂性但带来的收益很少。
在经典方法类型中,unknown_mthd是什么类型的方法?它是一个实例方法,但是是一个有问题的实例方法。当您访问它时,会创建一个绑定的方法对象,但由于它不接受任何参数,它无法接受self
参数,因此无法成功调用。
为什么可以通过类调用unknown_mthd而不传递参数?在Python 3中,删除了未绑定的方法,因此Test.unkown_mthd
只是一个普通的函数。不会进行任何包装来处理self
参数,因此您可以将其作为不接受任何参数的普通函数调用。在Python 2中,Test.unknown_mthd
是一个未绑定的方法对象,它有一个检查,确保传递适当类的self
参数;由于该方法不接受任何参数,此检查失败。
感谢您的回答。对于您的第一个答案,不,我指的是在未显式装饰为静态方法的情况下创建没有参数的方法。我知道已删除未绑定的方法。:这不是特别的目标,但有意地删除了阻止您这样做的检查的机制。因此,基本上,他们并没有特意让这种情况成为可能,但是他们认为使其不可能的现有机制并不值得引入复杂性。
是的,我可以确认常规函数适用于类。例如,如果未知方法接受参数def unknown_mthd(foo): pass
,那么Test.unknown_mthd("bar")
是有效的。不接受self
参数,我不确定这是否可以称为实例方法。:我想这取决于您如何定义“实例方法”,是根据其如何定义还是根据其如何访问。它在定义方式上是一个实例方法。如果您在实例上访问它,它将不起作用。但是,您可以在类上访问它,在这种情况下,它的行为类似于静态方法。但它不是真正的静态方法,因为真正的静态方法也可以在实例上调用。
Python中是否有超过三种类型的方法?
是的。有三种内置的方法类型(实例方法,类方法,静态方法),如果计算,则为四种,并且任何人都可以定义新的方法类型。一旦您理解了如何做到这一点的机制,就很容易解释为什么在Python 3中可以从类中调用
unknown_mthd
。
一个新的方法类型
假设我们想要创建一种新的方法类型,称之为optionalselfmethod
,这样我们就可以像这样使用:
class Test(object): def optionalself_mthd(self, *args): print('Optional-Self Method:', self, *args)
使用方法如下:
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__
以及其基类。在实例字典中,Python只是寻找值,因此如果x.__dict__['whatever']
存在,那就是值。但是,在类字典中,Python正在寻找实现描述符协议的对象。
描述符协议是所有三种内置方法类型的工作方式,也是的工作方式,以及我们的特殊
optionalselfmethod
的工作方式。
基本上,如果类字典具有正确名称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()
使用方法如下:
In[22]: x = Test() In[23]: x.get_test __get__ <__main__.Test object at 0x7fe8003fc470> <class '__main__.Test'> Out[23]: 3
请注意,描述符在用实例和类类型调用时都会使用实例和类参数。它也可以用于类:
In [29]: Test.get_test __get__ None <class '__main__.Test'> Out[29]: 3
当描述符用于类而不是实例时,__get__
方法对于self返回None,但仍然会得到类参数。
这允许简单实现方法:函数只需实现描述符协议。当您调用__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>>
和
类似。这些装饰器创建具有提供不同绑定的
__get__
方法的代理对象。类方法的__get__
将方法绑定到实例,而静态方法的__get__
不绑定任何内容,即使在实例上调用时也是如此。
可选自身方法的实现
我们可以做类似的事情,创建一种可选地绑定到实例的新方法类型。
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
插入到第一个参数中。
具体问题
在Python 3的设计中,以这种方式(没有参数而没有明确地被装饰为静态方法)制作方法是否是有意的?已更新
我认为这是有意的;然而,意图可能是消除未绑定的方法。在Python 2和Python 3中,def
总是创建一个函数(您可以通过检查类型的__dict__
来看到这一点:即使Test.instance_mthd
返回为<unbound method Test.instance_mthd>
,Test.__dict__['instance_mthd']
仍然是<function instance_mthd at 0x...>
)。
在Python 2中,function
的__get__
方法在通过类访问时始终返回instancemethod
。在通过实例访问时,方法绑定到该实例。在通过类访问时,方法是未绑定的,并包含一个机制,该机制检查第一个参数是否是正确类的实例。
在Python 3中,function
的__get__
方法在通过类访问时将原始函数不变地返回,并在通过实例访问时返回一个method
。
我不知道确切的理由,但我猜测认为对类级函数的第一个参数进行类型检查是不必要的,甚至可能有害的;毕竟,Python允许鸭子类型。
在经典方法类型中,
unknown_mthd
是哪种类型的方法?
unknown_mthd
是一个普通的函数,就像任何普通的实例方法一样。只有在通过实例调用时才会失败,因为当method.__call__
试图使用绑定的实例调用function
unknown_mthd
时,它没有足够的参数接收instance
参数。
为什么可以在不传递参数的情况下通过类调用unknown_mthd?
因为它只是一个普通的function
,与任何其他function
一样。当然,当作为实例方法使用时,它没有足够的参数来正确工作。
您可能会注意到,无论通过实例还是类调用,classmethod
和staticmethod
的工作方式都相同,而unknown_mthd
只有在通过类调用时才能正确工作,在通过实例调用时会失败。
1.如果特定名称在实例字典中具有值,并且在类字典中具有描述符,则取决于描述符的类型使用哪个值。如果描述符只定义了__get__
,则使用实例字典中的值。如果描述符还定义了__set__
,则它是一个数据描述符,描述符始终获胜。这就是为什么您可以分配给方法而不能分配给;方法只定义
__get__
,因此可以将值放入实例字典中的同名槽中,而定义
__set__
,因此即使它们是只读的,也永远不会从实例__dict__
中获取值,即使您以前绕过了属性查找并使用例如x.__dict__['whatever'] = 3
将值插入到字典中。
您对描述符的解释正是我正在寻找的深度。我明白描述符起到了一定的作用,但我试图理解为什么不使用常规的静态方法?FYI:我已经修复了标题中的拼写错误。您可能希望编辑第一行以反映这个更改。
您所说的为什么不使用常规的静态方法是什么意思?用于什么目的?
我看不出unknown_mthd
这样的函数的实用性。一个实例或静态方法应该足够了。
我不认为我说过它会有用。这只是可能创建这样一个函数的方式,因为方法定义的方式(方法只是围绕函数的包装器,插入一个特定值作为第一个参数),如果您愿意,您可以自己这样做。没有为这样做而特定的用途,但是阻止您在类体中创建零参数函数将需要语言具有特殊情况逻辑,而不是让def
始终只是创建一个function
。
Python中有多于三种方法类型吗?
这个问题的出现是因为在Python的不同版本中,绑定方法和非绑定方法的改变是特定于版本的,并与新式类或经典类无关。在Python 2.X中,经典类默认使用非绑定方法,而在Python 3.X中,默认使用新式类。
对于经典类来说:
>>> class A: ... def meth(self): pass ... >>> A.meth>>> class A(object): ... def meth(self): pass ... >>> A.meth
对于新式类来说:
>>> class A: ... def meth(self): pass ... >>> A.meth
在Python 2中,当使用类名调用未知方法`Test.unknown_mthd()`时,会出现`TypeError: unbound method unknown_mthd() must be called with Test instance as first argument (got nothing instead)`的错误。这是因为`unknown_mthd`不接受参数,并且可以像`staticmethod`一样绑定到类上,例如`Test.unknown_mthd()`。然而,它并不是一个显式的`staticmethod`(没有装饰器)。在Python 3中,调用`Test.unknown_mthd`会返回一个简单的函数对象,而不是绑定到类的方法。
为什么在Python 3的设计中,将方法定义为不接受参数的方式是有意的呢?这可能是因为Python非常动态,不是一种限制性的语言。为什么要测试传递给类方法的对象的类型,从而将方法限制为特定的类实例?类型测试会消除多态性。当通过类名引用方法时,只需返回一个简单的函数,这样就可以将其视为静态方法。只要不通过类的实例引用它,就没有问题。
对于经典方法类型中的`unknown_mthd`,在Python 3下它是一个简单的函数对象。在Python 2下,它是一个存储在`Test__dict__`命名空间字典中的简单函数。当通过类本身获取方法属性时,它成为`MethodType`的实例(未绑定方法),而当通过实例获取方法属性时,它成为`MethodType`的实例(绑定方法)。在Python 3中,`Test.unknown_mthd`是一个简单的函数(`FunctionType`的实例),而`Test().unknown_mthd`是一个保留原始类`Test`实例并在函数调用时隐式地将其作为第一个参数添加的`MethodType`的实例。
为什么`unknown_mthd`可以在类中被调用而不需要传递参数?因为在Python 3中,`Test.unknown_mthd`只是一个简单的函数。而在Python 2中,`unknown_mthd`不是一个简单的函数,当调用时必须传递一个`Test`类的实例。
这种方法的实用性除了引入预期的副作用之外,我无法找到其他用途。这些“有限的静态方法”在实例上不起作用,似乎是一种退步。如果你知道其他的应用场景,我会很感激你的想法。谢谢。
目前,我可以想象使用这个特性来实现既是静态方法又是实例方法的类方法,或者仅仅作为静态方法使用。但是要小心,不要使用实例调用它,这当然是无法保证的。