函数和生成器的实时装饰器

时间:2018-12-08 20:51:19

标签: python-3.x generator python-decorators

我遇到一种情况,需要挂接某些函数,以便可以检查返回值并跟踪它们。这对于跟踪例如方法/函数返回的值的运行平均值很有用。但是,这些方法/函数也可以是生成器。

但是,如果我没记错的话,python在解析时会检测到生成器,并且在运行时调用该函数时,它始终会返回生成器。因此,我不能简单地做类似的事情:

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,因为在调用者迭代此装饰的生成器时,我需要跟踪这些值(即 “实时” >)。意思是,我不能简单地将foryield替换为values = list(ret_value)并返回values。 (即)如果func是一个生成器,则需要在装饰后保持其为生成器。但是,如果func是纯函数/方法,即使执行了elsewrap仍然是生成器。意味着ret_value永远无法被获取。

使用这种发电机的一个玩具例子是:

@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

对于玩具示例,可能有更简单的解决方案,但这只是为了证明这一点。

也许我缺少明显的东西,但是我做了一些研究却没有发现任何东西。我找到的最接近的东西是this。但是,这仍然不能让装饰器检查包装的生成器(只是第一个)返回的每个值。这是否有解决方案,还是需要两种类型的装饰器(一种用于功能,一种用于装饰器)?

1 个答案:

答案 0 :(得分:0)

我意识到的解决方法是:

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

我不知道这是不是唯一的一个,或者是否存在没有为生成器案例创建单独功能的一个。