вызывая выход для генератора в другой функции - PullRequest
9 голосов
/ 19 июня 2010

предположим, у меня есть какой-то объект менеджера. API этого объекта имеет функцию main_hook, которая получает в качестве аргумента другую функцию f и выполняет заданный f в цикле, делая некоторые вещи между каждой итерацией:

def main_hook(self,f):
    while (self.shouldContinue()):
        #do some preparations
        f(self)
        #do some tear down

Теперь у меня также есть (точнее, хотел бы иметь ) функцию stop_and_do_stuff, которая однажды вызвана, останавливает main_hook мертвую на своих треках, возвращает элемент управления в зависимости от того, какая функция называется main_hook, и после того, как этот функционал завершит свою работу, верните управление main_hook и продолжайте. В основном результат будет таким же, как и

def main_hook(self,f):
    while (self.shouldContinue()):
        #do some preparations
        yield
        #do some tear down

За исключением того, что вместо yield я хочу позвонить на f(), в то же время давая f возможность звонить self.stop_and_do_stuff()

Я не могу обойти это, сделав f также генератором по 2 причинам:

1. f не является частью моего API - он предоставлен мне пользователем, использующим мою библиотеку

2. Даже если бы он мог попросить его использовать yield, место в коде, в котором ему нужно будет вызывать stop_and_do_stuff, не будет находиться непосредственно внутри f, скорее в каком-то месте в стеке функций, который будет внутри f(), но не непосредственно в нем, например

def h(manager):
    #do stuff
    if should stop:
        manager.stop_and_do_stuff()
    #do more stuff
def g(manager):
    #some stuff
    if should stop:
        manager.stop_and_do_stuff()
    #more stuff
    if should stop again:
        manager.stop_and_do_stuff()  
    if should call h:
        h()
def f(manager):
    g(manager)

, поэтому, если я решу сделать f генератором, мне также нужно сделать g генератором и также h, иначе этот трюк не сработает.

Есть ли какое-нибудь решение для всего этого? может я пытаюсь решить это неправильно?

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

EDIT

Может быть pep 342 это решение?

Ответы [ 5 ]

3 голосов
/ 29 сентября 2014

Мой предыдущий ответ описывает, как это сделать в Python2, что очень уродливо. Но теперь я наткнулся на PEP 380 : синтаксис для делегирования субгенератору. Это именно то, что вы просите. Единственная проблема заключается в том, что для этого требуется Python3. Но это не должно быть проблемой.

Вот как это работает:

def worker():
    yield 1
    yield 2
    return 3

def main():
    yield 0
    value = yield from worker()
    print('returned %d' % value)
    yield 4

for m in main():
    print('generator yields %d' % m)

Результат этого:

generator yields 0
generator yields 1
generator yields 2
returned 3
generator yields 4

Исключения передаются так, как вы ожидаете.

2 голосов
/ 21 июня 2010

Полагаю, мне также следует добавить ответ с другой точки зрения, то есть не пытаться объяснить, как вы можете достичь того, что мы можем понять о том, что вы пытаетесь сделать, но почему yield определенно не может сработать.

Когда функция содержит ключевое слово yield, она сильно модифицируется.Это все еще вызываемая, но уже не нормальная функция: она становится фабрикой, возвращающей итератор.

С точки зрения вызывающей стороны нет никакой разницы между тремя реализациями ниже (за исключением того, что yieldодин намного проще).

##########################################
print "Function iterator using yield",

def gen():
    for x in range(0, 10):
        yield x

f = gen()
try:
    while True:
        print f.next(),
except StopIteration:
    pass

for x in gen():
    print x,

print

#########################################
print "Class iterator defining iter and next",

class gen2(object):

    def __init__(self):
        self.index = 0;
        self.limit = 10;

    def __iter__(self):
        return self

    def next(self):
        if self.index >= self.limit:
            raise StopIteration
        self.index += 1;
        return self.index - 1;


f = gen2()
try:
    while True:
        print f.next(),
except StopIteration:
    pass

for x in gen2():
    print x,
print

#########################################
print "Function iterator using iter() and sentinel",
def gen3():
    def g3():
        if g3.index is None:
            g3.index = 0
        g3.index += 1;
        return g3.index - 1

    g3.index = None
    return iter(g3, 10)

f = gen3()
try:
    while True:
        print f.next(),
