如何创建可在 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
。
基本上,如何使第二个工作。用例是包装函数和陷阱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
。