.pyファイルを解析し、ASTを読み取って変更し、変更したソースコードを書き戻す


168

プログラムでpythonソースコードを編集したい。基本的に、.pyファイルを読み取ってASTを生成し、変更されたpythonソースコード(つまり、別の.pyファイル)を書き戻します。

astまたはなど、標準のPythonモジュールを使用してPythonソースコードを解析/コンパイルする方法がありますcompiler。ただし、ソースコードを変更して(この関数宣言を削除するなど)、変更したpythonソースコードを書き戻す方法をサポートしているものはないと思います。

更新:これを実行したい理由は、Python用のミューテーションテストライブラリを記述したいためです。ほとんどの場合、ステートメント/式を削除し、テストを再実行して、何が壊れるかを確認します。


4
バージョン2.6で廃止:コンパイラパッケージはPython 3.0で削除されました。
dfa 2009

1
ソースを編集できませんか?デコレータを作成できないのはなぜですか?
S.Lott、2009

3
聖なる牛!私は同じテクニックを使用してPythonのミューテーションテスターを作成したいと思っていました(具体的には、鼻のプラグインを作成しています)。
ライアン

2
@ライアンうん私は私が作成したものは何でもオープンソースにします。私たちはこれについて連絡を取り合う必要があります
ロリー

1
間違いなく、Launchpad経由でメールを送信しました。
ライアン、

回答:


73

Pythoscopeは、Python 2.6の2to3ツール(Python 2.xソースをPython 3.xソースに変換する)と同様に、自動生成するテストケースに対してこれを行います。

これらのツールは両方とも、Pythonパーサー/コンパイラー機構の実装であるlib2to3ライブラリーを使用します。これは、ソース-> AST->ソースからラウンドトリップしたときに、ソース内のコメントを保持できます。

ロープのプロジェクトは、あなたが変換のようなより多くのリファクタリングを行いたい場合は、あなたのニーズを満たすことがあります。

ASTのモジュールは、あなたの他のオプションであり、そしてどのように「unparse」構文木バックコードに以前の例があります(パーサーモジュールを使用しては)。ただし、このastコードは、コードをAST変換してからコードオブジェクトに変換する場合に役立ちます。

redbaronのプロジェクトはまた、良好なフィット(HTザビエルCombelle)であってもよいです


5
未解析の例はまだ維持されています。更新されたpy3kバージョンは次のとおり
Janus Troelsen

2
unparse.pyスクリプトに関して-別のスクリプトからそれを使用するのは本当に面倒かもしれません。しかし、(astunparseと呼ばれるパッケージがあるgithubの上は、PyPI上で基本的に適切にパッケージ化されたバージョンです)unparse.py
mbdevpl '18年

推奨オプションとしてparsoを追加して、回答を更新できますか?それはとても良く、更新されました。
ボックス化

59

組み込みのastモジュールには、ソースに変換するメソッドがないようです。ただし、ここでのcodegenモジュールは、astのプリティプリンターを提供します。例えば。

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))

これは印刷されます:

def foo():
    return 42

これらは保持されないため、正確なフォーマットとコメントを失う可能性があることに注意してください。

ただし、必要はありません。置き換えられたASTを実行することだけが必要な場合は、astでcompile()を呼び出し、結果のコードオブジェクトを実行するだけで実行できます。


20
将来これを使用する人のためだけに、codegenはほとんど古くなっており、いくつかのバグがあります。それらのいくつかを修正しました。私はこれをgithubの要点として持っています:gist.github.com/791312
mattbasta

上記のコメントの後の2012年に最新のcodegenが更新されることに注意してください。そのため、codegenが更新されたと思います。@mattbasta
zjffdu

4
astorはcodegenの後継者として維持されているようです
medmunds

20

別の答えで、astorパッケージの使用を提案しましたが、それ以降、より最新のAST解析解除パッケージが見つかりましたastunparse

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


def foo(x):
    return (2 * x)

これをPython 3.5でテストしました。


19