except StopIteration:
    pass

for x in gen3():
    print x,
print

Тогда вы должны понимать, что yield - это не столько поток управления, сколько сохранение контекста вызова внутри переменных.Как только вы поймете, что вам нужно решить, действительно ли API main_loop хочет предоставить итератор вызывающей стороне.Тогда, если так, если f может зацикливаться, он также должен быть итератором (и должен быть цикл вокруг вызовов f (), как показано ниже).

def main_hook(self,f):
    while (self.shouldContinue()):
        #do some preparations
        for v in f(self):
            yield v
        #do some tear down

Но вам не важно, должен ли f () вызывать внутренние функции g () и т. Д. Это совершенно не имеет значения.Вы предоставляете библиотеку, и ваша пользовательская проблема - вызывать с соответствующей итерацией.Если вы считаете, что ваш пользователь lib не сможет, вам придется изменить общий дизайн.

Надеюсь, это поможет.

1 голос
/ 19 июня 2010

Я тоже не понимаю всего (как выглядит вызывающий main_hook?), Но я бы сказал, бросить исключение StopNow, когда вы должны остановиться, точно так же, как вы должны бросить StopIteration, когда ваш генератор закончен.

вот как я понял вещь, а также то, что я бы сделал.

class StopNow(Exception):
    pass

def main_hook(self,f):
    got_stop_now_exc = False
    while (!got_stop_now_exc and self.shouldContinue()):
        #do some preparations
        try:
             f(self)
        except StopNow:
             got_stop_now_exc = True

        #do some compulsary tear down, exception or not

def stop_and_do_stuff()
    raise StopNow()
def my_f():
    if needed:
        stop_and_do_stuff()

def the_main_hook_caller():
    while i_should:
        managerthingie.main_hook(my_f)
        do_stuff()
0 голосов
/ 20 июня 2010

Поведение, которое вы описываете, похоже на простой вызов функции.Как показано ниже.

def f(manager):
    print("Entering f")
    manager.stop_and_do_stuff()
    print("Exiting f")

class Manager(Object):
    def shouldContinue(self):
        return True

    def stop_and_do_stuff(self):
        print("Manager stop and do stuff")

    def main_hook(self,f):
        while self.shouldContinue()
            print("Manager Setup")
            f(self)
            print("Manager Tear Down")

Нет проблем, если f() предоставлен другим пользователем, если stop_and_do_stuff вызывается из некоторой внутренней функции.Если вы также хотите, чтобы менеджер мог откатить стек от stop_and_do_stuff и в некоторых случаях действительно завершить работу, не проблема.Просто вызовите какое-то исключение из него, и вы поймаете его из main_hook или верхнего кода.

Вы должны иметь возможность делать изнутри stop_and_and_do_stuff() все, что вы хотите сделать из вызывающей функции main hook.Если нет, вы должны объяснить, почему.

Что неясно в этом вопросе, так это то, что происходит на стороне вызывающей функции main_hook () и почему вы хотели бы иметь возможность выйти из цикла main_hook, но не совсем.Либо вызывающий main_loop ожидает генератор, либо нет.Вам нужно объяснить эту часть, если вы хотите получить разумный ответ (некоторые контекстные данные также были бы хорошими, если вы действительно объясните WTF, который вы пытаетесь сделать, и ваши реальные ограничения - вы сказали, что f предоставлен другим пользователем и main_hookесть в lib, что из вызывающего main_hook? - наверняка есть общеизвестные обычные решения).

0 голосов
/ 19 июня 2010

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

Из моего частичного понимания, почему бы тебе не сделать что-то подобное

def main_hook(self,f):
    while (self.shouldContinue()):
        #do some preparations
        stop_and_do_stuff = f(self)
        if stop_and_do_stuff :
            yield

        #do some tear down

Таким образом, в основном f возвращает флаг, чтобы остановить или нет, и если он говорит "стоп", мы уступаем функции, которая вызвала main_hook, и эта функция может продолжиться после выполнения некоторых вещей

, например

class A(object):
    def main_hook(self,f):
        while (self.shouldContinue()):
            #do some preparations
            stop = f(self)
            if stop:
                yield

            #do some tear down

    def shouldContinue(self):
        return True

def f(a):
    return True

a = A()
for x in a.main_hook(f):
    print x
...