декоратор в реальном времени для функций и генераторов - PullRequest
0 голосов
/ 08 декабря 2018

У меня есть ситуация, в которой мне нужно подключить определенные функции, чтобы я мог проверять возвращаемые значения и отслеживать их.Это полезно для отслеживания, например, скользящих средних значений, возвращаемых методами / функциями.Однако эти методы / функции также могут быть генераторами.

Однако, если я не ошибаюсь, 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 в этом декораторе необходимо, так как мне нужно отслеживать значения, когда вызывающая программа выполняет итерацию этого декорированного генератора (то есть "в режиме реального времени" ).То есть я не могу просто заменить for и yield на values = list(ret_value) и вернуть values.(т.е.) Если func является генератором, он должен оставаться генератором после декорирования.Но если func является чистой функцией / методом, даже если else выполняется, wrap все еще остается генератором.Это означает, что 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

Для примера с игрушкой могут быть более простые решения,но это просто для того, чтобы доказать свою точку зрения.

Может быть, я упускаю что-то очевидное, но я провел некоторое исследование и ничего не нашел.Самым близким, что я нашел, было это .Однако это все еще не позволяет декоратору проверять каждое значение, возвращаемое упакованным генератором (только первое).Есть ли у этого решение, или необходимы два типа декораторов (один для функций и один для декораторов)?

1 Ответ

0 голосов
/ 09 декабря 2018

Когда-то решение, которое я понял, это:

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

Я не знаю, единственное ли это, или существует, не выполняя отдельную функцию для корпуса генератора.

...