python compiler-construction abstract-syntax-tree

plot title python



Analice un archivo.py, lea el AST, modifíquelo y luego vuelva a escribir el código fuente modificado (9)

Analizar y modificar la estructura del código es ciertamente posible con la ayuda del módulo ast y lo mostraré en un ejemplo en un momento. Sin embargo, escribir el código fuente modificado no es posible solo con el módulo ast . Hay otros módulos disponibles para este trabajo, como uno aquí .

NOTA: El ejemplo a continuación puede tratarse como un tutorial introductorio sobre el uso del módulo ast pero una guía más completa sobre el uso del módulo ast está disponible aquí en el tutorial de Green Tree snakes y en la documentación oficial del módulo ast .

Introducción a ast :

>>> import ast >>> tree = ast.parse("print ''Hello Python!!''") >>> exec(compile(tree, filename="<ast>", mode="exec")) Hello Python!!

Puede analizar el código python (representado en cadena) simplemente llamando a la API ast.parse() . Esto devuelve el identificador a la estructura del árbol de sintaxis abstracta (AST). Curiosamente, puede compilar esta estructura y ejecutarla como se muestra arriba.

Otra API muy útil es ast.dump() que vuelca todo el AST en forma de cadena. Se puede usar para inspeccionar la estructura del árbol y es muy útil en la depuración. Por ejemplo,

En Python 2.7:

>>> import ast >>> tree = ast.parse("print ''Hello Python!!''") >>> ast.dump(tree) "Module(body=[Print(dest=None, values=[Str(s=''Hello Python!!'')], nl=True)])"

En Python 3.5:

>>> import ast >>> tree = ast.parse("print (''Hello Python!!'')") >>> ast.dump(tree) "Module(body=[Expr(value=Call(func=Name(id=''print'', ctx=Load()), args=[Str(s=''Hello Python!!'')], keywords=[]))])"

Observe la diferencia en la sintaxis para la declaración de impresión en Python 2.7 frente a Python 3.5 y la diferencia en el tipo de nodo AST en los árboles respectivos.

Cómo modificar el código usando ast :

Ahora, echemos un vistazo a un ejemplo de modificación del código python por un módulo ast . La herramienta principal para modificar la estructura AST es la clase ast.NodeTransformer . Cada vez que se necesita modificar el AST, él / ella necesita una subclase a partir de él y escribir la (s) Transformación (es) del Nodo en consecuencia.

Para nuestro ejemplo, intentemos escribir una utilidad simple que transforme el Python 2, imprima declaraciones en las llamadas a funciones de Python 3.

Imprimir declaración a la utilidad del convertidor de llamadas divertidas: print2to3.py:

#!/usr/bin/env python '''''' This utility converts the python (2.7) statements to Python 3 alike function calls before running the code. USAGE: python print2to3.py <filename> '''''' import ast import sys class P2to3(ast.NodeTransformer): def visit_Print(self, node): new_node = ast.Expr(value=ast.Call(func=ast.Name(id=''print'', ctx=ast.Load()), args=node.values, keywords=[], starargs=None, kwargs=None)) ast.copy_location(new_node, node) return new_node def main(filename=None): if not filename: return with open(filename, ''r'') as fp: data = fp.readlines() data = ''''.join(data) tree = ast.parse(data) print "Converting python 2 print statements to Python 3 function calls" print "-" * 35 P2to3().visit(tree) ast.fix_missing_locations(tree) # print ast.dump(tree) exec(compile(tree, filename="p23", mode="exec")) if __name__ == ''__main__'': if len(sys.argv) <=1: print ("/nUSAGE:/n/t print2to3.py <filename>") sys.exit(1) else: main(sys.argv[1])

Esta utilidad se puede probar en un pequeño archivo de ejemplo, como el siguiente, y debería funcionar bien.

Archivo de entrada de prueba: py2.py

class A(object): def __init__(self): pass def good(): print "I am good" main = good if __name__ == ''__main__'': print "I am in main" main()

Tenga en cuenta que la transformación anterior es solo para fines tutoriales y, en el caso real, uno tendrá que considerar todos los diferentes escenarios, como print " x is %s" % ("Hello Python") .

Quiero editar programáticamente el código fuente de Python. Básicamente, quiero leer un archivo .py , generar el AST y luego volver a escribir el código fuente de python modificado (es decir, otro archivo .py ).

