¿Es posible construir mediante programación un marco de pila de Python e iniciar la ejecución en un punto arbitrario del código?

StackOverflow https://stackoverflow.com/questions/541329

Pregunta

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

  1. Tiene un motor de flujo de trabajo donde los flujos de trabajo se pueden programar en Python con algunas construcciones (p. ej.bifurcación, espera/unión) que son llamadas al motor de flujo de trabajo.

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

  3. Tiene un script de flujo de trabajo que llama a la condición de espera en el motor, esperando alguna condición que se señalará más adelante.Esto configura el oyente en el motor de despacho de eventos.

  4. El estado del script de flujo de trabajo y los marcos de pila relevantes, incluido el contador del programa (o estado equivalente), persisten, 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 mediante programación el contexto del script de flujo de trabajo.

  6. El motor de despacho de eventos activa el evento en el que se activa la condición de espera.

  7. El motor de flujo de trabajo lee el estado serializado y la pila y reconstruye un hilo con la pila.Luego 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 no modificado?Aún mejor, ¿alguien puede indicarme alguna documentación que pueda cubrir este tipo de cosas o un ejemplo de código que construye mediante programación un marco de pila y comienza la ejecución en algún lugar en medio de un bloque de código?

Editar: Para aclarar el 'intérprete de Python no modificado', no me importa usar la API de C (¿hay suficiente información en PyThreadState para hacer esto?), pero no quiero hurgar en las partes internas del intérprete de Python y tener que compilar. uno modificado.

Actualizar: A partir de una investigación inicial, se 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 pila en frame.Un marco de pila se mantiene en una estructura definida para PyFrameObject, que se define en frameobject.h. PyFrameObject tiene un campo f_lasti (apoyos a Bobina) que tiene un contador de programa expresado como un desplazamiento desde el comienzo del bloque de código.

Esto último es una buena noticia, porque significa que mientras conserve el bloque de código compilado real, debería poder reconstruir 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 crear un intérprete de Python modificado, aunque significa que el código probablemente seguirá siendo complicado y estrechamente acoplado a versiones específicas del intérprete.

Los tres problemas restantes son:

  • Estado de transacción y reversión de 'saga', que probablemente se pueda lograr mediante el tipo de pirateo de metaclases que uno usaría para construir un mapeador O/R.Una vez construí un prototipo, así que tengo una idea clara de cómo podría lograrse.

  • Serialización sólida del estado de transacciones y locales arbitrarios.Esto podría lograrse leyendo __locals__ (que está disponible en el marco de la pila) y construir programáticamente una llamada a pickle.Sin embargo, no sé qué problemas podría haber aquí, si es que hay alguno.

  • Versionado y actualización de flujos de trabajo.Esto es algo 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 ello, habría que identificar las compensaciones de todos los puntos de entrada y asignarlas a la nueva versión.Probablemente sea factible hacerlo manualmente, pero sospecho que sería difícil de automatizar.Este es probablemente el mayor obstáculo si desea admitir esta capacidad.

Actualización 2: PyCodeObject (code.h) tiene una lista de direcciones (f_lasti)-> asignaciones de números de línea en PyCodeObject.co_lnotab (corríjame si me equivoco 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 script, en términos de números de línea.Todavía bastante complicado pero un poco más prometedor.

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

¿Fue útil?

Solución 5

Con el estándar CPython esto se complica por la mezcla de datos C y Python en la pila. La reconstrucción de la pila de llamadas requeriría la pila C para ser reconstruida al mismo tiempo. Esto realmente pone en la canasta demasiado duro, ya que podría potencialmente firmemente par la implementación de versiones específicas de CPython.

Stackless Python permite tasklets a decapar, lo que da la mayor parte de la capacidad requerida de la caja.

Otros consejos

Los enlaces Python expatriados incluidos en la distribución normal de Python está construyendo marcos de pila programtically. Se advierte sin embargo, se basa en las API de indocumentados y privadas.

http: //svn.python. org / view / Python / trunk / Módulos / pyexpat.c? rev = 64048 & view = auto

Lo que queremos son generalmente continuaciones, que veo es ya una etiqueta en esta cuestión.

Si usted tiene la capacidad de trabajar con todo el código en el sistema, es posible que desee probar haciendo de esta manera en lugar de tratar con las partes internas pila del intérprete. No estoy seguro de cómo esto fácilmente se conservará.

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

En la práctica, me gustaría estructurar su motor de flujo de trabajo para que su escritura presenta objetos de acción con un director. El gerente podría conservar en vinagre el conjunto de acciones en cualquier momento y permitir que se carguen y comenzar la ejecución de nuevo (mediante la reanudación de la presentación de las acciones).

En otras palabras:. Hacer su propio, a nivel de aplicación, pila

pitón Stackless es probablemente la mejor ... si no te importa pasarse totalmente a una distribución diferente de pitón. stackless puede serializar todo en Python, además de sus tasklets. Si usted desea permanecer en la distribución estándar de Python, entonces me gustaría usar eneldo , que puede serializar casi nada 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}

Dill 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 el código de tercera parte.

A continuación se dill decapado toda la sesión intérprete ...

>>> # continuing from above
>>> dill.dump_session('foobar.pkl')
>>>
>>> ^D
dude@sakurai>$ 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 href="https://github.com/uqfoundation/dill/blob/master/dill/detect.py" rel="nofollow"> algunas buenas herramientas para ayudar a entender lo que está causando el decapado a fallar cuando el código de falla.

También pedirá donde se utiliza para guardar el estado intérprete?

IPython puede utilizar dill para guardar la sesión 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

mística utiliza dill para salvar los puestos de control para grandes trabajos de optimización de guardar el estado del optimizador, ya que es en progreso. https://github.com/uqfoundation/mystic/blob/master/tests /test_solver_state.py

Hay un par de otros paquetes que utilizan dill para guardar el estado de los objetos o sesiones.

Se podía agarrar 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 manera proporcionada para reanudar la ejecución en el medio (frame.f_lasti) del bloque de código.

“excepciones reanudables” son una idea idioma muy interesante, aunque es difícil pensar en una forma razonable que pudieran interactuar con Python existente ‘try / finally’ y ‘con’ bloques.

Por el momento, la forma normal de hacer esto es simplemente para usar hilos para ejecutar el flujo de trabajo en un contexto separado para su controlador. (O / corrutinas Verdecillo si no le importa compilarlas en).

Tengo el mismo tipo de problema a resolver. Me pregunto lo que el cartel original decidió hacer.

sin apilado afirma que puede conservar en vinagre tasklets, siempre y cuando no hay 'gravado' pila C asociado (gravado es mi elección de frases).

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

¿Y si uso JOBLIB ?

No estoy muy seguro de que esto es lo que quiere pero parece ajustarse a la idea de tener un flujo de trabajo de los cuales etapas se pueden conservar. caso de uso de JOBLIB parece ser la de evitar el recálculo, no estoy seguro si esto es lo que está tratando de hacer aquí o algo más complicado?

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top