ソースコードを再生成する必要がない場合があります。もちろん、コードでいっぱいの.pyファイルを生成する必要があると思う理由を実際に説明していないので、これは私にとって少し危険です。だが:

  • 人々が実際に使用する.pyファイルを生成したい場合は、おそらく彼らがフォームに記入し、プロジェクトに挿入するのに役立つ.pyファイルを取得できるようにするために、それをASTに変更したくはありません。なぜなら、すべての書式が失われるからです(関連する一連の行をグループ化することでPythonが読みやすくなる空白行を考えてください)astノードはlinenocol_offset属性)コメント。代わりに、おそらくテンプレートエンジン(たとえば、Djangoテンプレート言語はテキストファイルでも簡単にテンプレート化できるように設計されています)を使用して.pyファイルをカスタマイズするか、Rick CopelandのMetaPython拡張機能を使用します。

  • モジュールのコンパイル中に変更を行おうとする場合は、テキストに戻る必要がないことに注意してください。ASTを.pyファイルに戻す代わりに、直接コンパイルすることができます。

  • しかし、ほとんどすべての場合において、新しい.pyファイルを作成せずに、Pythonなどの言語が実際に非常に簡単にする動的なことを実行しようとしている可能性があります。質問を拡張して、実際に達成したいことを私たちに知らせる場合、新しい.pyファイルはおそらく回答にまったく関与しません。私は何百ものPythonプロジェクトが何百もの実際のことを行っているのを見てきましたが、.pyファイルを作成するためにそれらの1つだけが必要だったわけではありません。だから、私は認めなければなりません。私は、あなたが最初の優れたユースケースを見つけたとは少し懐疑的です。:-)

更新:あなたが何をしようとしているのかを説明したので、とにかくASTを操作したくなります。ファイルの行ではなく(SyntaxErrorで単に終了するハーフステートメントになる可能性がある)行を削除することで変異させたいと思いますが、ステートメント全体と、ASTでそれを行うより良い場所は何ですか?


可能な解決策と可能性のある代替案の概要。
ライアン

1
コード生成の実際の使用例:KidとGenshi(私は信じています)は、動的なページの高速レンダリングのためにXMLテンプレートからPythonを生成します。
リックコープランド

10

astモジュールの助けを借りて、コード構造を解析および変更することは確かに可能です。それをすぐに例で示します。ただし、変更されたソースコードを書き戻すことは、astモジュールだけでは不可能です。このジョブには、ここにあるような他のモジュールが利用可能です

注:以下の例は、astモジュールの使用法の入門チュートリアルとして扱うことができますが、モジュールの使用に関するより包括的なガイドastは、Green Tree snakesチュートリアルモジュールの公式ドキュメントでast入手できます

はじめにast

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

APIを呼び出すだけで、Pythonコード(文字列で表される)を解析できますast.parse()。これは、抽象構文ツリー(AST)構造へのハンドルを返します。興味深いことに、この構造をコンパイルして、上記のように実行できます。

別の非常に便利なAPIはast.dump()、AST全体を文字列形式でダンプします。ツリー構造の検査に使用でき、デバッグに非常に役立ちます。例えば、

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)])"

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=[]))])"

Python 2.7とPython 3.5のprintステートメントの構文の違いと、それぞれのツリーのASTノードのタイプの違いに注意してください。


を使用してコードを変更する方法ast

では、astモジュールによるpythonコードの変更の例を見てみましょう。AST構造を変更するための主なツールはast.NodeTransformerクラスです。ASTを変更する必要があるときはいつでも、ASTをサブクラス化し、それに応じてノード変換を書き込む必要があります。

この例では、Python 2のprintステートメントをPython 3関数呼び出しに変換する簡単なユーティリティを作成してみましょう。

Funコールコンバーターユーティリティへのステートメントの印刷: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])

このユーティリティは、以下のような小さなサンプルファイルで試すことができ、正常に動作するはずです。

テスト入力ファイル: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()

上記の変換はastチュートリアルのみを目的としているため、実際のシナリオでは、などのさまざまなシナリオをすべて確認する必要があることに注意してくださいprint " x is %s" % ("Hello Python")


6

私は最近、非常に安定していて(コアは本当に十分にテストされています)、astツリーからコードを生成する拡張可能なコードを作成しました:https : //github.com/paluh/code-formatter

私は自分のプロジェクトを小さなvimプラグイン(毎日使用しています)のベースとして使用しているので、私の目標は本当に素晴らしく読みやすいpythonコードを生成することです。

PS私は拡張しようとしましたcodegenが、そのアーキテクチャはast.NodeVisitorインターフェイスに基づいているため、フォーマッタ(visitor_メソッド)は単なる関数です。この構造は非常に限定的で最適化が難しいことがわかりました(長くてネストされた式の場合、オブジェクトツリーを保持し、一部の結果をキャッシュする方が簡単です。他の方法では、最適なレイアウトを検索したい場合は、指数関数的な複雑さに達する可能性があります)。しかし codegen(私が読んだ)光彦の仕事のすべての部分として非常によく書かれており、簡潔されます。


