2010-06-19 5 views
8

Предположим, у меня есть некоторый объект-менеджер. 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 мертвых в своих дорожках, возвращает элемент управления в зависимости от того, какой func называется main_hook, и после этого func закончил то, что он делает, получить управление обратно в 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()

Я не могу работа вокруг этого путь п также генератор для 2 причин:

1. f не является частью моей API - это дала мне пользователь, который использует мой LIB

2. Даже если вы можете попросить его использовать доходность, то место в коде, в котором ему нужно будет позвонить 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 это решение?

+0

У меня такое же понимание, как у Анурага, а также думаю (как он), что вы действительно не задавали вопрос, а предоставляли элементы своего собственного решения (это еще не работает). Поэтому лучшее, что вы можете ожидать, это заставить ваше решение работать, а не получать действительно pythonic решение. Кроме того, из того, что я видел в этом вопросе, у меня странное чувство. Мне кажется странным говорить о функциях как о «выполнении чего-то», а не о «возвращении результата», похоже на то, что вы делаете, это в основном какой-то интерактивный побочный эффект. Это ? – kriss

+1

мне непонятно, что если f является чужой функцией lib, то как она может вызвать stop_and_do_stuff в середине, и если она может это сделать, почему она не может уступить? –

+0

@ Anurag-f получает объект 'manager' как аргумент, и он имеет функцию stop_and_do_stuff – olamundo

ответ

2

Я считаю, что я должен добавить ответ с другой точки зрения, то есть не пытаться объяснить, как вы могли бы достичь того, что мы можем понять о том, что вы пытаетесь сделать, но почему 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 

Тогда вы должны понимать, что доходность не столько зависит от потока управления, сколько от поддержания контекста вызова внутри переменных. Как только вы поняли, вам нужно решить, действительно ли 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() должен вызывать внутренние функции г() и т.д. Это совершенно не имеет значения. Вы предоставляете lib, и ваша проблема с пользователем должна быть вызвана с соответствующим итерабельным. Если вы считаете, что ваш пользователь lib не сможет, вам придется изменить общий дизайн.

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

0

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

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

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 и эта функция может продолжаться после некоторых вещей

eg

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 
+0

Это решение далека от желаемого результата. Все дело в том, что 'f' может вызывать' stop_and_do_stuff' посередине, не заканчивая (т. Е. Как будто 'f' имеет в нем' yield'). В вашем решении 'f' должен закончить и вернуть флаг, который заставляет' f' заканчиваться до вызова 'stop_and_do_stuff'. Что касается моего вопроса, если вы можете уточнить, что неясно в моих объяснениях, я был бы рад его прояснить. – olamundo

+0

@noam: часть того, что неясно, является «stop_and_» в «stop_and_do_stuff». Либо вы хотите остановить f(), как возвращение, это то, что понимал Анураг, либо это обычный вызов функции внутри f() (в этом случае это тоже легко, вам просто нужно предоставить функцию для вызова). Похоже, вы хотите вызвать функцию, но не предоставлять ее явно. Обычное использование доходности в python - это другой способ (вернуть контроль), ваше использование больше похоже на доходность рубина (вызов анонимной функции). Большая проблема заключается в том, что вы не объясняете, чего вы пытаетесь достичь, но только как вы это сделали. – kriss

1

Я не понимаю, весь либо (что делает 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() 
+1

Вопрос не в том, «как я могу прервать функцию?», А скорее «как приостановить его таким образом, чтобы потом возобновить?» Генератор делает именно это, используя выход. Единственная проблема заключается в том, что если генератор хочет вызвать функцию, которая должна уступить, то эта функция вместо этого становится генератором, который разрушает все. Вопрос в том, как обойти это. Я опубликовал то, что я сейчас делаю в качестве ответа, но меня, безусловно, интересуют более элегантные решения. –

0

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

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_loop ожидает генератора, либо нет. Вы должны объяснить эту часть, если хотите получить разумный ответ (некоторые контекстные данные также были бы хорошими, если вы действительно объясняете WTF, который вы пытаетесь сделать, и ваши реальные ограничения - вы сказали, что f предоставляется другим пользователем и main_hook есть в lib, что из main_hook caller? - есть, вероятно, хорошо известные обычные решения).

+0

Как я понимаю (и, по крайней мере, у меня есть проблема с самим собой), дело в том, что он хочет написать сложный генератор, который не будет (только) давать «напрямую», но также вызывает функции, которые его уступят , Тем не менее, при добавлении урона в функцию эта функция становится самим генератором, так что это не работает. Мой собственный ответ показывает мое уродливое обходное решение. Если вы знаете более элегантный способ сделать это, сообщите нам об этом. PEP 342 рассказывает о сопрограммах, которые мы хотим. И мы хотим вызывать функции из сопрограмм. Это не представляется возможным. –

0

Я уже некоторое время борюсь с той же проблемой. Я видел PEP 342, но он уже реализован в Python с версии 2.5 и делает вещи более полезными для того, что мы хотим, но это не делает то, что вы описываете.

До сих пор, я думаю, что решения нет. Это означает, что требуется довольно уродливое обходное решение.Вот что я в настоящее время сделать:

  • Любая функция, которая должна (прямо или косвенно) выход, должен начинаться со строки

    resumeinfo = [(yield), None] 
    
  • Calling такая «функция» должно быть сделано с помощью:

    c = call(resumeinfo, target_function, arguments) 
    while c(): c.args = (yield WAIT) 
    
  • call() также может использоваться из обычных функций; в этом случае вместо resumeinfo следует передать None. В этом случае технически невозможно уступить, поэтому вторая строка не должна использоваться, и функция будет работать в «фоновом режиме» (то есть, вызывающий абонент не уведомляется о возврате или не приостанавливается, пока он Бег). Большую часть времени, назначая c, не является полезным в этом случае.

  • Для возврата обычно используйте yield вместо return. Достижение конца функции возвращает None, как обычно.

  • Чтобы получить возвращаемую функцию после вызова, используйте c.ret().

  • В основной функции (или из любой точки), если вы хотите возобновить приостановленную функцию, вам необходимо иметь ее resumeinfo. Чтобы возобновить его, просто позвоните resumeinfo[0](), опционально передав один аргумент.

  • При использовании yield WAIT без дополнительного кода он будет возобновлен при вызове resumeinfo[0](). Аргумент (если есть) является результатом этого выражения yield.

  • Реализация всего этого в классе call(), конечно. Вы можете найти его на https://github.com/wijnen/python-websockets/blob/master/websockets.py#L293. Этот код в основном не зависит от остальной части этого файла (он использует некоторые константы, но вы можете определить их отдельно).

С другой стороны, это все довольно уродливо. Поэтому вы можете просто использовать другой подход. Во многих случаях это возможно. В некоторых случаях это не так. (Многопоточность никогда не является вариантом ИМО.)

0

В моем предыдущем ответе описывается, как это сделать в 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 

Исключения передаются через пути можно было бы ожидать.

Смежные вопросы