函数和生成器的实时装饰器 [英] real-time decorator for functions and generators

查看:112
本文介绍了函数和生成器的实时装饰器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

And yielding 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