4

他の回答の1つが推奨codegenしていますが、これはに取って代わられたようastorです。バージョンastorは、PyPIに(これを書いている時点ではバージョン0.5)を使用すると、開発版のインストールできるように少しは、同様に古くなっているようだastor、次のように。

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

次にastor.to_source、Python ASTを人間が読めるPythonソースコードに変換するために使用できます。

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

これをPython 3.5でテストしました。


4

2019年にこれを見ている場合は、このlibcs​​tを使用できます パッケージをます。astに似た構文があります。これは魅力のように機能し、コード構造を保持します。これは、コメント、空白、改行などを保存する必要があるプロジェクトに基本的に役立ちます。

保存するコメントや空白などを気にする必要がない場合は、astとastorの組み合わせが適切に機能します。


2

私たちにも同様のニーズがありましたが、それはここの他の回答では解決されませんでした。そこで、このためのライブラリASTTokensを作成しました。これは、astまたはastroidモジュールで生成されたASTツリーを受け取り、元のソースコードのテキストの範囲でマークします。

コードを直接変更することはありませんが、変更する必要のあるテキストの範囲を通知するため、上に追加するのは難しくありません。

たとえば、これは関数呼び出しをでラップしWRAP(...)、コメントやその他すべてを保持します。

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:])

生成する:

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

お役に立てれば!


1

プログラム変換システム、ソーステキストを解析ツールであるASTのを構築し、あなたが(「あなたはこのパターンを見た場合、そのパターンによってそれを置き換える」)ソース・ソース間の変換を使用してそれらを変更することができます。このようなツールは、「このパターンが表示された場合は、パターンバリアントで置き換える」という既存のソースコードの変更を行うのに最適です。

もちろん、関心のある言語を解析し、パターン指向の変換を実行できるプログラム変換エンジンが必要です。当社のDMSソフトウェアリエンジニアリングツールキットは、それを実行できるシステムであり、Pythonやその他のさまざまな言語を処理します。

コメントをキャプチャするPythonのDMS解析ASTの例については、このSOの回答を参照してください正確に。DMSはASTに変更を加え、コメントを含む有効なテキストを再生成できます。独自のフォーマット規則を使用してASTをプリティプリントするように要求するか(これらを変更できます)、または元の行と列の情報を使用して元のレイアウトを最大限に維持する「忠実な印刷」を行うことができます(新しいコードが配置されているレイアウトの一部の変更)挿入は避けられません)。

DMSを使用してPythonの「変換」ルールを実装するには、次のように記述します。

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

このルールは、構文的に正しい方法で「+」を「-」に置き換えます。ASTで動作するため、たまたま正しく見える文字列やコメントには触れません。「mutate_this_place」の追加条件は、これが発生する頻度を制御できるようにすることです。プログラムのすべての場所を変更する必要はありません。

明らかに、さまざまなコード構造を検出し、それらを変更されたバージョンに置き換える、このようなより多くのルールが必要です。DMSは一連のルールを適用します。次に、変異したASTがプリティプリントされます。


私はこの答えを4年間見ていない。うわー、それは数回反対投票されています。それはOPの質問に直接答えるだけでなく、彼がやりたい突然変異を行う方法を示すので、それは本当に素晴らしいです。私は、どの反対投票者も、なぜ反対投票したのを説明しようとは思わないでしょう。
Ira Baxter 2014

4
非常に高価なクローズドソースツールを促進するため。
Zoran Pavlovic

@ZoranPavlovic:では、技術的な正確性や実用性に異議を唱えていませんか?
Ira Baxter、

2
@ゾラン:オープンソースのライブラリがあるとは言わなかった。彼は、Pythonのソースコードを(ASTを使用して)変更したいと考えていましたが、彼が見つけたソリューションではそれができませんでした。これはそのようなソリューションです。Java on Pythonなどの言語で記述されたプログラムで商用ツールを使用しているとは思わないのですか?
Ira Baxter

1
私は反対投票者ではありませんが、投稿は広告のようなものです。答えを改善するために、あなたは製品と提携していることを開示することができます
wim

0

以前はこれに男爵を使用していましたが、現代のpythonで最新であるため、今はparsoに切り替えました。それは素晴らしい働きをします。

突然変異テスターに​​もこれが必要でした。parsoで作成するのは非常に簡単です。https://github.com/boxed/mutmutで私のコードを確認してください

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.