Hay formas de analizar / compilar el código fuente de Python utilizando módulos de python estándar, como ast o compiler . Sin embargo, no creo que ninguno de ellos admita formas de modificar el código fuente (por ejemplo, eliminar esta declaración de función) y luego escribir de nuevo el código fuente de python modificador.

ACTUALIZACIÓN: La razón por la que quiero hacer esto es que me gustaría escribir una biblioteca de pruebas de mutaciones para Python, principalmente eliminando declaraciones / expresiones, volviendo a ejecutar las pruebas y viendo qué se rompe.


El módulo incorporado ast no parece tener un método para convertir de nuevo a fuente. Sin embargo, el módulo codegen aquí proporciona una impresora bonita para el ast que le permitiría hacerlo. p.ej.

import ast import codegen expr=""" def foo(): print("hello world") """ p=ast.parse(expr) p.body[0].body = [ ast.parse("return 42").body[0] ] # Replace function body with "return 42" print(codegen.to_source(p))

Esto se imprimirá:

def foo(): return 42

Tenga en cuenta que puede perder el formato exacto y los comentarios, ya que estos no se conservan.

Sin embargo, puede que no sea necesario. Si todo lo que necesita es ejecutar el AST reemplazado, puede hacerlo simplemente llamando a compile () en el momento y ejecutando el objeto de código resultante.


En una respuesta diferente, sugerí usar el paquete astor , pero desde entonces he encontrado un paquete AST sin análisis más astunparse llamado astunparse :

>>> import ast >>> import astunparse >>> print(astunparse.unparse(ast.parse(''def foo(x): return 2 * x''))) def foo(x): return (2 * x)

He probado esto en Python 3.5.


Es posible que no necesite volver a generar el código fuente. Es un poco peligroso para mí decirlo, por supuesto, ya que no has explicado por qué crees que necesitas generar un archivo .py lleno de código; pero:

  • Si desea generar un archivo .py que las personas realmente usarán, tal vez para que puedan completar un formulario y obtener un archivo .py útil para insertar en su proyecto, entonces no desea cambiarlo a AST y de vuelta porque perderá todo el formato (piense en las líneas en blanco que hacen que Python sea tan legible agrupando conjuntos de líneas relacionados) (los nodos tienen lineno y col_offset ). En cambio, es probable que desee utilizar un motor de plantillas (el lenguaje de plantilla de Django , por ejemplo, está diseñado para facilitar la creación de plantillas incluso en archivos de texto) para personalizar el archivo .py o utilizar la extensión MetaPython Rick Copeland.

  • Si intenta realizar un cambio durante la compilación de un módulo, tenga en cuenta que no tiene que volver al texto; simplemente puede compilar el AST directamente en lugar de convertirlo nuevamente en un archivo .py.

  • Pero en casi todos los casos, probablemente estés tratando de hacer algo dinámico que un lenguaje como Python realmente hace muy fácil, ¡sin escribir nuevos archivos .py! Si expande su pregunta para hacernos saber lo que realmente desea lograr, es probable que los nuevos archivos .py no se incluyan en la respuesta; He visto cientos de proyectos de Python haciendo cientos de cosas del mundo real, y ninguno de ellos necesitaba escribir un archivo .py. Entonces, debo admitir, soy un poco escéptico de que hayas encontrado el primer buen caso de uso. :-)

Actualización: ahora que ya has explicado lo que estás tratando de hacer, me sentiría tentado a operar el AST de todos modos. Querrás mutar eliminando, no las líneas de un archivo (lo que podría dar como resultado declaraciones a medias que simplemente mueren con un SyntaxError), sino declaraciones completas, y ¿qué mejor lugar para hacerlo que en AST?


Recientemente he creado código bastante estable (el núcleo está realmente probado) y extensible que genera código desde ast tree: https://github.com/paluh/code-formatter .

Estoy usando mi proyecto como base para un pequeño complemento vim (que estoy usando todos los días), así que mi objetivo es generar un código python realmente agradable y legible.

PD: He tratado de extender codegen pero su arquitectura se basa en la interfaz ast.NodeVisitor , por lo que los formateadores (métodos de visitor_ ) son solo funciones. Encontré esta estructura bastante limitante y difícil de optimizar (en el caso de expresiones largas y anidadas, es más fácil mantener los árboles en árbol y almacenar en caché algunos resultados parciales; de otra forma, puede alcanzar la complejidad exponencial si desea buscar el mejor diseño). PERO codegen como cada pieza del trabajo de mitsuhiko (que he leído) está muy bien escrita y es concisa.


