gpt4 book ai didi

python - Python 中的猴子修补类和实例

转载 作者:行者123 更新时间:2023-12-05 02:26:55 24 4
gpt4 key购买 nike

我对以下差异感到困惑。假设我有这个类和一些用例:

class C:
def f(self, a, b, c=None):
print(f"Real f called with {a=}, {b=} and {c=}.")


my_c = C()
my_c.f(1, 2, c=3) # Output: Real f called with a=1, b=2 and c=3.

我可以猴子修补它以进行这样的测试:

class C:
def f(self, a, b, c=None):
print(f"Real f called with {a=}, {b=} and {c=}.")


def f_monkey_patched(self, *args, **kwargs):
print(f"Patched f called with {args=} and {kwargs=}.")


C.f = f_monkey_patched
my_c = C()
my_c.f(1, 2, c=3) # Output: Patched f called with args=(1, 2) and kwargs={'c': 3}.

到目前为止一切顺利。但我只想修补一个实例,它以某种方式消耗了第一个参数:

class C:
def f(self, a, b, c=None):
print(f"Real f called with {a=}, {b=} and {c=}.")


def f_monkey_patched(self, *args, **kwargs):
print(f"Patched f called with {args=} and {kwargs=}.")


my_c = C()
my_c.f = f_monkey_patched
my_c.f(1, 2, c=3) # Output: Patched f called with args=(2,) and kwargs={'c': 3}.

为什么第一个参数被消费为 self 而不是实例本身?

最佳答案

Python 中的函数是描述符;当它们附加到一个类,但查找类的一个实例时,描述符协议(protocol)被调用,代表您生成一个绑定(bind)方法(所以 my_c.f,其中 f class 上定义,与您最初定义的实际函数 f 不同,并隐式传递 my_c 作为 self )。

如果你想做一个仅对特定实例隐藏类 f 的替换,但仍然像你期望的那样将实例作为 self 传递,你需要手动 将实例绑定(bind)到函数以使用(不可否认的非常有据可查的)types.MethodType 创建绑定(bind)方法:

from types import MethodType  # The class implementing bound methods in Python 3

# ... Definition of C and f_monkey_patched unchanged

my_c = C()
my_c.f = MethodType(f_monkey_patched, my_c) # Creates a pre-bound method from the function and
# the instance to bind to

被绑定(bind)后,my_c.f 现在将表现为一个不接受来自调用者的 self 的函数,但是当被调用时 self 将在构造 MethodType 时作为绑定(bind)到 my_c 的实例接收。


性能比较更新:

看起来,在性能方面,所有解决方案都非常相似,以至于在性能方面无关紧要(Kedar's 描述符协议(protocol)的显式使用和我对 MethodType 的使用是等效的,并且最快,但与 functools.partial 的百分比差异是如此之小,以至于在您正在进行的任何有用工作的重压下都无关紧要):

>>> # ... define C as per OP
>>> def f_monkey_patched(self, a): # Reduce argument count to reduce unrelated overhead
... pass

>>> from types import MethodType
>>> from functools import partial
>>> partial_c, mtype_c, desc_c = C(), C(), C()
>>> partial_c.f = partial(f_monkey_patched, partial_c)
>>> mtype_c.f = MethodType(f_monkey_patched, mtype_c)
>>> desc_c.f = f_monkey_patched.__get__(desc_c, C)
>>> %%timeit x = partial_c # Swapping in partial_c, mtype_c or desc_c
... x.f(1)
...

我什至不打算为 IPython %%timeit 魔术提供准确的计时输出,因为它在不同的运行中会有所不同,即使在不涉及 CPU 节流的桌面上也是如此。我可以肯定地说的是 partial 确实慢了一点,但只慢了 ~1 ns(其他两个通常在 56-56.5 ns 内运行,partial 解决方案通常需要 56.5-57.5),并且需要大量的无关内容的配对(例如,从 %timeit 切换到从全局范围读取名称导致 dict 查找缓存到 %%timeit 中的本地名称以使用简单的数组查找)甚至获得可预测的差异。

重点是,它们中的任何一个都可以在性能方面发挥作用。我个人会推荐我的 MethodType 或 Kedar 对描述符协议(protocol)方法的显式使用(它们在最终结果 AFAICT 中是相同的;两者都产生相同的绑定(bind)方法类),无论哪个看起来更漂亮,因为它意味着绑定(bind)方法实际上是一个绑定(bind)方法(所以你可以提取.__self__.__func__就像你在任何绑定(bind)方法上构造正常方式,其中 partial 要求您切换到 .args[0].func 以获得相同的信息)。

关于python - Python 中的猴子修补类和实例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73545390/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com