.py 파일을 구문 분석하고 AST를 읽고 수정 한 다음 수정 된 소스 코드를 다시 작성하십시오.
파이썬 소스 코드를 프로그래밍 방식으로 편집하고 싶습니다. 기본적으로 .py
파일 을 읽고 AST를 생성 한 다음 수정 된 파이썬 소스 코드 (예 : 다른 .py
파일)를 다시 작성 하려고합니다 .
같은 표준 파이썬 모듈을 사용하여 구문 분석 / 컴파일 파이썬 소스 코드에 가지 방법이 있습니다 ast
또는 compiler
. 그러나 나는 소스 코드를 수정하는 방법 (예 :이 함수 선언 삭제)을 수정 한 다음 수정 파이썬 소스 코드를 다시 쓰는 방법을 지원하지 않는다고 생각합니다.
업데이트 :이 작업을 수행하는 이유는 주로 명령문 / 표현식을 삭제하고 테스트를 다시 실행하고 중단되는 부분을 확인하여 Python에 대한 돌연변이 테스트 라이브러리 를 작성하고 싶습니다 .
Pythoscope 는 Python 2.6 용 2to3 도구 (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 ()을 호출하고 결과 코드 객체를 실행하면됩니다.
소스 코드를 다시 생성하지 않아도됩니다. 물론 코드로 가득 찬 .py 파일을 생성해야한다고 생각하는 이유를 실제로 설명하지 않았기 때문에이 방법은 다소 위험합니다. 그러나:
사람들이 실제로 사용할 .py 파일을 생성하려는 경우 양식을 작성하고 프로젝트에 삽입 할 유용한 .py 파일을 얻을 수 있도록 AST 파일로 변경하고 싶지 않은 경우 당신은 잃게됩니다 다시 때문에
모든 포맷을 (함께 라인의 관련 세트를 그룹화하여 파이썬이 그렇게 읽을 수 있도록 빈 줄 생각)( AST 노드가lineno
및col_offset
속성 ) 주석. 대신 .py 파일을 사용자 지정하기 위해 템플릿 엔진 ( 예 : Django 템플릿 언어 는 텍스트 파일도 쉽게 템플릿을 만들도록 설계됨)을 사용하거나 Rick Copeland의 MetaPython 확장명을 사용 하려고 합니다.모듈을 컴파일하는 동안 변경을 시도하는 경우 다시 텍스트로 돌아갈 필요가 없습니다. AST를 다시 .py 파일로 변환하는 대신 AST를 직접 컴파일하면됩니다.
그러나 거의 모든 경우에, 아마도 새로운 .py 파일을 쓰지 않고도 파이썬과 같은 언어가 실제로 매우 쉬운 동적 작업을 시도하고있을 것입니다! 실제로 달성하려는 것을 알려주기 위해 질문을 확장하면 새로운 .py 파일이 응답에 전혀 관여하지 않을 것입니다. 수백 개의 파이썬 프로젝트가 수백 개의 실제 작업을 수행하는 것을 보았으며 .py 파일을 작성하는 데 필요한 단일 프로젝트는 아닙니다. 그래서, 나는 당신이 첫 번째 좋은 유스 케이스를 찾은 것에 대해 약간 회의론자임을 인정해야합니다. :-)
업데이트 : 이제 당신이하려는 일을 설명 했으므로 어쨌든 AST에서 작동하고 싶습니다. 파일의 행을 제거하지 않고 (SyntaxError로 간단히 반감 지하는 반문을 초래할 수 있음), 전체 문장을 제거하여 변경하고 싶을 것입니다.
다른 대답으로 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에서 이것을 테스트했습니다.
ast
모듈 의 도움으로 코드 구조를 파싱하고 수정하는 것이 가능 하며 잠시 후에 예제로 보여 드리겠습니다. 그러나 ast
모듈만으로는 수정 된 소스 코드를 다시 작성할 수 없습니다 . 같은 하나이 작업에 사용할 수있는 다른 모듈이 있습니다 여기가 .
참고 : 예 아래의 사용에 입문 튜토리얼로 취급 할 수 있습니다 ast
모듈 있지만 사용에 대한보다 포괄적 인 가이드 ast
모듈은 여기에서 확인할 수있다 그린 트리 튜토리얼 뱀 과 에 공식 문서 ast
모듈 .
소개 ast
:
>>> import ast
>>> tree = ast.parse("print 'Hello Python!!'")
>>> exec(compile(tree, filename="<ast>", mode="exec"))
Hello Python!!
You can parse the python code (represented in string) by simply calling the API ast.parse()
. This returns the handle to Abstract Syntax Tree (AST) structure. Interestingly you can compile back this structure and execute it as shown above.
Another very useful API is ast.dump()
which dumps the whole AST in a string form. It can be used to inspect the tree structure and is very helpful in debugging. For example,
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)])"
On 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=[]))])"
Notice the difference in syntax for print statement in Python 2.7 vs. Python 3.5 and the difference in type of AST node in respective trees.
How to modify code using ast
:
Now, let's a have a look at an example of modification of python code by ast
module. The main tool for modifying AST structure is ast.NodeTransformer
class. Whenever one needs to modify the AST, he/she needs to subclass from it and write Node Transformation(s) accordingly.
For our example, let's try to write a simple utility which transforms the Python 2 , print statements to Python 3 function calls.
Print statement to Fun call converter utility: 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])
This utility can be tried on small example file, such as one below, and it should work fine.
Test Input file : 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()
Please note that above transformation is only for ast
tutorial purpose and in real case scenario one will have to look at all different scenarios such as print " x is %s" % ("Hello Python")
.
I've created recently quite stable (core is really well tested) and extensible piece of code which generates code from ast
tree: https://github.com/paluh/code-formatter .
I'm using my project as a base for a small vim plugin (which I'm using every day), so my goal is to generate really nice and readable python code.
P.S. I've tried to extend codegen
but it's architecture is based on ast.NodeVisitor
interface, so formatters (visitor_
methods) are just functions. I've found this structure quite limiting and hard to optimize (in case of long and nested expressions it's easier to keep objects tree and cache some partial results - in other way you can hit exponential complexity if you want to search for best layout). BUT codegen
as every piece of mitsuhiko's work (which I've read) is very well written and concise.
One of the other answers recommends codegen
, which seems to have been superceded by astor
. The version of astor
on PyPI (version 0.5 as of this writing) seems to be a little outdated as well, so you can install the development version of astor
as follows.
pip install git+https://github.com/berkerpeksag/astor.git#egg=astor
Then you can use astor.to_source
to convert a Python AST to human-readable Python source code:
>>> import ast
>>> import astor
>>> print(astor.to_source(ast.parse('def foo(x): return 2 * x')))
def foo(x):
return 2 * x
I have tested this on Python 3.5.
A Program Transformation System is a tool that parses source text, builds ASTs, allows you to modify them using source-to-source transformations ("if you see this pattern, replace it by that pattern"). Such tools are ideal for doing mutation of existing source codes, which are just "if you see this pattern, replace by a pattern variant".
Of course, you need a program transformation engine that can parse the language of interest to you, and still do the pattern-directed transformations. Our DMS Software Reengineering Toolkit is a system that can do that, and handles Python, and a variety of other languages.
See this SO answer for an example of a DMS-parsed AST for Python capturing comments accurately. DMS can make changes to the AST, and regenerate valid text, including the comments. You can ask it to prettyprint the AST, using its own formatting conventions (you can changes these), or do "fidelity printing", which uses the original line and column information to maximally preserve the original layout (some change in layout where new code is inserted is unavoidable).
To implement a "mutation" rule for Python with DMS, you could write the following:
rule mutate_addition(s:sum, p:product):sum->sum =
" \s + \p " -> " \s - \p"
if mutate_this_place(s);
This rule replace "+" with "-" in a syntactically correct way; it operates on the AST and thus won't touch strings or comments that happen to look right. The extra condition on "mutate_this_place" is to let you control how often this occurs; you don't want to mutate every place in the program.
You'd obviously want a bunch more rules like this that detect various code structures, and replace them by the mutated versions. DMS is happy to apply a set of rules. The mutated AST is then prettyprinted.
We had a similar need, which wasn't solved by other answers here. So we created a library for this, ASTTokens, which takes an AST tree produced with the ast or astroid modules, and marks it with the ranges of text in the original source code.
It doesn't do modifications of code directly, but that's not hard to add on top, since it does tell you the range of text you need to modify.
For example, this wraps a function call in WRAP(...)
, preserving comments and everything else:
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:])
Produces:
def foo(): # Test
'''My func'''
WRAP(log("hello world")) # Print
Hope this helps!
I used to use baron for this, but have now switched to parso because it's up to date with modern python. It works great.
I also needed this for a mutation tester. It's really quite simple to make one with parso, check out my code at https://github.com/boxed/mutmut
If you are looking at this in 2019, then you can use this libcst package. It has syntax similar to ast. This works like a charm, and preserve the code structure. It's basically helpful for the project where you have to preserve comments, whitespace, newline etc.
If you don't need to care about the preserving comments, whitespace and others, then the combination of ast and astor works well.
'Programing' 카테고리의 다른 글
JavaScript에서 많은 수의 과학적 표기법을 피하는 방법은 무엇입니까? (0) | 2020.06.08 |
---|---|
"로컬 복사"및 프로젝트 참조에 대한 모범 사례는 무엇입니까? (0) | 2020.06.08 |
"단위 테스트 작성"은 어디에 있습니까? (0) | 2020.06.08 |
여러 열에서 INNER JOIN을 수행하는 방법 (0) | 2020.06.08 |
글꼴이 포함 된 SVG 이미지에 적합한 MIME 유형 (0) | 2020.06.08 |