函数和生成器的实时装饰器 [英] real-time decorator for functions and generators
问题描述
我遇到一种情况,需要挂接某些函数,以便可以检查返回值并跟踪它们.这对于跟踪例如方法/函数返回的值的运行平均值很有用.但是,这些方法/函数也可以是生成器.
I have a situation in which I need to hook certain functions so that I can inspect the return values and track them. This is useful for tracking for example running averages of values returned by methods/functions. However, these methods/function can also be generators.
但是,如果我没有记错的话,python在解析时会检测到生成器,并且在运行时调用该函数时,它总是会返回一个生成器.因此,我不能简单地做类似的事情:
However, if i'm not wrong, python detects generators when parsing and when the function is called at runtime it always returns a generator. Thus I can't simply do something like:
import types
def decorator(func):
average = None # assume average can be accessed by other means
def wrap(*args, **kwargs):
nonlocal average
ret_value = func(*args, **kwargs)
#if False wrap is still a generator
if isinstance(ret_value, types.GeneratorType):
for value in ret_value:
# update average
yield value
else:
# update average
return ret_value # ret_value can't ever be fetched
return wrap
此装饰器中的yield
是必需的,因为在调用者迭代此装饰的生成器时(即 实时" ),我需要跟踪这些值.意思是,我不能简单地将for
和yield
替换为values = list(ret_value)
,然后返回values
. (即)如果func
是生成器,则需要在装饰后保持其为生成器.但是,如果func
是纯函数/方法,即使执行了else
,wrap
仍然保留为生成器.这意味着ret_value
永远无法获取.
And yield
ing in this decorator is necessary, since I need to track the values as the caller iterates this decorated generator (i.e. "real-time"). Meaning, I can't simply replace the for
and yield
with values = list(ret_value)
, and return values
. (i.e.) If the func
is a generator it needs to remain a generator once decorated. But if func
is a pure function/method, even if the else
is executed, wrap
still remains a generator. Meaning, the ret_value
can't ever be fetched.
使用这种发电机的一个玩具示例是:
A toy example of using such a generator would be:
@decorated
def some_gen(some_list):
for _ in range(10):
if some_list[0] % 2 == 0:
yield 1
else:
yield 0
def caller():
some_list = [0]
for i in some_gen(some_list):
print(i)
some_list[0] += 1 # changes what some_gen yields
对于玩具示例,可能有更简单的解决方案,但这只是为了证明这一点.
For the toy example, there may be simpler solutions, but it's just to prove a point.
也许我缺少明显的东西,但是我做了一些研究却没有找到任何东西.我找到的最接近的东西是此.但是,这仍然不能让装饰器检查包装的生成器(只是第一个)返回的每个值.这是否有解决方案,还是需要两种类型的装饰器(一种用于功能,一种用于装饰器)?
Maybe I'm missing something obvious, but I did some research and didn't find anything. The closest thing I found was this. However, that still doesn't let the decorator inspect every value returned by the wrapped generator (just the first). Does this have a solution, or are two types of decorators (one for functions and one for decorators) necessary?
推荐答案
我意识到的解决方法是:
Once solution I realized is:
def as_generator(gen, avg_update):
for i in gen:
avg_update(i)
yield i
import types
def decorator(func):
average = None # assume average can be accessed by other means
def wrap(*args, **kwargs):
def avg_update(ret_value):
nonlocal average
#update average
pass
ret_value = func(*args, **kwargs)
#if False wrap is still a generator
if isinstance(ret_value, types.GeneratorType):
return as_generator(ret_value, avg_update)
else:
avg_update(ret_value)
return ret_value # ret_value can't ever be fetched
return wrap
我不知道这是不是唯一的一个,或者是否存在没有为生成器案例创建单独功能的一个.
I don't know if this is the only one, or if there exists one without making a separate function for the generator case.
这篇关于函数和生成器的实时装饰器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!