Python:将钩子注册到现有代码以在调用函数时获得控制

问题描述

我想在 Python 中调用函数时获得控制权(以执行一些先发制人的任务)而不修改源程序,例如,在调用 test()

def test(i : int,s: str) -> int:
    pass

我想要一个函数 myobserver 被调用,并且有一些方法来检查(甚至可能修改?!)参数?把它想象成一个迷你调试器,例如,将日志添加到不能/不应该被修改的现有程序中?

def myobserver(handle)
    name = get_name(handle)
    for n,arg in enumerate(get_arg_iterator(handle)):
        print("Argument {n} of function {name}: {arg}")

ETA:我不是在寻找传统的 decorator,因为添加装饰器需要更改源代码。 (从这个意义上说,装饰器比添加打印更好,但仍然相似,因为它们需要更改源。)

解决方法

您正在寻找 python 装饰器

from functools import wraps
def debugger(func):
    @wraps(func)
    def with_logging(*args,**kwargs):
        print('"'+func.__name__+'({},{})"'.format(*args,**kwargs)+" was invoked")
        # -------------------
        # Your logic here
        # -------------------
        return func(*args,**kwargs)
    return with_logging

@debugger
def test(i : int,s: str) -> int:
    print('We are in test',i,s)

test(10,'hello')

编辑

由于上面提到的装饰器方法干扰了源代码(必须应用 @ 装饰器),我提出以下建议:

# This is source code to observe,should not be _touched_!
class SourceCode:
    def __init__(self,label):
        self.label = label

    def test1(self,s):
        print('For object labeled {},we are in {} with param {},{}'.format(self.label,'test1',s))

    def test2(self,k):
        print('For object labeled {},we are in {} with param {}'.format(self.label,'test2',k))

我的建议是在编写钩子时执行一些手动操作,我不确定这是否可行(只是我突然想到,因此添加):

from functools import wraps

# the following is pretty much syntactic and generic
def hook(exist_func,debugger):
    @wraps(exist_func)
    def run(*args,**kwargs):
        return debugger(exist_func,*args,**kwargs)
    return run

# here goes your debugger
def myobserver(orig_func,**kwargs):
    # -----------------------
    # Your logic goes here
    # -----------------------
    print('Inside my debugger')
    return orig_func(*args,**kwargs)


# ---------------------------------
obj = SourceCode('Test')

# making the wrapper ready to receive
no_iterference_hook1 = hook(obj.test1,myobserver)
no_iterference_hook2 = hook(obj.test2,myobserver)

# call your debugger on the function of your choice
no_iterference_hook1(10,'hello')
no_iterference_hook2('Another')