2010-06-19 11 views
8

supongamos que tengo algún objeto de administrador. API de este objeto tiene una función main_hook, que obtiene otra función f ya que es el argumento, y corre el f dada en un bucle, haciendo algunas cosas entre cada iteración:invocando el rendimiento de un generador en otra función

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

Ahora, también tengo (más exactamente, desea tener) una función stop_and_do_stuff, que una vez llamada, detiene main_hook muerto en sus pistas, devuelve el control a cualquiera de las funciones llamadas main_hook, y después de eso el func finalizó lo que está haciendo, recupera el control a main_hook y continúa. Básicamente, el resultado será el mismo que hacer

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

excepto que en lugar yield Quiero tener una llamada a f(), al tiempo que f la opción de llamar self.stop_and_do_stuff()

I no puede trabajo en torno a este f haciendo también un generador por 2 razones:

1. f no es parte de mi API - se ha dado a mí por un usuario que utiliza mi lib

2. Aunque podría pedirle que use yield, el lugar en el código en el que necesitará llamar al stop_and_do_stuff no estará directamente dentro de f, sino en algún lugar de la pila de funciones que estará dentro de f(), pero no directamente en ella, por ejemplo

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) 

así que si decido hacer f un generador, también necesito hacer g un generador y también h, de lo contrario este truco no funcionará.

¿Hay alguna solución a todo esto? tal vez estoy tratando de resolverlo de la manera incorrecta?

(Sé que esta pregunta es largo y feo - es lo mejor que podría hacer si algo no está claro por favor dime y te aclaro.)

EDITAR

Tal pep 342 es la solucion?

+0

Tengo el mismo entendimiento que Anurag y también pienso (como él) que realmente no hizo una pregunta pero proporcionó elementos de su propia solución (eso no funciona todavía). Entonces, lo mejor que puede esperar es lograr que su solución funcione, sin obtener una solución verdaderamente pitonica. Además, por lo que he visto en la pregunta, tengo una sensación extraña. Me parece extraño hablar de funciones como "hacer algo" en lugar de "devolver un resultado", parece que lo que estás haciendo es principalmente un efecto secundario interactivo. Lo es ? – kriss

+1

no es claro para mí que si f es una función lib extranjera, ¿cómo puede llamar a stop_and_do_stuff en el medio y si puede hacerlo, por qué no puede ceder? –

+0

@ Anurag-f obtiene un objeto 'manager' como argumento, y tiene la función stop_and_do_stuff – olamundo

Respuesta

2

Creo que también debería agregar una respuesta desde el otro punto de vista, es decir, no tratar de explicar cómo puede lograr lo que podemos entender de lo que está tratando de hacer, pero por qué yield definitivamente no podría funcionar.

Cuando una función contiene la palabra clave yield, se modifica profundamente. Sigue siendo una función invocable pero no una función normal: se convierte en una fábrica que devuelve un iterador.

Desde el punto de vista de la persona que llama no hay diferencia entre las tres implementaciones a continuación (excepto que el yield es mucho más simple).

########################################## 
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 

A continuación, usted debe entender que el rendimiento no es mucho de flujo de control, sino de guardar el contexto de la llamada dentro de las variables. Una vez que se entiende, debe decidir si la API de main_loop realmente desea proporcionar un iterador a su llamador. Entonces, si es así, si f puede crear un bucle, también debe ser un iterador (y debería haber un bucle alrededor de las llamadas a f() como a continuación).

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

Pero no debe preocuparse si f() tiene que llamar a las funciones interno g(), etc. Eso es completamente irrelevante. Usted proporciona una lib y es su problema de usuario llamar con un iterable apropiado. Si cree que su usuario lib no podrá, tendrá que cambiar el diseño general.

Espero que ayude.

0

No estoy del todo claro qué es exactamente lo que está tratando de lograr, por lo que puede ser si puede explicar el problema más en lugar de dar la solución que sería mejor.

Desde mi comprensión parcial por qué no haces algo como esto

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 

Así que, básicamente f Muestra una marca de parar o no, y si dice detener cedemos a funcionar el que llama y que la función main_hook puede continuar después de hacer algunas cosas

por ejemplo

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

Esta solución está lejos del resultado deseado. El punto fue que 'f' puede llamar' stop_and_do_stuff' en el medio, sin terminar (es decir, como si 'f' tuviera' yield'). En su solución, 'f' tiene que terminar y devolver una bandera, lo que fuerza a' f' a terminar antes de que pueda llamarse 'stop_and_do_stuff'. Con respecto a mi pregunta, si puede explicar lo que no está claro en mi explicación, me complacerá aclararlo. – olamundo

+0

@noam: una parte de lo que no está claro es el "stop_and_" en "stop_and_do_stuff". O desea detener f() como una devolución, eso es lo que Anurag entendió, o bien es una llamada de función habitual dentro de f() (en cuyo caso también es fácil, solo tiene que proporcionar una función para llamar). Parece que desea llamar a una función, pero no proporcionarla explícitamente. El uso habitual del rendimiento en python es a la inversa (devuelve el control), tu uso se parece más al rendimiento de un rubí (llamada a la función anónima). El problema más grande es que no explicas lo que intentas lograr, sino solo cómo lo hiciste. – kriss

