Skip to content
Advertisement

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

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. 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. Is there a way to override the function not just in the scope where I define it, but also in all called 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.

NOTE2: I recognize this is bad practice and should not be done as it goes against a number of best-practice coding principles.

Advertisement

Answer

You can select the scope in which you monkey patch your function. Python evaluates in LEGB order:

  • 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. 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.

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

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. However, knowing how to monkey-patch properly is an important tool when it comes to unit testing, among other things.

User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement