<blockquote>
<p>Are there more than three types of methods in Python?</p>
</blockquote>
<p><strong>是的。您提到的三种内置类型(实例方法、类方法、静态方法),如果计算<code>@property</code>,则有四种,任何人都可以定义新的方法类型。在</p>
<p>一旦您理解了这样做的机制,就很容易解释为什么在python3中可以从类调用<code>unknown_mthd</code>。在</p>
<h2>一种新的方法</h2>
<p>假设我们想创建一个新的方法类型,将其称为<code>optionalselfmethod</code>,这样我们就可以这样做:</p>
<pre><code>class Test(object):
@optionalselfmethod
def optionalself_mthd(self, *args):
print('Optional-Self Method:', self, *args)
</code></pre>
<p>用法如下:</p>
^{pr2}$
<p>当对实例调用时,<code>optionalselfmethod</code>的工作方式与普通的实例方法类似,但在类上调用时,它总是接收第一个参数的<code>None</code>。如果它是一个普通的实例方法,则必须始终为<code>self</code>参数传递一个显式值才能使其工作。在</p>
<p>这是怎么回事?你怎么能像这样创建一个新的类型?在</p>
<h2>描述符协议</h2>
<p>当Python查找一个实例的字段时,即当您查找<code>x.whatever</code>时,它会在多个位置进行检查。当然,它会检查实例的<code>__dict__</code>,但它也会检查对象类的<code>__dict__</code>及其基类。在dict实例中,Python只是在寻找值,所以如果<code>x.__dict__['whatever']</code>存在,那么就是这个值。但是,在dict类中,Python正在寻找实现<a href="https://docs.python.org/3/howto/descriptor.html" rel="nofollow noreferrer">the Descriptor Protocol</a>的对象。在</p>
<p>描述符协议是三种内置方法的工作方式,它是<code>@property</code>的工作方式,也是我们的特殊<code>optionalselfmethod</code>的工作方式。在</p>
<p>基本上,如果dict类具有正确的名称<sup>1</sup>,Python会检查它是否有一个<code>__get__</code>方法,并像<code>type(x).whatever.__get__(x, type(x))</code>一样调用它,那么从<code>__get__</code>返回的值将用作字段值。在</p>
<p>例如,一个通常返回3:</p>
<pre><code>class GetExample:
def __get__(self, instance, cls):
print("__get__", instance, cls)
return 3
class Test:
get_test = GetExample()
</code></pre>
<p>用法如下:</p>
^{4}$
<p>请注意,使用实例和类类型调用描述符。它也可以用于类:</p>
<pre><code>In [29]: Test.get_test
__get__ None <class '__main__.Test'>
Out[29]: 3
</code></pre>
<p>{13>仍然是一个描述符,而不是类的自变量。在</p>
<p>这允许方法的简单实现:函数只是实现描述符协议。当您对函数调用<code>__get__</code>时,它返回实例的绑定方法。如果实例是<code>None</code>,则返回原始函数。您可以亲自致电<code>__get__</code>来查看:</p>
<pre><code>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>>
</code></pre>
<p><code>@classmethod</code>和{<cd21>}相似。这些修饰符使用提供不同绑定的<code>__get__</code>方法创建代理对象。类方法的<code>__get__</code>将方法绑定到实例,而静态方法的<code>__get__</code>不绑定到任何东西,即使在实例上调用也是如此。在</p>
<h2>可选的Self方法实现</h2>
<p>我们可以做一些类似的事情来创建一个新的方法,这个方法可以选择性地绑定到一个实例上。在</p>
<pre><code>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}>'
</code></pre>
<p>当您用<code>optionalselfmethod</code>修饰函数时,该函数将替换为我们的代理。此代理保存原始方法并提供一个<code>__get__</code>方法,该方法返回<code>boudnoptionalselfmethod</code>。当我们创建<code>boundoptionalselfmethod</code>时,我们告诉它要调用的函数和作为<code>self</code>传递的值。最后,调用<code>boundoptionalselfmethod</code>调用原始函数,但要在第一个参数中插入实例或<code>None</code>。在</p>
<h2>具体问题</h2>
<blockquote>
<p>Was making a method this way (without args while not explicitly
decorated as staticmethods) intentional in Python 3's design? UPDATED</p>
</blockquote>
<p>我相信这是有意的;但是目的本来是为了消除无约束的方法。在python2和python3中,<code>def</code><em>always</em>都会创建一个函数(通过检查类型的<code>__dict__</code>可以看到这一点:即使<code>Test.instance_mthd</code>返回为<code><unbound method Test.instance_mthd></code>,<code>Test.__dict__['instance_mthd']</code>仍然是{<cd37>})。在</p>
<p>在Python2中,<code>function</code>的<code>__get__</code>方法总是返回一个<code>instancemethod</code>,即使是通过类访问的时候。当通过实例访问时,方法绑定到该实例。当通过类访问时,该方法是非绑定的,并且包含一个检查tha的机制第一个参数是正确类的实例。在</p>
<p>在Python3中,<code>function</code>的<code>__get__</code>方法在通过类访问时返回原始函数,在通过实例访问时返回<code>method</code>。在</p>
<p>我不知道确切的原理,但我想对类级函数的第一个参数进行类型检查被认为是不必要的,甚至是有害的;Python毕竟允许duck类型。在</p>
<blockquote>
<p>Among the classic method types, what type of method is unknown_mthd?</p>
</blockquote>
<p><code>unknown_mthd</code>是一个普通函数,就像任何普通的实例方法一样。它只在通过实例调用时失败,因为当<code>method.__call__</code>试图用绑定实例调用<code>function</code><code>unknown_mthd</code>时,它没有接受足够的参数来接收<code>instance</code>参数。在</p>
<blockquote>
<p>Why can unknown_mthd be called by the class without passing an
argument?</p>
</blockquote>
<p>因为它只是一个普通的<code>function</code>,和其他的<code>function</code>一样。当用作实例方法时,我没有足够的参数来正确工作。在</p>
<p>您可能会注意到,<code>classmethod</code>和{<cd52>}无论是通过实例还是通过类调用它们的工作方式都是相同的,而{<cd2>}只有在通过类调用时才能正常工作,而在通过实例调用时失败。在</p>
<p><sub>1。如果一个特定的名称在实例dict中有一个值,并且在dict类中有一个描述符,那么使用哪一个取决于它是什么样的描述符。如果描述符只定义<code>__get__</code>,则使用实例dict中的值。如果描述符也定义了<code>__set__</code>,那么它就是一个数据描述符,描述符总是获胜。这就是为什么您可以在一个方法之上而不是<code>@property</code>;方法只定义<code>__get__</code>,这样你就可以把东西放在实例dict中的同一个命名槽中,而<code>@properties</code>定义{<cd55>},因此即使它们是只读的,也永远不会从实例<code>__dict__</code>中获得值,即使您以前绕过了属性查找并粘贴了一个值例如<code>x.__dict__['whatever'] = 3</code>。</sub></p>