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

Instance method aliases to builtin-functions in Python

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

from heapq import heappush


class PriorityQueue(list):
    __slots__ = ()

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

但是,我真的不想为调用 heappush 编写包装方法,因为它会导致调用函数的额外开销。我的理由是,由于 heappush 签名使用 list 作为第一个参数,同时将 push class 属性与 heappush 函数别名化,后者成为一个完整的-成熟的 class 实例方法。然而,我的假设竟然是错误的,下面的代码报错了。

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]
from dis import dis
from heapq import heappush


dis(heappush)
# TypeError: don't know how to disassemble builtin_function_or_method objects

谢谢!

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

对于问题 1,用于识别哪个函数是哪种类型的装饰器(即 staticmethodclassmethod)就像调用和处理函数以及 return 处理后的函数那个名字。所以确定的数据应该在函数的某些属性中。等我找到位置后,问题3可能就解决了

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

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

heapq 库就是其中之一 - 如果您选择要 link 的文件,但接近尾声时,您将看到代码片段,该代码片段查看本机版本是否可用,并覆盖Python 版本,其中包含您复制和粘贴的代码 - https://github.com/python/cpython/blob/76cd81d60310d65d01f9d7b48a8985d8ab89c8b4/Lib/heapq.py#L580

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

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

现在,重点是:为什么本机函数不能像 class 方法一样工作? 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)