Analyser a.py fichier, Lire L'AST, le modifier, puis réécrire le code source modifié
Je veux éditer par programmation le code source python. Fondamentalement, je veux lire un fichier .py
, générer le AST , puis réécrire le code source Python modifié (c'est-à-dire un autre fichier .py
).
Il existe des moyens d'analyser/compiler le code source python en utilisant des modules Python standard, tels que ast
ou compiler
. Cependant, je ne pense pas qu'aucun d'entre eux supporte des moyens de modifier le code source (par exemple supprimer cette déclaration de fonction), puis réécrire la modification code source python.
UPDATE: la raison pour laquelle je veux le faire est que j'aimerais écrire une bibliothèque de tests de Mutation pour python, principalement en supprimant des instructions / expressions, en réexécutant des tests et en voyant ce qui se casse.
10 réponses
Pythoscope le fait dans les cas de test qu'il génère automatiquement tout comme l'outil 2to3 pour Python 2.6 (il convertit python 2.X source en python 3.x source).
Ces deux outils utilisent la bibliothèque lib2to3 qui est une implémentation de l'analyseur / compilateur python qui peut conserver les commentaires dans la source quand il est arrondi à partir de source - > AST - > source.
Le corde projet peut répondre à vos besoins si vous voulez faire plus refactoring comme des transformations.
Le module ast est votre autre option, et Il y a un exemple plus ancien de comment "analyser" les arbres de syntaxe dans le code (en utilisant le module d'analyseur). Mais le module ast
est plus utile lors d'une transformation AST sur du code qui est ensuite transformé en un objet de code.
Le projet redbaron peut également être un bon ajustement (HT Xavier Combelle)
Le module AST intégré ne semble pas avoir de méthode pour le convertir en source. Cependant, le modulecodegen fournit ici une jolie imprimante pour l'ast qui vous permettrait de le faire. par exemple.
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))
Cela va imprimer:
def foo():
return 42
Notez que vous risquez de perdre le formatage exact et les commentaires, car ceux-ci ne sont pas conservés.
Cependant, vous n'en aurez peut-être pas besoin. Si tout ce dont vous avez besoin est d'exécuter l'AST remplacé, vous pouvez le faire simplement en appelant compile() sur l'ast, et exécuter l'objet de code résultant.
Vous n'avez peut - être pas besoin de générer à nouveau du code source. C'est un peu dangereux pour moi de le dire, bien sûr, puisque vous n'avez pas réellement expliqué pourquoi vous pensez avoir besoin de générer un fichier. py plein de code; mais:
Si vous voulez générer un fichier .py que les gens vont réellement utiliser, peut-être pour qu'ils puissent remplir un formulaire et obtenir un fichier .py utile à insérer dans leur projet, alors vous ne voulez pas le changer en AST et retour parce que vous perdrez
tout le formatage (pensez à la lignes vides qui rendent Python Si lisible en regroupant les ensembles de lignes connexes ensemble)(les nœuds ast ont des Commentaireslineno
etcol_offset
attributs). Au lieu de cela, vous voudrez probablement utiliser un moteur de modèle (le langage de modèle Django, par exemple, est conçu pour faciliter la création de modèles de fichiers texte) pour personnaliser le fichier .PY, ou bien utiliser L'extension MetaPython de Rick Copeland.Si vous essayez d'effectuer une modification lors de la compilation d'un module, notez que vous n'avez pas à revenir au texte; vous pouvez simplement compiler L'AST directement au lieu de le retourner dans un fichier .py.
Mais dans presque tous les cas, vous essayez probablement de faire quelque chose de dynamique qu'un langage comme Python rend très facile, sans écrire de nouveaux fichiers. py! Si vous développez votre question pour nous faire savoir ce que vous voulez réellement accomplir, les nouveaux fichiers. py ne seront probablement pas impliqués dans la réponse du tout; j'ai vu des centaines de Projets Python faisant des centaines de choses du monde réel, et pas un seul d'entre eux avait besoin de jamais écrire un fichier .py. Donc, je dois admettre, je suis un peu sceptique que vous avez trouvé le premier bon cas d'utilisation. :-)
Update: maintenant que vous avez expliqué ce que vous essayez de faire, je serais tenté de simplement opérer sur L'AST de toute façon. Vous voudrez muter en supprimant, pas les lignes d'un fichier (ce qui pourrait entraîner des demi-instructions qui meurent simplement avec une SyntaxError), mais entières déclarations-et quel meilleur endroit pour le faire que dans L'AST?
Dans une réponse différente, j'ai suggéré d'utiliser le paquet astor
, mais j'ai depuis trouvé un paquet AST unsing plus à jour appelé astunparse
:
>>> import ast
>>> import astunparse
>>> print(astunparse.unparse(ast.parse('def foo(x): return 2 * x')))
def foo(x):
return (2 * x)
J'ai testé ceci sur Python 3.5.
J'ai créé récemment assez stable (le noyau est vraiment bien testé) et un morceau de code extensible qui génère du code à partir de ast
tree: https://github.com/paluh/code-formatter .
J'utilise mon projet comme base pour un petit plugin vim (que j'utilise tous les jours), donc mon but est de générer du code python vraiment agréable et lisible.
P.S.
J'ai essayé d'étendre codegen
mais son architecture est basée sur l'interface ast.NodeVisitor
, donc les formatters (visitor_
méthodes) ne sont que des fonctions. J'ai trouvé cette structure assez limitante et difficile à optimiser (dans le cas d'expressions longues et imbriquées, il est plus facile de conserver l'arborescence des objets et de mettre en cache certains résultats partiels - d'une autre manière, vous pouvez atteindre la complexité exponentielle si vous voulez rechercher la meilleure mise en page). Mais codegen
Comme chaque morceau du travail de mitsuhiko (que j'ai Lu) est très bien écrit et concis.
L'analyse et la modification de la structure du code sont certainement possibles à l'aide du module ast
et je vais le montrer dans un exemple dans un instant. Cependant, l'écriture du code source modifié n'est pas possible avec le seul module ast
. Il y a d'autres modules disponibles pour ce travail comme un ici.
NOTE: L'exemple ci-dessous peut être traité comme un tutoriel d'introduction sur l'utilisation du module ast
mais un guide plus complet sur l'utilisation du module ast
est disponible ici à tutoriel sur les serpents des arbres verts et documentation officielle sur le module ast
.
Introduction à la ast
:
>>> import ast
>>> tree = ast.parse("print 'Hello Python!!'")
>>> exec(compile(tree, filename="<ast>", mode="exec"))
Hello Python!!
Vous pouvez analyser le code python (représenté en chaîne) en appelant simplement L'API ast.parse()
. Cela renvoie la structure handle to Abstract Syntax Tree (AST). Fait intéressant, vous pouvez compiler cette structure et l'exécuter comme indiqué ci-dessus.
Une autre API très utile est ast.dump()
qui vide l'AST entier sous une forme de chaîne. Il peut être utilisé pour inspecter la structure arborescente et est très utile dans le débogage. Par exemple,
Sur 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)])"
Sur 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=[]))])"
Notez la différence de syntaxe pour l'instruction print dans Python 2.7 par rapport à Python 3.5 et la différence de type de nœud AST dans les arbres respectifs.
Comment modifier le code à l'aide ast
:
Maintenant, regardons un exemple de modification du code python par le module ast
. L'outil principal pour modifier la structure AST est ast.NodeTransformer
classe. Chaque fois que l'on a besoin de modifier L'AST, il / elle doit en sous-classer et écrire la(Les) Transformation (s) de nœud en conséquence.
Pour notre exemple, essayons d'écrire un utilitaire simple qui transforme les instructions Python 2, print en appels de fonction Python 3.
Déclaration D'impression à L'utilitaire de convertisseur D'appel amusant: 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])
Cet utilitaire peut être essayé sur un petit fichier d'exemple, tel que celui ci-dessous, et cela devrait bien fonctionner.
Fichier D'entrée de Test : 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()
Veuillez noter que la transformation ci-dessus est uniquement pour le but du tutoriel ast
et dans un scénario de cas réel, il faudra regarder tous les différents scénarios tels que print " x is %s" % ("Hello Python")
.
L'Une des autres réponses recommande codegen
, ce qui semble avoir été remplacé par astor
. La version de astor
sur PyPI (version 0.5 à ce jour) semble être un peu obsolète, donc vous pouvez installer la version de développement de astor
comme suit.
pip install git+https://github.com/berkerpeksag/astor.git#egg=astor
Ensuite, vous pouvez utiliser astor.to_source
pour convertir un AST Python en code source Python lisible par l'homme:
>>> import ast
>>> import astor
>>> print(astor.to_source(ast.parse('def foo(x): return 2 * x')))
def foo(x):
return 2 * x
J'ai testé ceci sur Python 3.5.
Un système de Transformation de programme est un outil qui analyse le texte source, construit les AST, vous permet de les modifier en utilisant des transformations source à source ("si vous voyez Ce modèle, remplacez-le par ce modèle"). De tels outils sont idéaux pour faire la mutation des codes sources existants ,qui sont juste "si vous voyez Ce modèle, remplacez par une variante de modèle".
Bien sûr, vous avez besoin d'un moteur de transformation de programme qui peut analyser la langue qui vous intéresse, et toujours faire le transformations dirigées par un motif. Notre DMS Software Reengineering Toolkit est un système qui peut le faire, et gère Python, et une variété d'autres langages.
Voir ceci alors répondez à un exemple D'AST analysé par DMS pour Python capturant les commentaires avec précision. DMS peut apporter des modifications à L'AST, et régénérer le texte valide, y compris les commentaires. Vous pouvez lui demander de prettyprint L'AST, en utilisant ses propres conventions de formatage (vous pouvez les modifier), ou faire " fidélité printing", qui utilise les informations de ligne et de colonne d'origine pour préserver au maximum la mise en page d'origine (un changement de mise en page où un nouveau code est inséré est inévitable).
Pour implémenter une règle "mutation" pour Python avec DMS, vous pouvez écrire ce qui suit:
rule mutate_addition(s:sum, p:product):sum->sum =
" \s + \p " -> " \s - \p"
if mutate_this_place(s);
Cette règle remplace " + " par " - " de manière syntaxique correcte; elle fonctionne sur L'AST et ne touchera donc pas les chaînes ou les commentaires qui semblent corrects. La condition supplémentaire sur "mutate_this_place" est de vous laisser contrôlez la fréquence à laquelle cela se produit; vous ne voulez pas muter chaque place dans le programme.
Vous voudriez évidemment un tas de règles comme celle - ci qui détectent diverses structures de code, et les remplacent par les versions mutées. DMS est heureux d'appliquer un ensemble de règles. L'AST muté est alors jolieimprimé.
Nous avions un besoin similaire, qui n'a pas été résolu par d'autres réponses ici. Nous avons donc créé une bibliothèque pour cela, ASTTokens , qui prend un arbre AST produit avec les modules ast ou astroid, et le marque avec les plages de texte dans le code source original.
Il ne fait pas de modifications de code directement, mais ce n'est pas difficile à ajouter, car il vous indique la plage de texte que vous devez modifier.
Par exemple, cela encapsule un appel de fonction dans WRAP(...)
, préserver les commentaires et tout le reste:
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:])
Produit:
def foo(): # Test
'''My func'''
WRAP(log("hello world")) # Print
Espérons que cela aide!
J'avais l'habitude d'utiliser baron pour cela, mais je suis maintenant passé à parso parce qu'il est à jour avec Python moderne. Il fonctionne très bien.
J'en avais aussi besoin pour un testeur de mutation. C'est vraiment assez simple d'en faire un avec parso, consultez mon code à https://github.com/boxed/mutmut