É possível construir programaticamente um quadro de pilha Python e iniciar a execução em um ponto arbitrário no código?

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

Pergunta

É possível construir programaticamente uma pilha (um ou mais quadros de pilha) em CPython e iniciar a execução em um ponto de código arbitrário? Imagine a seguinte situação:

  1. Você tem um motor de fluxo de trabalho, onde os fluxos de trabalho podem ser script em Python com algumas construções (por exemplo, ramificação, esperando / juntando), que são chamadas para o mecanismo de fluxo de trabalho.

  2. Uma chamada de bloqueio, tais como uma espera ou participar configura uma condição de ouvinte em um motor de despachar evento com um armazenamento de backup persistente de algum tipo.

  3. Você tem um script de fluxo de trabalho, o que exige a condição de espera no motor, à espera de alguma condição que será sinalizado mais tarde. Isso configura o ouvinte no motor de despachar evento.

  4. O estado do script de fluxo de trabalho, quadros de pilha relevantes, incluindo o contador de programa (ou estado equivalente) são persistentes -. Como a condição de espera poderia ocorrer dias ou meses mais tarde

  5. Nesse ínterim, o motor de fluxo de trabalho pode ser interrompido e re-iniciado, o que significa que deve ser possível para armazenar e reconstruir o contexto do script de fluxo de trabalho de programação.

  6. O evento despachando motor aciona o evento que a condição de espera pega.

  7. O motor de fluxo de trabalho lê o estado serializado e pilha e reconstrói um fio com a pilha. Em seguida, continua a execução no ponto onde o serviço de espera foi chamado.

A Pergunta

Isso pode ser feito com um interpretador Python não modificada? Mesmo melhor, pode alguém me aponte para alguma documentação que possa cobrir esse tipo de coisa ou um exemplo de código que programaticamente constrói um quadro de pilha e começa em algum lugar da execução no meio de um bloco de código?

Editar: Para esclarecer 'interpretador Python não modificada', eu não me importo usando a API C (existe informação suficiente em um PyThreadState fazer isso?), Mas eu não quero ir bisbilhotando as partes internas do interpretador Python e ter que construir um modificado.

Update: De alguma investigação inicial, pode-se obter o contexto de execução com PyThreadState_Get(). Isto devolve o estado do segmento num PyThreadState (definido em pystate.h), o qual tem uma referência para o quadro de pilha em frame. Um quadro de pilha é mantida numa estrutura typedef para PyFrameObject, a qual é definida em frameobject.h. PyFrameObject tem uma f_lasti campo (adereços para bobince ) que tem um contador de programa expressa como um deslocamento a partir do início do bloco de código.

Esta última é uma espécie de uma boa notícia, porque significa que, enquanto você manter o bloco de código compilado real, você deve ser capaz de reconstruir os locais para tantos quadros de pilha como necessário e re-iniciar o código. Eu diria que isso significa que é teoricamente possível, sem ter que fazer uma interpereter python modificada, embora isso significa que o código ainda é provavelmente vai ser trabalhosa e fortemente acoplados a versões específicas do intérprete.

Os três problemas remanescentes são:

  • estado de transação e rollback 'saga', que provavelmente pode ser realizado pelo tipo de metaclass hackers pode-se usar para construir um R / mapper O. Eu fiz construir um protótipo, uma vez, por isso tenho uma boa idéia de como isso pode ser realizado.

  • locals
  • robustamente serializadas estado de transação e arbitrária. Isto pode ser conseguido através da leitura __locals__ (que está disponível a partir do quadro de pilha) e programaticamente construindo uma chamada para pickle. No entanto, eu não sei o que, se houver, gotchas pode haver aqui.

  • Versioning e atualização dos fluxos de trabalho. Isso é um pouco mais complicado, pois o sistema não está fornecendo qualquer ancho simbólicars para nós de fluxo de trabalho. Tudo o que temos é a âncora A fim de fazer isso, seria preciso identificar os deslocamentos de todos os pontos de entrada e mapeá-los para a nova versão. Provavelmente viável fazer manualmente, mas eu suspeito que seria difícil de automatizar. Este é provavelmente o maior obstáculo se você quiser apoiar esta capacidade.

Update 2: PyCodeObject (code.h) tem uma lista de endereço (f_lasti) -> mapeamentos número da linha no PyCodeObject.co_lnotab (me corrigir se errado aqui). Isso pode ser usado para facilitar um processo de migração para fluxos de trabalho de atualização para uma nova versão, como ponteiros de instrução congelados pode ser mapeado para o local apropriado no novo roteiro, feito em termos de números de linha. Ainda bastante confuso, mas um pouco mais promissor.

