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
中的heappush
与heapq
模块中的实际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 中的本机 Pythonheappush
可以正常工作?从逻辑上讲,还应该检查self
和list
类型之间的对应关系。
抱歉,self.func 调用的第一个参数必须是instance
- 我写过self
- 那是因为它没有通过isinstance(x, list)
内部检查。该解决方案确实有效(尽管如前所述,它应该比简单的换行还要慢)【参考方案2】:
也许是 python 调用函数的方式。当您尝试print(type(heappush))
时,您会注意到不同之处。
对于问题1,用于识别哪个函数是哪个类型的装饰器(即staticmethod
、classmethod
)就像调用和处理函数并将处理后的函数返回到该名称。所以确定的数据应该在函数的某个属性中。等我找到它在哪里,问题3就可以解决了。
对于问题2,当你导入内置函数时,它的类型是builtin_function_or_method
。但是如果你复制并粘贴它,它是在你的代码中定义的,所以它只是function
。这可能会导致解释器将其称为静态方法而不是实例方法。
【讨论】:
你得到了正确的线索,但没有完全切中要害 - 当然,函数类型的变化不是因为它被“复制并粘贴”到不同的模块 您知道原因(在您的回答中)。但是为了澄清,这就像你在解释器中实现一个函数作为一些“python”代码的副本。当您导入“本机”代码时。但公平地说,在一般情况下。感谢您的澄清和解释。以上是关于Python中内置函数的实例方法别名的主要内容,如果未能解决你的问题,请参考以下文章