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


168

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

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

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


4
버전 2.6부터 사용되지 않음 : Python 3.0에서 컴파일러 패키지가 제거되었습니다.
dfa

1
소스를 편집 할 수없는 것은 무엇입니까? 왜 데코레이터를 쓸 수 없습니까?
S.Lott

3
이런 젠장! 동일한 기술 (특히 코 플러그인 생성)을 사용하여 파이썬에 대한 돌연변이 테스터를 만들고 싶었습니다. 오픈 소싱을 계획하고 있습니까?
Ryan

2
@Ryan 그래 내가 만든 것을 오픈 소스로 열 것이다. 우리는 이것에 계속 연락해야합니다
Rory

1
런치 패드를 통해 이메일을 보냈습니다.
Ryan

답변:


73

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

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

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

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

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


5
다음 unparse 예는 아직 여기에 업데이트 된 py3k 버전입니다, 유지 hg.python.org/cpython/log/tip/Tools/parser/unparse.py
야누스 Troelsen

2
unparse.py스크립트 와 관련하여 -다른 스크립트에서 사용하는 것은 실제로 번거로울 수 있습니다. 그러나 astunparse ( github , pypi )라는 패키지가 있으며 기본적으로 올바르게 패키지 된 버전입니다 unparse.py.
mbdevpl

parso를 선호하는 옵션으로 추가하여 답변을 업데이트 할 수 있습니까? 매우 좋고 업데이트되었습니다.
박스 박스

59

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


20
미래에 이것을 사용하는 사람이라면 누구나 codegen이 최신이 아니며 몇 가지 버그가 있습니다. 나는 그들 중 몇 가지를 고쳤다; 나는 이것을 github의 요지로 가지고있다 : gist.github.com/791312
mattbasta

위의 주석 다음에 최신 codegen이 2012 년에 업데이트되었으므로 codegen이 업데이트 된 것 같습니다. @mattbasta
zjffdu

4
애 스터는 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)

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


19

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

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

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

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

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


가능한 솔루션 및 가능한 대안에 대한 좋은 개요.
Ryan

1
코드 생성을위한 실제 사용 사례 : Kid and Genshi는 동적 페이지의 빠른 렌더링을 위해 XML 템플릿에서 Python을 생성합니다.
Rick Copeland

10

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")합니다.


6

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

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

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


4

다른 답변 중 하나가 추천 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에서 이것을 테스트했습니다.


4

2019 년에 이것을보고 있다면이 libcst를 사용할 수 있습니다 패키지를 . ast와 비슷한 구문이 있습니다. 이것은 매력처럼 작동하며 코드 구조를 유지합니다. 주석, 공백, 줄 바꿈 등을 유지 해야하는 프로젝트에 기본적으로 도움이됩니다.

주석, 공백 등을 보존 할 필요가 없다면 ast와 astor 의 조합이 효과적입니다.


2

우리는 비슷한 요구가 있었지만 다른 답변으로는 해결되지 않았습니다. 이를 위해 ASTTokens 라이브러리를 만들었습니다. ASTTokensast 또는 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 소프트웨어 재 설계 Toolkit은 그렇게 할 수있는 시스템이며, 파이썬, 및 기타 다양한 언어를 처리합니다.

주석을 정확하게 캡처하기위한 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 년 동안이 답변을 보지 않았습니다. 와우, 그것은 여러 번 downvoted되었습니다. 그것은 OP의 질문에 직접 대답하고 심지어 그가 원하는 돌연변이를 수행하는 방법을 보여주기 때문에 정말 놀랍습니다. 나는 어떤 downvoter가 그들의 downvoted 이유 를 설명 할 것이라고 생각하지 않습니다 .
Ira Baxter

4
매우 고가의 비공개 소스 도구를 장려하기 때문입니다.
Zoran Pavlovic

@ ZoranPavlovic : 그래서 당신은 기술적 정확성이나 유틸리티에 반대하지 않습니까?
Ira Baxter

2
@ 조란 : 그는 오픈 소스 라이브러리가 있다고 말하지 않았습니다. 그는 AST를 사용하여 Python 소스 코드를 수정하고 싶었지만 찾을 수있는 솔루션으로는 그렇게하지 않았다고 말했다. 이것은 그러한 해결책입니다. 사람들이 Python on Java와 같은 언어로 작성된 프로그램에서 상용 도구를 사용한다고 생각하지 않습니까?
Ira Baxter

1
나는 투표권이 아니지만 게시물은 광고처럼 읽습니다. 답변을 개선하기 위해 제품과 관련이 있음을 공개 할 수 있습니다.
wim

0

나는 이것을 위해 남작을 사용했지만 현대 파이썬으로 최신 상태이기 때문에 이제 파소로 전환했습니다. 잘 작동합니다.

나는 또한 돌연변이 테스터를 위해 이것을 필요로했다. 파소로 하나를 만드는 것은 정말 간단합니다. https://github.com/boxed/mutmut 에서 내 코드를 확인하십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.