Python中内置函数的实例方法别名

Posted

技术标签:

【中文标题】Python中内置函数的实例方法别名【英文标题】:Instance method aliases to builtin-functions in Python 【发布时间】:2021-07-22 20:54:08 【问题描述】:

为了在 Python 中尽可能高效地编写优先级队列的面向对象实现,我遇到了一个有趣的行为。以下代码工作正常

from heapq import heappush


class PriorityQueue(list):
    __slots__ = ()

    def push(self, item):
        heappush(self, item)

但是,我真的不想为调用heappush 编写包装方法,因为调用该函数会产生额外的开销。我推断由于heappush 签名使用list 作为第一个参数,同时将push 类属性与heappush 函数别名,后者成为一个成熟的类实例方法。但是,我的假设结果是错误的,下面的代码给出了错误。

from heapq import heappush


class PriorityQueue(list):
    __slots__ = ()
    push = heappush


PriorityQueue().push(0)
# TypeError: heappush expected 2 arguments, got 1

但是转到cpython heapq 源代码,只需将heappush 实现复制到范围并应用相同的逻辑即可。

from heapq import _siftdown


def heappush(heap, item):
    """Push item onto heap, maintaining the heap invariant."""
    heap.append(item)
    _siftdown(heap, 0, len(heap) - 1)


class PriorityQueue(list):
    __slots__ = ()
    push = heappush


pq = PriorityQueue()
pq.push(0)
pq.push(-1)
pq.push(3)
print(pq)
# [-1, 0, 3]
第一个问题:为什么会这样? Python 如何决定哪个函数适合绑定为实例方法,哪个不适合? 第二个问题:cpython/Lib/heapq.py 中的heappushheapq 模块中的实际heappush 有什么区别?它们实际上是不同的,因为以下代码给出了错误
from dis import dis
from heapq import heappush


dis(heappush)
# TypeError: don't know how to disassemble builtin_function_or_method objects
第三个问题:如何强制Python绑定原生heappush作为实例方法?一些元类魔法?

谢谢!

【问题讨论】:

【参考方案1】:

发生的情况是 Python 在标准库中提供了许多算法的纯 Python 实现,即使它包含相同算法的加速本机代码实现

heapq 库就是其中之一 - 如果您选择链接到的文件,但接近尾声,您将看到代码 sn-p,它查看本机版本是否可用,并覆盖 Python 版本,这有你复制粘贴的代码 - https://github.com/python/cpython/blob/76cd81d60310d65d01f9d7b48a8985d8ab89c8b4/Lib/heapq.py#L580

try:
    from _heapq import *
except ImportError:
    pass
...

heappush 的原生版本被加载到模块中,没有简单的方法来获取对原始 Python 函数的引用,只能获取实际的文件源代码。

现在,重点是:为什么原生函数不能作为类方法工作? heappush 的类型是 builtin_function_or_method,与纯 Python 函数的 function 形成对比 - 主要区别之一是第二种对象类型具有 __get__ 方法。这个__get__ 使Python 定义的函数作为“描述符”工作:当从实例中检索属性时调用__get__ 方法。对于普通函数,此调用记录self参数,并在实际函数调用时注入。

因此,很容易编写一个“instancemethod”装饰器,它可以让内置函数像 Python 函数一样工作并且可以作为方法使用。但是,创建部分函数或 lambda 函数的开销应该超过您试图消除的额外函数调用的开销 - 因此您不应该从中获得速度提升,尽管它可能仍然读起来更优雅:

class instancemethod:
    def __init__(self, func):
        self.func = func
    def __get__(self, instance, owner):
        return lambda *args, **kwargs: self.func(instance, *args, **kwargs)

import heapq

class MyHeap(list):
    push = instancemethod(heapq.heappush)

【讨论】:

感谢您的全面回答。是的,您建议的代码确实会引发错误。但是为什么我的第一个代码 sn-p 中的本机 Python heappush 可以正常工作?从逻辑上讲,还应该检查selflist 类型之间的对应关系。 抱歉,self.func 调用的第一个参数必须是instance - 我写过self - 那是因为它没有通过isinstance(x, list) 内部检查。该解决方案确实有效(尽管如前所述,它应该比简单的换行还要慢)【参考方案2】:

也许是 python 调用函数的方式。当您尝试print(type(heappush)) 时,您会注意到不同之处。

对于问题1,用于识别哪个函数是哪个类型的装饰器(即staticmethodclassmethod)就像调用和处理函数并将处理后的函数返回到该名称。所以确定的数据应该在函数的某个属性中。等我找到它在哪里,问题3就可以解决了。

对于问题2,当你导入内置函数时,它的类型是builtin_function_or_method。但是如果你复制并粘贴它,它是在你的代码中定义的,所以它只是function。这可能会导致解释器将其称为静态方法而不是实例方法。

【讨论】:

你得到了正确的线索,但没有完全切中要害 - 当然,函数类型的变化不是因为它被“复制并粘贴”到不同的模块 您知道原因(在您的回答中)。但是为了澄清,这就像你在解释器中实现一个函数作为一些“python”代码的副本。当您导入“本机”代码时。但公平地说,在一般情况下。感谢您的澄清和解释。

以上是关于Python中内置函数的实例方法别名的主要内容,如果未能解决你的问题,请参考以下文章

python内置函数

python详解python函数定义 def()与参数args可变参数*args关键参数**args使用实例

Python中类的内置方法与继承关系实例

python内置函数和魔法函数

python类与对象-如何通过实例方法名字的字符串调用方法

Python内置:split()方法