1

No entiendo la totalidad o bien (¿Cómo es la persona que llama como main_hook?), Pero yo diría, una excepción de StopNow, cuándo debe dejar, al igual que usted debe lanzar StopIteration cuando su generador Está terminado.

así es como entendí la cosa y lo que haría.

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

La pregunta no era "¿cómo puedo abortar la función?", Sino más bien "¿cómo puedo suspenderla de una manera que luego pueda reanudar?" Un generador hace exactamente eso, usando rendimiento. El único problema es que si el generador desea llamar a una función que debería ceder, entonces esa función se convierte en un generador en sí mismo, lo que arruina todo. La pregunta es cómo evitarlo. He publicado lo que hago actualmente como respuesta, pero ciertamente estoy interesado en soluciones más elegantes. –

0

El comportamiento que describes es exactamente como una llamada a una función simple. Como abajo.

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") 

No hay problema si f() se proporciona por otro usuario de si stop_and_do_stuff se llama desde alguna función interior. Si también desea que el administrador pueda desenrollar la pila del stop_and_do_stuff y salga realmente en algunos casos, no hay problema. Solo levante una excepción y la captará desde main_hook o upper code.

Debería poder hacer desde el interior de stop_and_and_do_stuff() lo que quiera hacer de la persona que llama del gancho principal. Si no, deberías explicar por qué.

Lo que no está claro en la pregunta es qué está pasando en el lado del que llama de main_hook() y por qué querría poder salir del bucle main_hook, pero realmente no. O bien el llamador main_loop espera un generador o no lo hace. Necesitas explicar esa parte si quieres obtener una respuesta sensata (algunas informaciones de contexto también serían agradables, si realmente explicas la WTF que estás tratando de hacer, y tus restricciones reales - dijiste que f es proporcionada por otro usuario y main_hook está en una lib, ¿qué hay de la llamada main_hook? - probablemente haya soluciones habituales bien conocidas).

+0

Según tengo entendido (y al menos, lo que tengo un problema conmigo mismo), el punto es que quiere escribir un generador complejo, que no (solo) cederá "directamente", sino que también llama a las funciones que lo producirán . Sin embargo, al poner rendimiento en una función, esa función se convierte en un generador, por lo que no funciona. Mi propia respuesta aquí muestra mi fea solución. Si conoce una forma más elegante de hacerlo, háganoslo saber. PEP 342 habla de corutinas, que son exactamente lo que queremos. Y queremos llamar a funciones de corutinas. Eso no parece posible. –

0

He estado luchando con el mismo problema desde hace un tiempo. He visto PEP 342, pero ya está implementado en Python desde 2.5, y hace las cosas más útiles para lo que queremos, pero no hace lo que describes.

Hasta ahora, creo que no hay solución. Esto significa que se requiere una solución bastante desagradable.Esto es lo que actualmente hago:

  • Cualquier función que debe ceder el paso (directa o indirectamente), debe comenzar con la línea

    resumeinfo = [(yield), None] 
    
  • Calling tal una "función" se debe hacer usando:

    c = call(resumeinfo, target_function, arguments) 
    while c(): c.args = (yield WAIT) 
    
  • call() también se puede utilizar desde funciones regulares; en ese caso, se debe pasar None en lugar de resumeinfo. Es técnicamente imposible ceder en ese caso, por lo que no se debe usar la segunda línea, y la función se ejecutará en el "fondo" (es decir, la persona que llama no recibe la notificación al regresar, o se detiene mientras está corriendo). La mayoría de las veces, la asignación a c no es útil en ese caso.

  • Para regresar normalmente, use yield en lugar de return. Al llegar al final de una función, se devuelve None como de costumbre.

  • Para recuperar la función devuelta después de la llamada, use c.ret().

  • En la función principal (o desde cualquier lugar), si desea reanudar una función en pausa, necesita tener su información resumida. Para reanudarlo, simplemente llame al resumeinfo[0](), opcionalmente pasando un único argumento.

  • Al utilizar un yield WAIT sin código adicional, se reanudará cuando se llame al resumeinfo[0](). El argumento (si lo hay) es el resultado de esta expresión de rendimiento.

  • La implementación de todo esto está en la clase call(), por supuesto. Puede encontrarlo en https://github.com/wijnen/python-websockets/blob/master/websockets.py#L293. Este código es en su mayoría independiente del resto de ese archivo (usa algunas constantes, pero puede definirlas por separado).

Por otra parte, todo es bastante feo. Entonces, quizás solo quiera usar un enfoque diferente. En muchos casos, eso es posible. En algunos casos, no lo es. (Multithreading nunca es una opción IMO.)

0

Mi respuesta anterior describe cómo hacer esto en Python2, que es muy feo. Pero ahora me encontré con PEP 380: sintaxis para delegar en un subgenerador. Eso hace exactamente lo que preguntas. El único problema es que requiere Python3. Pero eso no debería ser realmente un problema.

Así es como funciona:

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) 

El resultado de esto es:

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

excepciones se pasan a través de la forma en que se puede esperar.

Cuestiones relacionadas