如何将装饰器应用于 python 中的子例程

How to apply a decorator to subroutines in python

我想修改我的几个函数的行为方式,所以想到了装饰器的使用。例如,假设我有一个批量数据获取功能 takeData(s):

def takeData(s):
    takeDataSet_1(s)
    takeDataSet_2(s)
    .
    .
    .

我可能想做的一件简单的事情是在每次调用 takeDataSet 函数之前更新参数 dict 。这样有效的代码会更像这样:

def takeData(s):
    s = updateParams(s)
    takeDataSet_1(s)
    s = updateParams(s)
    takeDataSet_2(s)
    s = updateParams(s)
    .
    .
    .

有没有一种方法可以用装饰器做到这一点,这样我的代码看起来更像

@takeDataWithUpdatedParams
def takeData(s):
    takeDataSet_1(s)
    takeDataSet_2(s)

有没有办法控制这种装饰器的递归深度?这样如果 takeDataSet_1(s) 有它自己的 s 的子程序可以在它们之间更新,如:

@recursiveUpdateParams
def takeData(s):
    takeDataSet_1(s)
    takeDataSet_2(s)

def takeDataSet_1(s):
    takeData_a(s)
    takeData_b(s)

作为

执行
def takeData(s):
    s = updateParams(s)
    takeDataSet_1(s)
    s = updateParams(s)
    takeDataSet_2(s)
    s = updateParams(s)

def takeDataSet_1(s):
    s = updateParams(s)
    takeData_a(s)
    s = updateParams(s)
    takeData_b(s)

没有。装饰器包装一个函数,这意味着它们可以在函数前后添加自己的行为。他们不能像您的示例那样更改函数 "in the middle" 发生的事情(更不用说在该函数调用的其他函数中间发生的事情,如您的递归示例)。

您可以创建一个类似装饰器的函数,它接受您的 takeData_* 函数作为参数并进行更新,因此您可以执行如下操作:

def updateAndCall(func, params):
    s = updateParams(params)
    func(s)

def takeData(s):
    updateAndCall(takeData_1, s)
    updateAndCall(takeData_2, s)

但是,这是否有用取决于各种功能之间的交互。特别是,使用这种方法,每个 "update" 都发生在原始 s 上;更新不会与更新的 s 一起为第二次调用再次更新,等等

非常有趣的问题。为此,您需要深入研究函数对象(无论如何,没有 evalexecast 东西)。

def create_closure(objs):
    creat_cell = lambda x: (lambda: x).__closure__[0]
    return tuple(create_cell(obj) for obj in objs)

def hijack(mapper):
    from types import FunctionType
    def decorator(f):
        globals_ = {k: mapper(v) for k, v in f.__globals__.items()}
        closure_ = f.__closure__
        if closure_:
            closure_ = create_closure(i.cell_contents for i in closure_)
        return (lambda *arg, **kwarg:
                FunctionType(f.__code__, globals_, f.__name__,
                             f.__defaults__, closure_)(*arg, **kwarg))
    return decorator

测试:

x = 'x'
y = 'y'
@hijack(lambda obj: 'hijacked!' if obj is x else obj)
def f():
    return (x, y)
f()

输出:

('hijacked!', 'y')

终于解决了原问题:

x = lambda: 'x()'
y = lambda: 'y()'
mydecorator = lambda f: lambda *arg, **kwarg: f(*arg, **kwarg) + ' decorated!'
targets = {id(x): mydecorator(x)}
def mapper(obj):
    if id(obj) in targets:
        return targets[id(obj)]
    else:
        return obj
@hijack(mapper)
def f():
    return (x(), y())
f()

输出:

('x() decorated!', 'y()')