web-dev-qa-db-ja.com

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

プログラムでpythonソースコードを編集します。基本的に、.pyファイル、 [〜#〜] ast [〜#〜] を生成し、変更されたpythonソースコード(つまり、別の.pyファイル)。

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

更新:私がこれをしたい理由は、Pythonの Mutation testing library を書きたいです。主にステートメント/式を削除し、テストを再実行し、何が壊れているかを確認することです。

151
Rory

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

これらのツールは両方とも、 lib2to3 ライブラリを使用します。これは、pythonパーサー/コンパイラマシンの実装であり、ソースのコメントをソースから往復-> AST->ソース。

ropeプロジェクト は、変換などのリファクタリングをさらに行う場合に、ニーズを満たすことができます。

ast モジュールは他のオプションです。 構文ツリーを「解析解除」する方法の古い例がありますcode (パーサーモジュールを使用)。ただし、astモジュールは、コードでAST変換を実行してからコードオブジェクトに変換する場合に便利です。

redbaron プロジェクトも適切かもしれません(ht Xavier Combelle)

68
Ryan

組み込みの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()を呼び出し、結果のコードオブジェクトを実行するだけで実行できます。

55
Brian

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

  • ユーザーが実際に使用する.pyファイルを生成したい場合、おそらくフォームに入力してプロジェクトに挿入する便利な.pyファイルを取得できるようにするには、それをASTを返します すべての書式設定(関連する行のセットをグループ化することでPythonを読みやすくする空白行を考えてください) ( astノードにはlinenoおよびcol_offset属性があります )コメント。代わりに、おそらくテンプレートエンジン( Djangoテンプレート言語 はテキストファイルでも簡単にテンプレート化できるように設計されています)を使用して.pyファイルをカスタマイズするか、Rick Copelandの MetaPython 拡張機能。

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

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

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

19
Brandon Rhodes

別の答えでastorパッケージを使用することを提案しましたが、それ以来 astunparse と呼ばれる最新のAST非解析パッケージを見つけました。 =:

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


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

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

17
argentpepper

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

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

astの紹介

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

API ast.parse()を呼び出すだけで、pythonコード(文字列で表される))を解析できます。これにより、Abstract Syntax Tree(AST)構造体へのハンドルが返されます。この構造をバックアップし、上記のように実行します。

もう1つの非常に有用なAPIはast.dump()です。これは、AST全体を文字列形式でダンプします。ツリー構造の検査に使用でき、デバッグに非常に役立ちます。

On 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 vs. Python 3.5とtype ASTノードの違い)のprintステートメントの構文の違いに注意してください。それぞれの木で。


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

ここで、python astモジュールによるコードの変更の例を見てみましょう。AST構造を変更するためのメインツールは_ast.NodeTransformer_クラス:ASTを変更する必要があるときはいつでも、そのサブクラスからサブクラスを作成し、それに応じてNode Transformations(s))を記述する必要があります。

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

Fun call converterユーティリティへのステートメントの印刷: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 __== '__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 __== '__main__':
    print "I am in main"
    main()
_

上記の変換はastチュートリアル専用であり、実際のシナリオでは、print " x is %s" % ("Hello Python")などのすべての異なるシナリオを調べる必要があることに注意してください。

8
ViFI

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

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

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

6
paluh

他の答えの1つcodegenを推奨しますが、これは astor に取って代わられたようです。 astor on 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
argentpepper

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

もちろん、目的の言語を解析でき、それでもパターン指向の変換を実行できるプログラム変換エンジンが必要です。 DMS Software Reengineering Toolkit は、それを実行できるシステムであり、Pythonおよび他のさまざまな言語を処理します。

これを参照してください SOのDMS解析の例についての回答AST for Pythonコメントのキャプチャ 。DMSは、 AST、およびコメントを含む有効なテキストを再生成します。独自の書式設定規則を使用して(これらを変更できます)ASTをprettyprintするか、元の行と列の情報を使用して「忠実な印刷」を行い、元のレイアウト(新しいコードが挿入されるレイアウトの変更は避けられません)。

DMSでPythonの「突然変異」ルールを実装するには、次のように記述できます。

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

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

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

2
Ira Baxter

同様のニーズがありましたが、他の回答では解決できませんでした。そのため、このライブラリ 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

お役に立てれば!

2
DS.

私はこれに男爵を使用していましたが、現在は最新のPythonであるため、parsoに切り替えました。それは素晴らしく機能します。

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

0
boxed