[python] .py 파일을 구문 분석하고 AST를 읽고 수정 한 다음 수정 된 소스 코드를 다시 작성하십시오.

파이썬 소스 코드를 프로그래밍 방식으로 편집하고 싶습니다. 기본적으로 .py파일 을 읽고 AST를 생성 한 다음 수정 된 파이썬 소스 코드 (예 : 다른 .py파일)를 다시 작성 하려고합니다 .

같은 표준 파이썬 모듈을 사용하여 구문 분석 / 컴파일 파이썬 소스 코드에 가지 방법이 있습니다 ast또는 compiler. 그러나 나는 소스 코드를 수정하는 방법 (예 :이 함수 선언 삭제)을 수정 한 다음 수정 파이썬 소스 코드를 다시 쓰는 방법을 지원하지 않는다고 생각합니다.

업데이트 :이 작업을 수행하는 이유는 주로 명령문 / 표현식을 삭제하고 테스트를 다시 실행하고 중단되는 부분을 확인하여 Python에 대한 돌연변이 테스트 라이브러리 를 작성하고 싶습니다 .



답변

Pythoscope2to3 과 마찬가지로 자동으로 생성되는 테스트 사례에 대해이 작업 을 수행합니다 . Python 2.6 용 도구 (python 2.x 소스를 python 3.x 소스로 변환) .

이 두 도구는 소스-> AST-> 소스에서 라운드 트립 될 때 소스에서 주석을 유지할 수있는 파이썬 파서 / 컴파일러 기계의 구현 인 lib2to3 라이브러리를 사용합니다 .

로프 프로젝트는 당신이 변환 같은 더 리팩토링을 수행하려는 경우 귀하의 요구를 충족 할 수 있습니다.

AST의 모듈은 다른 옵션이며, 코드로 백업하는 방법 “unparse”구문 트리에의 이전 예를있다 (파서 모듈을 사용하여이). 하지만ast 모듈은 코드에서 AST 변환을 수행 한 다음 코드 객체로 변환 할 때 더 유용합니다.

redbaron의 프로젝트는 또한 좋은 적합 할 수있다 (HT 자비에르 Combelle)


답변

내장 ast 모듈에 소스로 다시 변환하는 방법이없는 것 같습니다. 그러나 여기서 codegen 모듈은 그렇게 할 수있는 훌륭한 프린터를 제공합니다. 예.

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 ()을 호출하고 결과 코드 객체를 실행하면됩니다.


답변

다른 대답으로 astor패키지 사용을 제안 했지만 이후에 AST 구문 분석이없는 최신 패키지를 발견했습니다 astunparse.

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


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

파이썬 3.5에서 이것을 테스트했습니다.


답변

소스 코드를 다시 생성하지 않아도됩니다. 물론 코드로 가득 찬 .py 파일을 생성해야한다고 생각하는 이유를 실제로 설명하지 않았기 때문에이 방법은 다소 위험합니다. 그러나:

  • 사람들이 실제로 사용할 .py 파일을 생성하려는 경우 양식을 작성하고 프로젝트에 삽입 할 유용한 .py 파일을 얻을 수 있도록 AST 파일로 변경하고 싶지 않은 경우 당신은 잃게됩니다 다시 때문에 모든 포맷을 (함께 라인의 관련 세트를 그룹화하여 파이썬이 그렇게 읽을 수 있도록 빈 줄 생각) ( AST 노드가 linenocol_offset속성 ) 주석. 대신 .py 파일을 사용자 지정하기 위해 템플릿 엔진 ( 예를 들어 Django 템플릿 언어 는 텍스트 파일도 쉽게 템플릿을 만들도록 설계되어 있음)을 사용하거나 Rick Copeland의 MetaPython 확장을 사용 하려고 합니다.

  • 모듈을 컴파일하는 동안 변경하려고하면 텍스트로 돌아갈 필요가 없습니다. AST를 다시 .py 파일로 변환하는 대신 AST를 직접 컴파일하면됩니다.

  • 그러나 거의 모든 경우에, 아마도 새로운 .py 파일을 쓰지 않고도 파이썬과 같은 언어가 실제로 매우 쉬운 동적 작업을 시도하고있을 것입니다! 실제로 달성하려는 것을 알려주기 위해 질문을 확장하면 새로운 .py 파일이 응답에 전혀 관여하지 않을 것입니다. 수백 개의 파이썬 프로젝트가 수백 개의 실제 작업을 수행하는 것을 보았으며 .py 파일을 작성하는 데 필요한 단일 프로젝트는 아닙니다. 그래서, 나는 당신이 첫 번째 좋은 유스 케이스를 찾은 것에 대해 약간의 회의론자라는 것을 인정해야합니다. 🙂