Tuvimos una necesidad similar, que no fue resuelta por otras respuestas aquí. Así que creamos una biblioteca para esto, ASTTokens , que toma un árbol AST producido con los módulos ast o astroid , y lo marca con los rangos de texto en el código fuente original.

No hace modificaciones de código directamente, pero eso no es difícil de agregar en la parte superior, ya que te indica el rango de texto que necesitas modificar.

Por ejemplo, esto envuelve una llamada de función en WRAP(...) , preservando los comentarios y todo lo demás:

example = """ def foo(): # Test ''''''My func'''''' log("hello world") # Print """ import ast, asttokens atok = asttokens.ASTTokens(example, parse=True) call = next(n for n in ast.walk(atok.tree) if isinstance(n, ast.Call)) start, end = atok.get_text_range(call) print(atok.text[:start] + (''WRAP(%s)'' % atok.text[start:end]) + atok.text[end:])

Produce:

def foo(): # Test ''''''My func'''''' WRAP(log("hello world")) # Print

¡Espero que esto ayude!


Un Sistema de Transformación de Programas es una herramienta que analiza el texto fuente, construye AST, le permite modificarlos usando transformaciones fuente-fuente ("si ve este patrón, reemplácelo por ese patrón"). Estas herramientas son ideales para hacer mutaciones de códigos fuente existentes, que son solo "si ve este patrón, reemplácelo por una variante de patrón".

Por supuesto, necesita un motor de transformación de programas que pueda analizar el idioma que le interesa y seguir realizando las transformaciones dirigidas por patrones. Nuestro DMS Software Reengineering Toolkit es un sistema que puede hacer eso, y maneja Python, y una variedad de otros lenguajes.

Vea esta respuesta SO para obtener un ejemplo de AST analizado por DMS para que Python capture los comentarios con precisión. DMS puede realizar cambios en AST y regenerar texto válido, incluidos los comentarios. Puede pedirle que imprima correctamente el AST, utilizando sus propias convenciones de formato (puede cambiarlas), o haga "impresión de fidelidad", que utiliza la línea original y la información de columna para preservar al máximo el diseño original (algunos cambios en el diseño donde el nuevo código se inserta es inevitable).

Para implementar una regla de "mutación" para Python con DMS, puede escribir lo siguiente:

rule mutate_addition(s:sum, p:product):sum->sum = " /s + /p " -> " /s - /p" if mutate_this_place(s);

Esta regla reemplaza "+" con "-" de una manera sintácticamente correcta; opera en el AST y por lo tanto no tocará cadenas o comentarios que se vean correctos. La condición adicional en "mutate_this_place" es permitirle controlar la frecuencia con la que ocurre esto; no quiere mutar cada lugar en el programa.

Obviamente, querrás un montón de reglas como esta que detectan varias estructuras de código y las reemplazan por las versiones mutadas. DMS se complace en aplicar un conjunto de reglas. La AST mutada se imprime bastante bien.


Pythoscope hace esto en los casos de prueba que genera automáticamente, al igual que la herramienta 2to3 para python 2.6 (convierte el origen de python 2.x en el origen de python 3.x).

Ambas herramientas utilizan la biblioteca lib2to3 , que es una implementación del mecanismo de compilador / analizador de Python que puede conservar los comentarios en el origen cuando se produce una ronda desde el origen -> AST -> fuente.

El proyecto de cuerda puede satisfacer sus necesidades si desea hacer más refactorización, como transformaciones.

El módulo ast es su otra opción, y hay un ejemplo anterior de cómo "desenredar" los árboles de sintaxis en el código (utilizando el módulo analizador). Pero el módulo ast es más útil cuando se hace una transformación AST en un código que luego se transforma en un objeto de código.

El proyecto redbaron también puede ser una buena opción (ht Xavier Combelle)


share recomienda codegen , que parece haber sido reemplazado por astor . La versión de astor en PyPI (versión 0.5 al momento de escribir esto) parece estar un poco desactualizada también, por lo que puede instalar la versión de desarrollo de astor siguiente manera.

pip install git+https://github.com/berkerpeksag/astor.git#egg=astor

Luego puede usar astor.to_source para convertir Python AST en código fuente de Python legible para el ser humano:

>>> import ast >>> import astor >>> print(astor.to_source(ast.parse(''def foo(x): return 2 * x''))) def foo(x): return 2 * x

He probado esto en Python 3.5.