据我所知,Python中至少有3种方法具有不同的第一个参数:
self
cls
这些经典方法在下面的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.")
在Python 3中,可以安全地调用unknown_mthd
,但它在Python 2中引发错误:
>>> 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)
这个错误表明这种方法并不适用于Python 2.也许它的允许现在是由于在Python 3(REF 001)中消除了未绑定的方法。此外,unknown_mthd
不接受args,它可以绑定到类,如静态方法Test.unknown_mthd()
。但是,它不是一个明确的静态方法(没有装饰器)。
问题
unknown_mthd
?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
类似于静态方法。第二个建议它类似于实例方法。我不确定这种方法是什么,或者为什么它应该用于经典方法。如果能更好地检查和理解它,我将不胜感激。感谢。
答案 0 :(得分:5)
一些背景知识:在Python 2中,&#34;常规&#34;实例方法可能会产生两种方法对象,具体取决于您是通过实例还是类访问它们。如果您执行了inst.meth
(其中inst
是该类的实例),则会得到一个绑定的方法对象,该对象会跟踪它附加到哪个实例,并将其作为self
传递给它。如果您执行了Class.meth
(其中Class
是该类),则会得到一个未绑定的方法对象,该对象没有固定值self
,但仍会进行检查以确保self
当你调用它时,传递了相应类的1}}。
在Python 3中,删除了未绑定的方法。现在做Class.meth
只会给你&#34;普通&#34;函数对象,根本没有参数检查。
在Python 3的设计中是否有意这样做?
如果你的意思是,有意删除未绑定的方法,答案是肯定的。你可以看到Guido on the mailing list的讨论。基本上,我们认为未绑定的方法增加了复杂性,但收效甚微。
在经典方法类型中,什么类型的方法是unknown_mthd?
这是一个实例方法,但是一个方法。当您访问它时,会创建一个绑定的方法对象,但由于它不接受任何参数,因此它无法接受self
参数并且无法成功调用。
为什么在没有传递参数的情况下可以通过类调用unknown_mthd?
在Python 3中,删除了未绑定的方法,因此Test.unkown_mthd
只是一个简单的函数。没有包装来处理self
参数,因此您可以将其称为不接受任何参数的普通函数。在Python 2中,Test.unknown_mthd
是一个未绑定的方法对象,它有一个检查,强制传递适当类的self
参数;因为,再次,该方法不接受任何参数,此检查失败。
答案 1 :(得分:1)
首先,绑定和非绑定方法的这种更改是特定于版本的,它与新类型或经典类无关:
默认情况下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新式课程
>>> class A:
... def meth(self): pass
...
>>> A.meth
<function A.meth at 0x7efd07ea0a60>
你已经在你的问题中提到了这一点,提醒它两次提及它并没有什么坏处。
>>> # Python 2
>>> Test.unknown_mthd()
TypeError: unbound method unknown_mthd() must be called with Test instance as first argument (got nothing instead)
此外,
unknown_mthd
不接受args,并且可以绑定到类似staticmethod
,Test.unknown_mthd()
的类。但是,它不是明确的staticmethod
(没有装饰者)
unknown_meth
不接受args,通常是因为你没有定义函数,所以它不接受任何参数。小心谨慎,静态方法以及编码unknown_meth
方法在通过类名引用时不会神奇地绑定到类(例如Test.unknown_meth
)。在Python下3.X Test.unknow_meth
在3.X中返回一个简单的函数对象,而不是绑定到类的方法。
1 - 在Python 3的设计中是否有意这样的方法(没有args而没有明确地装饰为staticmethods)?修订版
我不能代表CPython开发人员,也不能声称自己是他们的代表,但根据我作为Python程序员的经验,他们似乎想要摆脱一个不好的限制,特别是考虑到Python非常动态,不是限制语言;为什么要测试传递给类方法的对象类型,从而将方法限制为特定的类实例?类型测试消除了多态性。如果你只是在通过类获取方法时返回一个简单的函数会很好,这个函数的行为类似于静态方法,你可以认为unknown_meth
是3.X下的静态方法,只要你是小心不要通过Test
的实例获取它,你很高兴。
2-在经典方法类型中,什么类型的方法是
unknown_mthd
?
3.X以下:
>>> from types import *
>>> class Test:
... def unknown_mthd(): pass
...
>>> type(Test.unknown_mthd) is FunctionType
True
你可以看到它只是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
的实例?好吧,当您从类本身获取方法属性时,它将成为MethodType
的实例,该类返回未绑定的方法或返回绑定方法的实例 。在3.X中,Test.unknown_mthd
是一个简单的函数 - FunctionType
的实例,而Test().unknown_mthd
是MethodType
的一个实例,它保留了类{{1}的原始实例并在函数调用中隐式添加它作为第一个参数。
3-为什么可以在不传递参数的情况下由类调用
Test
?
同样,因为unknown_mthd
只是3.X下的一个简单函数。而在2.X中,Test.unknown_mthd
不是一个简单的函数,必须在调用时调用unknown_mthd
的实例。
答案 2 :(得分:1)
Python中有三种以上的方法吗?
是即可。你提到了三种内置类型(实例方法,类方法,静态方法),如果算上@property
则有四种,任何人都可以定义新的方法类型。
一旦理解了执行此操作的机制,就可以很容易地解释为什么{3}可以从Python 3中的类调用。
假设我们想要创建一种新类型的方法,将其命名为unknown_mthd
,以便我们可以执行以下操作:
optionalselfmethod
用法如下:
class Test(object):
@optionalselfmethod
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
参数传递一个显式值才能使它工作。
那么这是如何工作的?你如何创建这样的新方法类型?
当Python查找实例的字段时,即当您执行self
时,它会检查几个地方。它当然会检查实例x.whatever
,但它也会检查对象类的__dict__
及其基类。在实例dict中,Python只是在寻找值,所以如果__dict__
存在,那就是值。但是,在类dict中,Python正在寻找一个实现the Descriptor Protocol。
描述符协议是所有三种内置方法的工作原理,x.__dict__['whatever']
的工作方式,以及我们的特殊@property
如何工作。
基本上,如果类dict具有正确名称 1 的值,Python会检查它是否具有optionalselfmethod
方法,并将其称为__get__
然后,从type(x).whatever.__get__(x, type(x))
返回的值用作字段值。
因此,例如,一个总是返回3的简单描述符:
__get__
用法是这样的:
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
方法为self获取None,但仍然获取类参数。
这允许方法的简单实现:函数简单地实现描述符协议。当您在函数上调用__get__
时,它将返回实例的绑定方法。如果实例为__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
相似。这些装饰器使用@staticmethod
方法创建代理对象,这些方法提供不同的绑定。类方法&#39; s __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
的{{1}}方法。当我们创建__get__
时,我们告诉它要调用的函数和要传递的值boudnoptionalselfmethod
。最后,调用boundoptionalselfmethod
调用原始函数,但将实例或self
插入第一个参数。
以这种方式制作方法(没有args而不是明确的 在Python 3的设计中故意装饰为staticmethods)?修订版
我认为这是故意的;但意图是消除未绑定的方法。在Python 2和Python 3中,boundoptionalselfmethod
总是创建一个函数(您可以通过检查类型None
来看到这一点:即使def
回复为__dict__
,Test.instance_mthd
仍为<unbound method Test.instance_mthd>
。)
在Python 2中,Test.__dict__['instance_mthd']
&#39; <function instance_mthd at 0x...>
方法始终返回function
,即使通过类访问也是如此。通过实例访问时,该方法绑定到该实例。通过类访问时,该方法是未绑定的,并包含一种机制,用于检查第一个参数是否是正确类的实例。
在Python 3中,__get__
的{{1}}方法将在通过类访问时返回原始函数,并在通过实例访问时返回instancemethod
。
我不知道确切的理由,但我猜想对类级功能的第一个参数的类型检查被认为是不必要的,甚至可能是有害的; Python毕竟允许鸭子打字。
在经典方法类型中,什么类型的方法是unknown_mthd?
function
是一个普通函数,就像任何普通的实例方法一样。它仅在通过实例调用时失败,因为当__get__
尝试使用绑定实例调用method
unknown_mthd
时,它不会接受足够的参数来接收{{1} }参数。
为什么class_可以调用unknown_mthd而不传递 参数
因为它只是普通的method.__call__
,与其他任何function
相同。当用作实例方法时,我只是没有足够的参数来正常工作。
您可能会注意到unknown_mthd
和instance
无论是通过实例还是类调用都是一样的,而function
只有在通过实例或类调用时才能正常工作该类在通过实例调用时失败。
<子> 1。如果特定名称在实例dict中具有值,并且在类dict中具有描述符,则使用哪一个取决于它是什么类型的描述符。如果描述符仅定义function
,则使用实例dict中的值。如果描述符也定义了classmethod
,那么它就是数据描述符,并且描述符总是获胜。这就是为什么你可以分配方法而不是staticmethod
;方法只定义unknown_mthd
,因此您可以将事物放在实例dict中同名的插槽中,而__get__
定义__set__
,所以即使它们是只读的,您永远不会从实例@property
获取值,即使您之前已绕过属性查找并在dict中粘贴了一个值,例如__get__
。子>