2009-02-12 9 views
22

¿Es posible construir mediante programación una pila (uno o más marcos de pila) en CPython y comenzar la ejecución en un punto de código arbitrario? Imagine el siguiente escenario:¿Es posible construir programáticamente un marco de pila Python y comenzar la ejecución en un punto arbitrario en el código?

  1. Tiene un motor de flujo de trabajo, donde los flujos de trabajo puede ser escrito en Python con algunas construcciones (por ejemplo, ramificación, en espera/unión) que son llamadas al motor de flujo de trabajo.

  2. Una llamada de bloqueo, como esperar o unir establece una condición de oyente en un motor de envío de eventos con un almacén de respaldo persistente de algún tipo.

  3. Tiene una secuencia de comandos de flujo de trabajo, que llama a la condición de espera en el motor, a la espera de alguna condición que se señalará más tarde. Esto configura el oyente en el motor de envío de eventos.

  4. El estado del script de flujo de trabajo, los marcos de pila relevantes, incluido el contador del programa (o estado equivalente) se conservan, ya que la condición de espera podría ocurrir días o meses después.

  5. Mientras tanto, el motor de flujo de trabajo podría detenerse y reiniciarse, lo que significa que debe ser posible almacenar y reconstruir el contexto del script de flujo de trabajo mediante programación.

  6. El motor de envío de eventos activa el evento de que la condición de espera se activa.

  7. El motor de flujo de trabajo lee el estado serializado y apila y reconstruye un hilo con la pila. A continuación, continúa la ejecución en el punto donde se llamó al servicio de espera.

La pregunta

se puede hacer esto con un intérprete de Python sin modificar? Mejor aún, ¿alguien puede indicarme algún tipo de documentación que pueda cubrir este tipo de cosas o un ejemplo de código que construye programáticamente un marco de pila y comienza la ejecución en algún lugar en medio de un bloque de código?

Editar: Para aclarar 'intérprete de python sin modificar', no me importa el uso de la API C (¿hay suficiente información en un PyThreadState hacer esto?), Pero no quiero ir a hurgar los componentes internos del intérprete de Python y tener que construir uno modificado.

Actualización: De alguna investigación inicial, uno puede obtener el contexto de ejecución con PyThreadState_Get(). Esto devuelve el estado del hilo en un PyThreadState (definido en pystate.h), que tiene una referencia al marco de la pila en frame. Un marco de pila se mantiene en una estructura typedef'd a PyFrameObject, que se define en frameobject.h. PyFrameObject tiene un campo f_lasti (apoyos a bobince) que tiene un contador de programa expresado como un desplazamiento desde el comienzo del bloque de código.

Esta última es una especie de buena noticia, porque significa que siempre que conserve el bloque de código compilado real, debería ser capaz de reconstruir los locales para tantos marcos de pila como sea necesario y reiniciar el código.Yo diría que esto significa que es teóricamente posible sin tener que hacer un intérprete de pitón modificado, aunque significa que el código probablemente todavía esté complicado y estrechamente vinculado a versiones específicas del intérprete.

Los tres problemas que quedan son:

  • estado de la transacción y el desmantelamiento 'saga', lo que probablemente se puede lograr mediante el tipo de piratería metaclase se podría usar para construir un asignador de O/R. Construí un prototipo una vez, así que tengo una buena idea de cómo se puede lograr esto.

  • Estado de transacciones de serialización robusta y locales arbitrarios. Esto se puede lograr leyendo __locals__ (que está disponible desde el marco de pila) y construyendo programáticamente una llamada a pickle. Sin embargo, no sé qué podría haber aquí, si es que hay alguno.

  • Versiones y actualizaciones de flujos de trabajo. Esto es un poco más complicado, ya que el sistema no proporciona ningún anclaje simbólico para los nodos de flujo de trabajo. Todo lo que tenemos es el ancla Para hacer esto, uno debería identificar las compensaciones de todos los puntos de entrada y asignarlos a la nueva versión. Probablemente sea factible hacerlo manualmente, pero sospecho que sería difícil automatizarlo. Este es probablemente el mayor obstáculo si desea soportar esta capacidad.

Actualización 2:PyCodeObject (code.h) tiene una lista de addr (f_lasti) -> asignaciones de número de línea en PyCodeObject.co_lnotab (corríjanme si mal aquí). Esto podría usarse para facilitar un proceso de migración para actualizar los flujos de trabajo a una nueva versión, ya que los punteros de instrucciones congelados podrían asignarse al lugar apropiado en el nuevo guión, hecho en términos de los números de línea. Aún bastante desordenado, pero un poco más prometedor.

Actualización 3: Creo que la respuesta podría ser Stackless Python. Puede suspender tareas y serializarlas. No he averiguado si esto también funcionará con la pila.

+3

Gran pregunta - ¡De seguro que odio ser el que tiene que depurar este proyecto! –

Respuesta

2

Con el estándar CPython esto se complica por la mezcla de Datos de C y Python en la pila. La reconstrucción de la pila de llamadas requeriría la reconstrucción de la pila C al mismo tiempo. Esto realmente lo coloca en la cesta demasiado difícil, ya que podría unir estrechamente la implementación a versiones específicas de CPython.

Stackless Python permite encuadernar tasklets, lo que proporciona la mayor parte de la capacidad requerida de fábrica.

2

se pudiera tomar el marco de la pila existente lanzando una excepción y dando un paso atrás un marco a lo largo de la traza. El problema es que no hay forma de proporcionar reanudar la ejecución en el medio (frame.f_lasti) del bloque de código.

