简体   繁体   English

在 scope 的所有子功能中重载 function

[英]Overload function in all subfunctions in scope

Let's say that I want to overload a standard function with a customized version, I can simply write假设我想用定制版本重载一个标准的function,我可以简单地写

original_function_name = customized_function

For example I can do:例如我可以这样做:

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

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

fun1(1)

>> !!1!!

However this override is only valid inside fun1 .但是,此覆盖仅在fun1内部有效。 If I do如果我做

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

the custom print function is not passed (clearly) to fun2 which uses the standard print function.自定义print function 未(清楚地)传递给使用标准print function 的fun2 Is there a way to override the function not just in the scope where I define it, but also in all called function?有没有办法覆盖 function 不仅在我定义它的 scope 中,而且在所有称为 function 中?

NOTE: This is a minimal example, in the real code there are several nested functions imported from different modules and I'd like to override the function in all of them without modifying them one by one.注意:这是一个最小的示例,在实际代码中,有几个从不同模块导入的嵌套函数,我想在所有这些函数中覆盖 function 而不一一修改它们。

NOTE2: I recognize this is bad practice and should not be done as it goes against a number of best-practice coding principles .注意 2:我承认这是不好的做法,不应该这样做,因为它违反了许多最佳实践编码原则

Is there a way to override the function not just in the scope where I define it, but also in all called function?有没有办法覆盖 function 不仅在我定义它的 scope 中,而且在所有称为 function 中?

Since Python does not have dynamic scopes , the only way is to swap the print builtin itself, then restore it afterwards.由于 Python 没有动态范围,唯一的方法是交换print内置本身,然后再恢复它。 This is wildly unsafe : in a multithreaded environment, or when coroutines (generators) are involved, as they will see the replacement function for the entirety of that swap.这是非常不安全的:在多线程环境中,或者当涉及协程(生成器)时,他们将看到整个交换的替换 function。

Furthermore the code you show here will obviously not work: you can't use the replaced print, since it's been replaced.此外,您在此处显示的代码显然不起作用:您不能使用替换的打印,因为它已被替换。 And not precisely matching the proper signature of the function you're replacing... is probably a bad idea.并且不完全匹配您要替换的 function 的正确签名......可能是个坏主意。 That's not hard to solve though: the signature of print is just *args, end='\n', sep=' ', file=sys.stdout, flush=False , and you can file.write(...) with your content.但这并不难解决: print 的签名只是*args, end='\n', sep=' ', file=sys.stdout, flush=False ,您可以file.write(...)你的内容。

Then you can just update the __builtins__ dict (this should be available in all scopes):然后你可以更新__builtins__ dict(这应该在所有范围内都可用):

# swapper.py
import builtins
import contextlib
import sys

def _my_print(*args, end='\n', sep=' ', file=sys.stdout, flush=False):
     file.write('XXX ')
     file.write(sep.join(map(str, args)))
     file.write(end)
     if flush:
          file.flush()

@contextlib.contextmanager
def swap():
     old_print = print
     builtins.print = _my_print
     try:
          yield
     finally:
          builtins.print = old_print
>>> print("ok")
ok
>>> import swapper
>>> print("ok")
ok
>>> with swapper.swap():
...     print('ok')
... 
XXX ok
>>> print('ok')
ok

You can select the scope in which you monkey patch your function.您可以在 select 和 scope 中猴子修补您的 function。 Python evaluates in LEGB order: Python 按 LEGB 顺序计算:

  • Local (which you show)本地(您显示的)
  • Enclosing (the nested functions you mention)封闭(您提到的嵌套函数)
  • Global (module)全局(模块)
  • Builtin (actually just a special module)内置(实际上只是一个特殊的模块)

You can do print = monkey_print at any of those levels.您可以在任何级别执行print = monkey_print Just make sure it follows the same interface that all the other parts of your program expect: print(*args, **kwargs) is usually a safe bet.只需确保它遵循程序的所有其他部分所期望的相同界面: print(*args, **kwargs)通常是一个安全的选择。

Here are some examples:这里有些例子:

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!')

The output of this script will be该脚本的 output 将是

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

Is this a good idea?这是一个好主意吗? Probably not when it comes to replacing a ubiquitous function like print.在更换无处不在的 function (如 print)时可能不会。 However, knowing how to monkey-patch properly is an important tool when it comes to unit testing, among other things.然而,在单元测试等方面,知道如何正确地打补丁是一个重要的工具。

Easy fix:轻松修复:

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)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM