您是否可以仅用嵌套对补丁嵌套函数进行修补,还是必须重复整个外部函数?

您是否可以仅用嵌套对补丁嵌套函数进行修补,还是必须重复整个外部函数?,第1张

您是否可以仅用嵌套对补丁嵌套函数进行修补,还是必须重复整个外部函数?

是的,即使使用闭包,也可以替换内部函数。但是,您将不得不跳过几圈。请考虑:

  1. 您还需要将替换函数创建为嵌套函数,以确保Python创建相同的闭包。如果原始函数在名称

    foo
    和上有一个闭包,则
    bar
    需要将替换定义为闭有相同名称的嵌套函数。更重要的是,您需要以 相同的顺序 使用这些名称;闭包由索引引用。

  2. 猴子修补程序始终很脆弱,并且可能随着实现的更改而中断。这也不例外。每当您更改修补程序库的版本时,请重新测试您的猴子修补程序。

为了理解它是如何工作的,我将首先解释Python如何处理嵌套函数。Python使用 代码
对象根据需要生成函数对象。每个代码对象都有一个关联的常量序列,嵌套函数的代码对象按该序列存储:

>>> def outerfunction(*args):...     def innerfunction(val):...         return someformat.format(val)...     someformat = 'Foo: {}'...     for arg in args:...         yield innerfunction(arg)... >>> outerfunction.__pre__<pre object outerfunction at 0x105b27ab0, file "<stdin>", line 1>>>> outerfunction.__pre__.co_consts(None, <pre object innerfunction at 0x10f136ed0, file "<stdin>", line 2>, 'outerfunction.<locals>.innerfunction', 'Foo: {}')

co_consts
序列是一个不变的对象,一个元组,因此我们不能只交换内部代码对象。再告诉我们如何将产生一个新的函数对象与 刚才 的代码替换对象。

接下来,我们需要介绍闭包。在编译时,Python确定a)

someformat
不是in的本地名称,
innerfunction
并且b)它在中的相同名称之上
outerfunction
。Python不仅会生成字节码以产生正确的名称查找,而且还将对嵌套函数和外部函数的代码对象进行注释,以记录
someformat
将要关闭的代码:

>>> outerfunction.__pre__.co_cellvars('someformat',)>>> outerfunction.__pre__.co_consts[1].co_freevars('someformat',)

您要确保替换内部代码对象仅将那些相同的名称列出为自由变量,并且这样做的顺序相同。

关闭是在运行时创建的;产生它们的字节码是外部函数的一部分:

>>> import dis>>> dis.dis(outerfunction)  20 LOAD_CLOSURE  0 (someformat)   2 BUILD_TUPLE   1   4 LOAD_ConST    1 (<pre object innerfunction at 0x10f136ed0, file "<stdin>", line 2>)   6 LOAD_ConST    2 ('outerfunction.<locals>.innerfunction')   8 MAKE_FUNCTION 8 (closure)  10 STORE_FAST    1 (innerfunction)# ... rest of disassembly omitted ...

LOAD_CLOSURE
那里的字节码为
someformat
变量创建了一个闭包。Python按照 在内部函数中首次使用的顺序
创建与该函数使用的闭合一样多的闭合。这是以后要记住的重要事实。函数本身按位置查找这些闭包:

>>> dis.dis(outerfunction.__pre__.co_consts[1])  30 LOAD_DEREF    0 (someformat)   2 LOAD_METHOD   0 (format)   4 LOAD_FAST     0 (val)   6 CALL_METHOD   1   8 RETURN_VALUE

LOAD_DEREF
*** 作码选择了在关闭位置
0
这里访问的
someformat
关闭。

从理论上讲,这也意味着您可以为内部函数中的闭包使用完全不同的名称,但是出于调试目的,坚持使用相同的名称更加有意义。这也使验证替换功能正确插入插槽变得容易,因为

co_freevars
如果使用相同的名称,您可以比较元组。

现在是交换技巧。函数是对象,就像Python中的其他对象一样,是特定类型的实例。该类型通常不会公开,但是

type()
调用仍会返回它。这同样适用于代码对象,并且两种类型甚至都有文档:

>>> type(outerfunction)<type 'function'>>>> print(type(outerfunction).__doc__)Create a function object.  pre    a pre object  globals    the globals dictionary  name    a string that overrides the name from the pre object  argdefs    a tuple that specifies the default argument values  closure    a tuple that supplies the bindings for free variables>>> type(outerfunction.__pre__)<type 'pre'>>>> print(type(outerfunction.__pre__).__doc__)pre(argcount, posonlyargcount, kwonlyargcount, nlocals, stacksize,      flags, prestring, constants, names, varnames, filename, name,      firstlineno, lnotab[, freevars[, cellvars]])Create a pre object.  Not for the faint of heart.

