Sandboxing / correr código de Python línea por línea

Me encantaría poder hacer algo como estos dos que están haciendo:

Inventando en principio @ 18: 20 , Live ClojureScript Game Editor

Si no quieres ver los videos, mi problema es este:

Digamos que tenía este código:

.... xs = [] for x in xrange(10): xs.append(x) ... 

Me gustaría crear un entorno donde pueda ejecutar el código, la statement de la statement y ver / rastrear a los locales / globales a medida que cambian. Tal vez déle una lista de variables para realizar un seguimiento de los diccionarios locales / globales. Como pasar por el código y guardar la información del estado.

De manera óptima, me gustaría guardar todos los estados y sus datos de contexto asociados (locales / globales) para poder verificar los predicados, por ejemplo.

Me gustaría hacer algo como el ejemplo de búsqueda binaria de Bret Victor Inventar en principio @ 18: 20

¿Tengo sentido? Me resulta complicado explicarlo en texto, pero los videos muestran lo que quiero probar 🙂

Gracias por tu tiempo


Lo que he probado / leído / buscado en Google:

  • code.InteractiveConsole / code.InteractiveInterpreter
  • livecoding módulo de livecoding : parece funcionar para código funcional / sin estado puro
  • exec / eval magic: parece que no puedo obtener un control tan fino como me gustaría.
  • El módulo de trace tampoco parece ser el camino.
  • Eval de Python (comstackr (…), sandbox), los globales van a sandbox a menos que estén definidos, ¿por qué? <- Esto está cerca de lo que quiero, pero compila toda la cadena / bloque de código y lo ejecuta en un solo paso. Si pudiera ejecutar un archivo como este, pero compruebe los locales entre cada línea / declaración.
  • ejecutar el código fuente de Python línea por línea <- Esto no es lo que quiero
  • ¿Cómo implementan Ruby y Python sus consolas interactivas? <- Este tema sugiere que busque más en el módulo de code

Mi próximo paso sería buscar en ast comstackr el código y ejecutarlo poco a poco, pero realmente necesito una orientación. ¿Debería mirar más detenidamente la reflexión y el módulo de inspect ?

He usado el verificador de modelos Spin antes, pero usa su propio DSL y me encantaría hacer el modelado en el lenguaje de implementación, en este caso Python.

Ah y, por cierto, conozco las implicaciones de seguridad del código de sandboxing, pero no estoy tratando de crear un entorno de ejecución seguro, estoy tratando de crear un entorno muy interactivo, con el objective de verificar el modelo en bruto o la afirmación de predicados, por ejemplo.

Después de mi éxito inicial con sys.settrace() , terminé cambiando al módulo ast (árboles de syntax abstracta). Analizo el código que quiero analizar y luego inserto nuevas llamadas después de cada asignación para informar sobre el nombre de la variable y su nuevo valor. También inserto llamadas para informar sobre iteraciones de bucle y llamadas de función. Luego ejecuto el arbol modificado.

  tree = parse(source) visitor = TraceAssignments() new_tree = visitor.visit(tree) fix_missing_locations(new_tree) code = compile(new_tree, PSEUDO_FILENAME, 'exec') self.environment[CONTEXT_NAME] = builder exec code in self.environment 

Estoy trabajando en una herramienta de encoding en vivo como la de Bret Victor, y puedes ver mi código de trabajo en GitHub , y algunos ejemplos de cómo se comporta en la prueba . También puede encontrar enlaces a un video de demostración, tutorial y descargas desde la página del proyecto .

Actualización: Después de mi éxito inicial con esta técnica, cambié a usar el módulo ast como se describe en mi otra respuesta .

sys.settrace() parece funcionar muy bien. Tomé la pregunta de los hackeos que mencionaste y el artículo de Andrew Dalke y conseguí este sencillo ejemplo.

 import sys def dump_frame(frame, event, arg): print '%d: %s' % (frame.f_lineno, event) for k, v in frame.f_locals.iteritems(): print ' %s = %r' % (k, v) return dump_frame def main(): c = 0 for i in range(3): c += i print 'final c = %r' % c sys.settrace(dump_frame) main() 

Tuve que resolver dos problemas para que esto funcione.

  1. La función de rastreo tiene que retornar a sí misma u otra función de rastreo si desea continuar el rastreo.
  2. El rastreo solo parece comenzar después de la primera llamada de función. Originalmente no tenía el método principal, y simplemente fui directamente a un bucle.

Aquí está la salida:

 9: call 10: line 11: line c = 0 12: line i = 0 c = 0 11: line i = 0 c = 0 12: line i = 1 c = 0 11: line i = 1 c = 1 12: line i = 2 c = 1 11: line i = 2 c = 3 14: line i = 2 c = 3 final c = 3 14: return i = 2 c = 3 38: call item =  selfref =  38: call item =  selfref =  

Parece que necesitas bdb, la biblioteca del depurador de Python. Está integrado y los documentos están aquí: http://docs.python.org/library/bdb.html

No tiene toda la funcionalidad que parece querer, pero es un lugar sensato para comenzar a implementarla.

