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

2024-04-19 09:21:56 发布

您现在位置:Python中文网/ 问答频道 /正文

试图用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{a1}源代码,只需将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绑定为实例方法?一些元类魔法

谢谢大家!


Tags: 实例方法代码fromimportitempushlist
2条回答

也许这就是python调用函数的方式。当您尝试print(type(heappush))时,您会注意到差异

对于问题1,用于标识哪个函数是哪个类型(即staticmethodclassmethod)的修饰符类似于调用并处理函数,并将处理后的函数返回到该名称。因此,确定它的数据应该在函数的某个属性中。在我找到它的位置后,问题3可能会被解决

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

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

heapq库就是其中之一——如果您选择链接到的文件,但接近结尾,您将看到代码片段,它看起来是否有本机版本,并覆盖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)

相关问题 更多 >