如何创建可在 python 中选取的动态装饰(包装)函数?

How to create dynamically decorated (wrapped) functions that are pickable in python?

基本上,如何使第二个工作。用例是包装函数和陷阱exceptions/add 计时等

import concurrent.futures
import functools
def with_print(func):
    """ Decorate a function to print its arguments.
    """
    @functools.wraps(func)
    def my_func(*args, **kwargs):
        print("LOOK", args, kwargs)
        return func(*args, **kwargs)
    return my_func

def f():
    print('f called')

g = with_print(f)

executor = concurrent.futures.ProcessPoolExecutor(max_workers=10)

tasks = [f for x in range(10)]
fut = list()
for task in tasks:
    fut.append(executor.submit(task))
res = [x.result() for x in fut]
print(res)


# THIS ONE FAILS
tasks = [g for x in range(10)]
fut = list()
for task in tasks:
    fut.append(executor.submit(task))
res = [x.result() for x in fut]
print(res)

错误是:

_pickle.PicklingError: 无法腌制:它与 main.f

不是同一个对象

定义装饰器函数外的内部函数,利用functools.partial是pickleable的事实:

import concurrent.futures
import functools


def inner_with_print(*args, func=None, **kwargs):
    print("LOOK", args, kwargs)
    return func(*args, **kwargs)


def with_print(func):
    result_func = functools.partial(inner_with_print, func=func)
    return functools.wraps(func)(result_func)


def f(arg, kwarg):
    print("f called")


g = with_print(f)

if __name__ == "__main__":
    with concurrent.futures.ProcessPoolExecutor(max_workers=10) as executor:
        [executor.submit(g, i, kwarg=i) for i in range(10)]

# LOOK (0,) {'kwarg': 0}
# called
# LOOK (1,) {'kwarg': 1}
# f called
# ...

已正确复制元数据:

print(vars(g))
# {'__module__': '__main__', '__name__': 'f', '__qualname__': 'f', '__doc__': None, '__annotations__': {}, '__wrapped__': <function f at 0x7f3251f01430>}

编辑:上面的工作,但看起来这不是动态装饰器的问题。如果将 g = with_print(f) 更改为 f = with_print(f),一切正常。看起来 pickle 动态地寻找 __main__.f 并找到 g,这是 functools.wraps 魔法的结果。

EDIT2:functools.wraps 魔术实际上是将 __qualname__ 设置为 f。如果您将其设置回 g 那么它可以正常工作:

g.__qualname__ = "g"

看起来这一切都在发生,因为你使用了wraps,而且还把函数名改成了g