Python 作用域中所有子函数中的重载函数

Python 作用域中所有子函数中的重载函数,python,python-3.x,Python,Python 3.x,假设我想用自定义版本重载一个标准函数,我只需编写 original\u function\u name=customized\u function 例如,我可以做: def custom_print(s): print(f'!!{s}!!') def fun1(n): print = custom_print print(n) fun1(1) >> !!1!! 但是,此覆盖仅在fun1内有效。如果我这样做 def custom_print(s):

假设我想用自定义版本重载一个标准函数,我只需编写

original\u function\u name=customized\u function

例如,我可以做:

def custom_print(s):
    print(f'!!{s}!!')

def fun1(n):
    print = custom_print
    print(n)

fun1(1)

>> !!1!!
但是,此覆盖仅在
fun1
内有效。如果我这样做

def custom_print(s):
    print(f'!!{s}!!')

def fun1(n):
    print = custom_print
    fun2(n)

def fun2(n):
    print(n)

if __name__ == '__main__':
    fun1(1)

>>1
自定义
print
功能未(清楚地)传递到使用标准
print
功能的
fun2
。是否有一种方法不仅在我定义的范围内重写函数,而且在所有调用的函数中重写函数

注意:这是一个最小的示例,在实际代码中有几个从不同模块导入的嵌套函数,我希望在所有这些模块中重写该函数,而不逐个修改它们

注2:我承认这是一种不好的做法,不应该这样做,因为它违背了许多最佳实践编码原则

简易修复:

def custom_print(s):
    print(f'!!{s}!!')

def fun1(n):
    print = custom_print
    fun2(n, print)

def fun2(n, print = print):
    print(n)

fun1(1)
是否有一种方法不仅在我定义的范围内重写函数,而且在所有调用的函数中重写函数

由于Python没有动态作用域,唯一的方法是交换
打印
内置内容本身,然后再恢复它这是非常不安全的:在多线程环境中,或者当涉及协同程序(生成器)时,因为它们将看到整个交换的替换功能

此外,这里显示的代码显然不起作用:您不能使用替换的打印,因为它已被替换。而且与你要替换的函数的正确签名不完全匹配。。。这可能是个坏主意。但这并不难解决:print的签名只是
*args,end='\n',sep='',file=sys.stdout,flush=False
,您可以
file.write(…)

然后,您只需更新
\uuuuuuuuuuuuuuuuuuuuuuuu
dict(这应该在所有作用域中都可用):

#swapper.py
进口内置设备
导入上下文库
导入系统
def _my_print(*参数,end='\n',sep='',file=sys.stdout,flush=False):
file.write('XXX')
file.write(sep.join(map(str,args)))
file.write(结束)
如果齐平:
file.flush()文件
@contextlib.contextmanager
def swap():
旧的印刷品
builtins.print=\u我的\u打印
尝试:
产量
最后:
builtins.print=旧打印

您可以选择修补函数的范围。Python按LEGB顺序计算:

  • 本地(您显示的)
  • 封闭(您提到的嵌套函数)
  • 全局(模块)
  • 内置(实际上只是一个特殊模块)
您可以在任何级别执行
print=monkey\u print
。只要确保它遵循程序所有其他部分所期望的相同界面:
print(*args,**kwargs)
通常是安全的

以下是一些例子:

from sys import stdout
import builtins

def bprint(*args, **kwargs):
    kwargs.get('file', stdout).write('Builtin!: ' + kwargs.get('sep', ' ').join(map(str, args)) + kwargs.get('end', '\n'))

def gprint(*args, **kwargs):
    # Otherwise this will be infinite recursion
    builtins.print('Global! ', *args, **kwargs)

def eprint(*args, **kwargs):
    print('Enclosing! ', *args, **kwargs)

def lprint(*args, **kwargs):
    print('Local! ', *args, **kwargs)

builtins.print = bprint
print = gprint

def decorator(func):
    def wrapper(*args, **kwargs):
        print(*args, **kwargs)
        return func(*args, **kwargs)
    print = eprint
    return wrapper

@decorator
def func(*args, **kwargs):
    print = lprint
    print(*args, **kwargs)

print('Example complete')
func('Print me next!')
此脚本的输出将是

Builtin!: Global!  Example complete
Builtin!: Global!  Enclosing!  Print me next!
Builtin!: Global!  Local!  Print me next!

这是个好主意吗?当涉及到替换像打印这样无处不在的功能时,可能不会。然而,当涉及到单元测试时,知道如何正确地进行monkey patch是一个重要的工具。

在任何函数之外进行。顺便说一句,这是违反规则的。我建议您重构代码,这样您就不必这样做。我同意这不是最佳实践。这主要是一个研究问题,看看能否做到。我理解这是一种黑客行为,应该加以劝阻。@rdas在任何函数之外这样做都不起作用,如果
fun2
是从另一个文件导入的。好的,先生,@LucaAmerioSo现在OP需要改变所有函数的签名,而不是给它们添加一行。好处是什么?这需要修改
fun2
以及堆栈中的任何其他函数以接收额外的输入。不幸的是,这不是我想要的,但是谢谢你的贡献。不,你可以使用非本地声明。不是在这种情况下,但你应该检查它。请给我们看整个代码,而不是这个基本的example@LucaAmerio. 我认为这根本不是答案。这不是你通常想要做的事情,但我已经展示了如何去做。印刷品没那么特别。
Builtin!: Global!  Example complete
Builtin!: Global!  Enclosing!  Print me next!
Builtin!: Global!  Local!  Print me next!