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]
- 第一个问题:为什么会发生?
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
作为实例方法?一些 metaclass 魔法?
谢谢!
也许是 python 调用函数的方式。当您尝试 print(type(heappush))
时,您会发现不同之处。
对于问题 1,用于识别哪个函数是哪种类型的装饰器(即 staticmethod
、classmethod
)就像调用和处理函数以及 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)
试图在 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]
- 第一个问题:为什么会发生?
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
作为实例方法?一些 metaclass 魔法?
谢谢!
也许是 python 调用函数的方式。当您尝试 print(type(heappush))
时,您会发现不同之处。
对于问题 1,用于识别哪个函数是哪种类型的装饰器(即 staticmethod
、classmethod
)就像调用和处理函数以及 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)