Bueno chicos, he hecho un poco de progreso.

Digamos que tenemos un archivo fuente como este, queremos ejecutar una statement por otra:

 print("single line") for i in xrange(3): print(i) print("BUG, executed outside for-scope, so only run once") if i < 0: print("Should not get in here") if i > 0: print("Should get in here though") 

Quiero ejecutarlo una statement a la vez, mientras tengo acceso a los locales / globales. Esta es una prueba de concepto sucia y rápida (no tenga en cuenta los errores y la crudeza):

 # returns matched text if found def re_match(regex, text): m = regex.match(text) if m: return m.groups()[0] # regex patterns newline = "\n" indent = "[ ]{4}" line = "[\w \"\'().,=<>-]*[^:]" block = "%s:%s%s%s" % (line, newline, indent, line) indent_re = re.compile(r"^%s(%s)$" % (indent, line)) block_re = re.compile(r"^(%s)$" % block) line_re = re.compile(r"^(%s)$" % (line)) buf = "" indent = False # parse the source using the regex-patterns for l in source.split(newline): buf += l + newline # add the newline we removed by splitting m = re_match(indent_re, buf) # is the line indented? if m: indent = True # yes it is else: if indent: # else, were we indented previously? indent = False # okay, now we aren't m = re_match(block_re, buf) # are we starting a block ? if m: indent = True exec(m) buf = "" else: if indent: buf = buf[4:] # hack to remove indentation before exec'ing m = re_match(line_re, buf) # single line statement then? if m: exec(m) # execute the buffer, reset it and start parsing buf = "" # else no match! add a line more to the buffer and try again 

Salida:

 morten@laptop /tmp $ python p.py single line 0 1 2 BUG, executed outside for-scope, son only run once Should get in here though 

Así que esto es algo de lo que quiero. Este código divide la fuente en sentencias ejecutables y puedo “pausar” entre las sentencias y manipular el entorno. Como se muestra en el código anterior, no puedo averiguar cómo dividir el código correctamente y ejecutarlo nuevamente. Esto me hizo pensar que debería poder usar alguna herramienta para analizar el código y ejecutarlo como quiero. En este momento estoy pensando en ast o pdb como ustedes sugieren.

Una mirada rápida sugiere que ast puede hacer esto, pero parece un poco complejo, así que tendré que profundizar en los documentos. Si pdb puede controlar el flujo programáticamente, esa también puede ser la respuesta.

Actualizar:

Entonces, leí algo más y encontré este tema: ¿Qué trucos geniales se pueden hacer usando sys.settrace?

Busqué usar sys.settrace() , pero no parece ser el camino a seguir. Me estoy convenciendo cada vez más de que necesito usar el módulo ast para obtener el control más fino que quisiera. FWIW aquí está el código para usar settrace() para alcanzar el máximo dentro de las funciones del scope de la función:

 import sys def trace_func(frame,event,arg): print "trace locals:" for l in frame.f_locals: print "\t%s = %s" % (l, frame.f_locals[l]) def dummy(ls): for l in ls: pass sys.settrace(trace_func) x = 5 dummy([1, 2, 3]) print "whatisthisidonteven-" 

salida:

 morten@laptop /tmp $ python t.py trace locals: ls = [1, 2, 3] whatisthisidonteven- trace locals: item =  selfref =  trace locals: item =  selfref =  

ACTUALIZAR:

De acuerdo, parece que lo resolví … 🙂 He escrito un analizador simple que inyecta una statement entre cada línea de código y luego ejecuta el código. Esta statement es una llamada de función que captura y guarda el entorno local en su estado actual.

Estoy trabajando en un editor de texto Tkinter con dos ventanas que harán lo que Bret Victor hace en su binarySearch-demo. Casi termino 🙂

Para un seguimiento simple, te sugiero que uses pdb . He encontrado que es bastante razonable para la mayoría de los propósitos de depuración / paso a paso. Para su ejemplo:

 import pdb ... xs = [] pdb.set_trace() for x in xrange(10): xs.append(x) 

Ahora su progtwig se detendrá en la set_trace() a set_trace() y puede usar n o s para recorrer su código mientras se ejecuta. AFAIK pdb está utilizando bdb como backend.

Veo que se te ha ocurrido algo que funciona para ti, pero pensé que valdría la pena mencionar ‘pyscripter’. http://code.google.com/p/pyscripter/

Soy bastante nuevo en Python, pero me resulta muy útil
simplemente haga clic más allá de la línea que tiene una variable que quiero verificar,
luego presione f4 para ejecutarlo en modo depurador.
Después de eso, solo puedo mover el mouse sobre la variable y aparece
una información sobre herramientas que tiene los valores de la variable.

También puede realizar un solo paso a través del script con f7 como se describe aquí:
http://openbookproject.net/thinkcs/python/english3e/functions.html#flow-of-execution
(ver ‘Observar el flujo de ejecución en acción’)

Aunque cuando seguí el ejemplo, todavía entró en el módulo de tortuga por alguna razón.

descarga eclipse + pydev y ejecútalo en modo debug …