Las "excepciones reanudables" son una idea de lenguaje realmente interesante, aunque es difícil pensar en una forma razonable de que puedan interactuar con los bloques 'try/finally' y 'with' existentes de Python.

Por el momento, la forma normal de hacer esto es simplemente usar subprocesos para ejecutar su flujo de trabajo en un contexto separado de su controlador. (O coroutines/greenlets si no te importa compilarlos).

7

Lo que generalmente desea son las continuaciones, que ya veo son una etiqueta en esta pregunta.

Si tiene la capacidad de trabajar con todo el código en el sistema, puede intentar haciéndolo de esta manera en lugar de ocuparse de las partes internas de la pila del intérprete. No estoy seguro de cuán fácilmente esto persistirá.

http://www.ps.uni-sb.de/~duchier/python/continuations.html

En la práctica, habría que estructurar su motor de flujo de trabajo de manera que la secuencia de comandos se somete la acción se opone a un gerente. El administrador podría resumir el conjunto de acciones en cualquier punto y permitir que se carguen y comenzar la ejecución nuevamente (reanudando el envío de acciones).

En otras palabras: crea tu propia pila de nivel de aplicación.

+1

Lo que me gusta de los 'objetos de acción' (piense en el patrón 'Comando') es que esto también proporcionaría soporte para la reversión del flujo de trabajo. Aunque es una solución de compromiso contra la simplicidad y la claridad del script de flujo de trabajo, es un buen enfoque desde esta perspectiva. – ConcernedOfTunbridgeWells

1

Tengo el mismo tipo de problema para resolver. Me pregunto qué decidió hacer el poster original.

stackless afirma que puede desencadenar tareas siempre que no haya una pila C asociada 'gravada' (grabada es mi elección de fraseo).

probablemente voy a utilizar eventlet y buscar la manera de decapado 'estado', la verdad es que no quiero escribir una máquina de estados explícita, aunque ..

+0

Lamentablemente, el OP ha archivado el problema por el momento ya que el proyecto original nunca se llevó a cabo:^p – ConcernedOfTunbridgeWells

1

¿Qué le parece usar joblib?

No estoy muy seguro de que esto sea lo que quiere, pero parece encajar con la idea de tener un flujo de trabajo cuyas etapas se puedan conservar. El caso de uso de Joblib parece ser evitar el recálculo, no estoy seguro de si esto es lo que estás tratando de hacer aquí o algo más complicado.

2

Stackless python es probablemente el mejor ... si no te importa pasar totalmente a una distribución de pitón diferente. stackless puede serializar todo en python, más sus tasklets. Si desea permanecer en la distribución estándar de Python, entonces usaría dill, que puede serializar casi cualquier cosa en python.

>>> import dill 
>>> 
>>> def foo(a): 
... def bar(x): 
...  return a*x 
... return bar 
... 
>>> class baz(object): 
... def __call__(self, a,x): 
...  return foo(a)(x) 
... 
>>> b = baz() 
>>> b(3,2) 
6 
>>> c = baz.__call__ 
>>> c(b,3,2) 
6 
>>> g = dill.loads(dill.dumps(globals())) 
>>> g 
{'dill': <module 'dill' from '/Library/Frameworks/Python.framework/Versions/7.2/lib/python2.7/site-packages/dill-0.2a.dev-py2.7.egg/dill/__init__.pyc'>, 'c': <unbound method baz.__call__>, 'b': <__main__.baz object at 0x4d61970>, 'g': {...}, '__builtins__': <module '__builtin__' (built-in)>, 'baz': <class '__main__.baz'>, '_version': '2', '__package__': None, '__name__': '__main__', 'foo': <function foo at 0x4d39d30>, '__doc__': None} 

eneldo registra su tipo en el registro pickle, por lo que si usted tiene algún código de cuadro negro que utiliza pickle y realmente no se puede editar, a continuación, el eneldo simplemente importar mágicamente puede hacer que funcione sin monkeypatching la tercera parte código.

Aquí es dill decapado toda la sesión intérprete ...

>>> # continuing from above 
>>> dill.dump_session('foobar.pkl') 
>>> 
>>> ^D 
[email protected]>$ python 
Python 2.7.5 (default, Sep 30 2013, 20:15:49) 
[GCC 4.2.1 (Apple Inc. build 5566)] on darwin 
Type "help", "copyright", "credits" or "license" for more information. 
>>> import dill 
>>> dill.load_session('foobar.pkl') 
>>> c(b,3,2) 
6 

dill también tiene some good tools para ayudar a entender lo que está causando el decapado a fallar cuando el código de falla.

¿También preguntó dónde se usa para guardar el estado del intérprete?

IPython puede usar dill para guardar la sesión del intérprete en un archivo.https://nbtest.herokuapp.com/github/ipython/ipython/blob/master/examples/parallel/Using%20Dill.ipynb

klepto utiliza dill para apoyar el almacenamiento en caché en memoria, en disco, o de base de datos que evita recálculo. https://github.com/uqfoundation/klepto/blob/master/tests/test_cache_info.py

mystic usa dill para guardar los puntos de control para grandes trabajos de optimización guardando el estado del optimizador mientras está en progreso. https://github.com/uqfoundation/mystic/blob/master/tests/test_solver_state.py

Existen otros dos paquetes que usan dill para guardar el estado de objetos o sesiones.

Cuestiones relacionadas