업데이트 : 이제 당신이하려는 일을 설명 했으므로 어쨌든 AST에서 작동하고 싶습니다. 파일의 행을 제거하지 않고 (SyntaxError로 간단히 반감 지하는 반문을 초래할 수 있음), 전체 문장을 제거하여 변경하고 싶을 것입니다.


답변

ast모듈 의 도움으로 코드 구조를 파싱하고 수정하는 것이 가능 하며 잠시 후에 예제로 보여 드리겠습니다. 그러나 ast모듈만으로는 수정 된 소스 코드를 다시 작성할 수 없습니다 . 이 작업에 사용할 수있는 다른 모듈이 있습니다 (예 : 여기) .

참고 : 예 아래의 사용에 입문 튜토리얼로 취급 할 수 있습니다 ast모듈 있지만 사용에 대한보다 포괄적 인 가이드 ast모듈은 여기에서 확인할 수있다 그린 트리 튜토리얼 뱀에 공식 문서 ast모듈 .

소개 ast:

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

API를 호출하여 Python 코드 (문자열로 표시)를 구문 분석 할 수 있습니다 ast.parse(). AST (Abstract Syntax Tree) 구조로 핸들을 리턴합니다. 흥미롭게도이 구조를 컴파일하고 위와 같이 실행할 수 있습니다.

또 다른 유용한 API는 ast.dump()전체 AST를 문자열 형식으로 덤프하는 것입니다. 트리 구조를 검사하는 데 사용할 수 있으며 디버깅에 매우 유용합니다. 예를 들어

파이썬 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)])"

파이썬 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모듈 별로 파이썬 코드를 수정하는 예를 살펴 보겠습니다 . AST 구조를 수정하는 주요 도구는 ast.NodeTransformer클래스입니다. AST를 수정해야 할 때마다 AST를 서브 클래 싱하고 이에 따라 노드 변환을 작성해야합니다.

이 예에서는 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 __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")합니다.


답변

나는 최근에 안정적으로 (코어는 실제로 잘 테스트되었습니다) ast트리 에서 코드를 생성하는 확장 가능한 코드 조각을 만들었습니다 : https://github.com/paluh/code-formatter .

나는 작은 vim 플러그인 (매일 사용하고 있음)의 기반으로 프로젝트를 사용하고 있으므로 목표는 정말 훌륭하고 읽을 수있는 파이썬 코드를 생성하는 것입니다.

추신 : 나는 확장하려고 시도했지만 codegen아키텍처는 ast.NodeVisitor인터페이스를 기반으로 하므로 포맷터 ( visitor_메서드)는 함수 일뿐입니다. 이 구조는 상당히 제한적이고 최적화하기가 어렵다는 것을 발견했습니다 (길고 중첩 된 표현식의 경우 객체를 트리로 유지하고 일부 결과를 캐시하는 것이 더 쉽습니다. 다른 방법으로 최상의 레이아웃을 검색하려는 경우 지수 복잡성을 달성 할 수 있습니다). 그러나 codegen 미쓰 히코의 모든 작품 (내가 읽은)은 매우 잘 쓰여지고 간결합니다.


답변

다른 답변 중 하나가 추천 codegen한 것으로,이에 의해 대체 된 것 같습니다 astor. astorPyPI 의 버전 (이 글을 쓰는 시점의 버전 0.5)도 약간 구식 인 것처럼 보이므로 astor다음과 같이 개발 버전을 설치할 수 있습니다 .

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

그런 다음 astor.to_sourcePython AST를 사람이 읽을 수있는 Python 소스 코드로 변환하는 데 사용할 수 있습니다 .

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

파이썬 3.5에서 이것을 테스트했습니다.