Update 3: Eu acho que a resposta para isso pode ser Stackless Python . você pode suspender tarefas e publicando-os. Eu não tenho trabalhado para fora se este também irá trabalhar com a pilha também.

Foi útil?

Solução 5

Com CPython padrão este é complicado pela mistura de dados C e Python na pilha. Reconstruindo a pilha de chamadas exigiria a pilha C para ser reconstruído, ao mesmo tempo. Isso realmente coloca-o na cesta demasiado duro como ele poderia potencialmente firmemente par da implementação de versões específicas do CPython.

Stackless Python permite tasklets a ser conservado, o que dá a maior parte da capacidade necessária para fora da caixa.

Outras dicas

As ligações expatriados python incluídas na distribuição normal Python está construindo quadros de pilha programtically. Esteja avisado, porém, ele conta com APIs não documentadas e privadas.

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

O que você geralmente quer são continuações, o que eu vejo é já um tag sobre esta questão.

Se você tiver a capacidade de trabalhar com todo o código no sistema, você pode querer tentar Fazendo dessa forma, em vez de lidar com os internos pilha intérprete. Eu não sei como facilmente este será mantido.

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

Na prática, gostaria de estruturar o seu motor de fluxo de trabalho para que a ação o script submete objetos a um gerente. O gerente poderia pickle o conjunto de ações a qualquer momento e permitir -los para ser carregado e começar a execução de novo (por retomar a apresentação de acções).

Em outras palavras:. Fazer o seu próprio, de nível de aplicação, pilha

Stackless python é provavelmente a melhor ... se você não se importa totalmente indo para uma distribuição python diferente. stackless pode serializar tudo em python, além de suas tasklets. Se você quiser ficar na distribuição padrão do Python, então eu usaria dill , que pode serializar quase nada em 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-lo de tipos no registro pickle, por isso, se você tem algum código caixa preta que usa pickle e você realmente não pode editá-lo, em seguida, apenas a importação de dill pode magicamente fazê-lo funcionar sem monkeypatching o código do 3o partido.

O Aqui dill decapagem toda a sessão 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 também tem algumas boas ferramentas para ajudar você a entender o que está causando o seu decapagem a falhar quando o seu código de falha.

Você também pediu onde ele é usado para salvar o estado intérprete?

IPython pode usar dill para salvar a sessão intérprete para um arquivo. https://nbtest.herokuapp.com /github/ipython/ipython/blob/master/examples/parallel/Using%20Dill.ipynb

klepto usa dill para caching apoio na memória, para o disco, ou-a base de dados que evita recomputation. https://github.com/uqfoundation/klepto/blob/master/tests /test_cache_info.py

místico usos dill para salvar os postos de controle para grandes trabalhos de otimização, salvando o estado do otimizador como é em progresso. https://github.com/uqfoundation/mystic/blob/master/tests /test_solver_state.py

Há um par de outros pacotes que o uso dill para salvar estado de objetos ou sessões.

Você poderia pegar o quadro de pilha existente lançando uma exceção e recuar um quadro ao longo do rastreamento. O problema é que não há maneira fornecido para execução currículo no meio (frame.f_lasti) do bloco de código.

“exceções recuperáveis” são uma ideia linguagem muito interessante, embora seja complicado de pensar de uma maneira razoável que eles poderiam interagir com o Python ‘try / finally’ existente e ‘com’ blocos.

No momento, a maneira normal de fazer isso é simplesmente tópicos usar para executar o fluxo de trabalho em um contexto separado para seu controlador. (Ou coroutines / greenlets se você não se importa de compilá-los em).

Eu tenho o mesmo tipo de problema para resolver. Eu me pergunto o que o poster original decidiu fazer.

reivindicações Stackless pode pickle tasklets Enquanto houver nenhuma está associada 'sobrecarregado' pilha C (onerado é a minha escolha de fraseado).

Eu provavelmente vou usar eventlet e descobrir uma maneira de decapagem 'estado', eu realmente não quero escrever uma máquina estatal explícita embora ..

Que tal usar joblib ?

Eu não estou muito certo este é o que você quer, mas ele parece caber a ideia de ter um fluxo de trabalho dos quais etapas podem ser persistentes. caso de uso de Joblib parece ser evitar o recálculo, eu não tenho certeza se é isso que você está tentando fazer aqui ou algo mais complicado?

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top