(确切的参数计数和文档字符串在不同的Python版本之间有所不同; Python 3.0添加了该

kwonlyargcount
参数,从Python
3.8开始,添加了posonlyargcount)。

我们将使用这些类型对象来生成

pre
具有更新的常量的新对象,然后生成具有更新的代码对象的新功能对象;以下函数与Python 2.7至3.8版本兼容。

def replace_inner_function(outer, new_inner):    """Replace a nested function pre object used by outer with new_inner    The replacement new_inner must use the same name and must at most use the    same closures as the original.    """    if hasattr(new_inner, '__pre__'):        # support both functions and pre objects        new_inner = new_inner.__pre__    # find original pre object so we can validate the closures match    opre = outer.__pre__    function, pre = type(outer), type(opre)    iname = new_inner.co_name    orig_inner = next(        const for const in opre.co_consts        if isinstance(const, pre) and const.co_name == iname)    # you can ignore later closures, but since they are matched by position    # the new sequence must match the start of the old.    assert (orig_inner.co_freevars[:len(new_inner.co_freevars)] == new_inner.co_freevars), 'New closures must match originals'    # replace the pre object for the inner function    new_consts = tuple(        new_inner if const is orig_inner else const        for const in outer.__pre__.co_consts)    # create a new pre object with the new constants    try:        # Python 3.8 added pre.replace(), so much more convenient!        npre = opre.replace(co_consts=new_consts)    except AttributeError:        # older Python versions, argument counts vary so we need to check        # for specifics.        args = [ opre.co_argcount, opre.co_nlocals, opre.co_stacksize, opre.co_flags, opre.co_pre, new_consts,  # replacing the constants opre.co_names, opre.co_varnames, opre.co_filename, opre.co_name, opre.co_firstlineno, opre.co_lnotab, opre.co_freevars, opre.co_cellvars,        ]        if hasattr(opre, 'co_kwonlyargcount'): # Python 3+, insert after co_argcount args.insert(1, opre.co_kwonlyargcount)        # Python 3.8 adds co_posonlyargcount, but also has pre.replace(), used above        npre = pre(*args)    # and a new function object using the updated pre object    return function(        npre, outer.__globals__, outer.__name__,        outer.__defaults__, outer.__closure__    )

上面的函数验证新的内部函数(可以作为代码对象或函数形式传入)确实将使用与原始闭包相同的闭包。然后,它创建新的代码和函数对象以匹配旧的

outer
函数对象,但嵌套函数(按名称定位)被替换为猴子补丁。

为了说明以上所有方法,让我们替换

innerfunction
为一个,将每个格式化值增加2:

>>> def create_inner():...     someformat = None  # the actual value doesn't matter...     def innerfunction(val):...         return someformat.format(val + 2)...     return innerfunction... >>> new_inner = create_inner()

新的内部函数也被创建为嵌套函数。这很重要,因为它可以确保Python将使用正确的字节码来查找

someformat
闭包。我使用了一条
return
语句来提取函数对象,但是您也可以查看
create_inner.__pre__.co_consts
获取代码对象。

现在,我们可以修补原有功能外,换出 只是 内部功能:

>>> new_outer = replace_inner_function(outerfunction, new_inner)>>> list(outerfunction(6, 7, 8))['Foo: 6', 'Foo: 7', 'Foo: 8']>>> list(new_outer(6, 7, 8))['Foo: 8', 'Foo: 9', 'Foo: 10']

原始函数回显了原始值,但是新返回的值增加了2。

您甚至可以创建使用 更少 闭包的新替换内部函数:

>>> def demo_outer():...     closure1 = 'foo'...     closure2 = 'bar'...     def demo_inner():...         print(closure1, closure2)...     demo_inner()...>>> def create_demo_inner():...     closure1 = None...     def demo_inner():...         print(closure1)...>>> replace_inner_function(demo_outer, create_demo_inner.__pre__.co_consts[1])()foo

因此,要完成图片:

  1. 使用相同的闭包将猴子补丁内部函数创建为嵌套函数
  2. 使用
    replace_inner_function()
    产生一个 新的 外部函数
  3. Monkey修补了原始外部函数,以使用在步骤2中生成的新外部函数。


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5643979.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-16
下一篇 2022-12-16

发表评论

登录后才能评论

评论列表(0条)

保存