PEP 511 – 코드 변환기를 위한 API
- Author:
- Victor Stinner <vstinner at python.org>
- Status:
- Rejected
- Type:
- Standards Track
- Created:
- 04-Jan-2016
- Python-Version:
- 3.6
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
거부 공지
이 PEP는 작성자가 거부했습니다.
이 PEP는 일반 Python 언어와 비슷하지만 호환되지 않는 새로운 Python 유사 프로그래밍 언어를 공인하는 것으로 여겨졌습니다. Python과 호환되지 않는 문법은 장려하지 않기로 결정했습니다.
이 PEP는 새로운 Python 기능을 실험하기에 좋은 도구로도 여겨졌지만, 이 PEP 없이 importlib 훅만으로도 이미 그러한 기능을 실험할 수 있습니다. 어떤 기능이 유용해지면 서드파티 Python 모듈에 의존하는 대신 Python에 직접 포함되어야 합니다.
마지막으로, 이 PEP는 FAT Python 최적화 프로젝트가 주도했는데, 이 프로젝트는 의미 있는 속도 향상을 입증하지 못했고 가장 고급스럽고 복잡한 최적화를 구현할 시간도 부족했기 때문에 2016년에 중단되었습니다.
초록
바이트코드 변환기와 AST 변환기를 등록하는 API를 제안합니다. .pyc 파일 이름을 바꾸는 -o OPTIM_TAG 명령줄 옵션도 추가하며, -o noopt는 핍홀 최적화기를 비활성화합니다. .pyc 파일이 없고 코드를 변환하는 데 필요한 코드 변환기도 없으면 임포트할 때 ImportError 예외를 발생시킵니다. 미리 변환된 코드(.pyc 파일에서 로드된 코드)에는 코드 변환기가 필요하지 않습니다.
근거
Python은 코드를 변환하는 표준 방법을 제공하지 않습니다. 코드를 변환하는 프로젝트들은 다양한 훅을 사용합니다. MacroPy 프로젝트는 임포트 훅을 사용합니다. 자체 모듈 파인더를 sys.meta_path에 추가하여 AST 변환기를 연결합니다. 또 다른 방법은 내장 compile() 함수를 몽키 패치하는 것입니다. 코드 변환기를 훅으로 연결하는 방법은 이 밖에도 더 있습니다.
Python 3.4에서는 importlib.abc.SourceLoader에 compile_source() 메서드가 추가되었습니다. 하지만 코드 변환은 모듈 임포트보다 더 넓은 범위에 적용됩니다. 아래에 설명한 사용 사례를 참고하십시오.
최적화기나 전처리기를 작성하는 것은 이 PEP의 범위를 벗어납니다.
사용 사례 1: AST 최적화기
추상 문법 트리(AST)를 변환하는 것은 최적화기를 구현하는 편리한 방법입니다. AST는 더 많은 정보를 담고 있고 더 고수준이므로, 바이트코드보다 AST를 다루는 편이 더 쉽습니다.
최적화를 미리 수행할 수 있으므로, 복잡하지만 느린 최적화도 구현할 수 있습니다.
AST 최적화기로 구현할 수 있는 최적화의 예는 다음과 같습니다.
가드(PEP 510 참조)를 사용하면 훨씬 더 다양한 최적화를 구현할 수 있습니다. 예시:
- 이터러블 단순화: 이터러블로 사용될 때
range(3)을(0, 1, 2)로 바꿉니다. - 루프 펼치기
- 순수 내장 함수 호출:
len("abc")를3으로 대체 - 사용된 내장 심볼을 상수로 복사
- Python 3.6용 정적 최적화기인 fatoptimizer에 구현된 최적화도 참고하십시오.
다음 이슈들은 AST 최적화기로 구현할 수 있습니다:
- 이슈 #1346238: AST를 위한 상수 폴딩 최적화 패스
- 이슈 #2181: 함수 끝의 지역 변수를 최적화로 제거
- 이슈 #2499: 상수에 대한 단항 +와 not 폴딩
- 이슈 #4264: 패치: list.append 호출 대신 LIST_APPEND를 사용하도록 코드 최적화
- 이슈 #7682: 상수 표현식을 사용하는 if의 최적화
- 이슈 #10399: AST 최적화: 함수 호출 인라이닝
- 이슈 #11549: AST 최적화기를 구축하고 일부 기능을 핍홀 최적화기에서 옮겨 오기
- 이슈 #17068: 상수 문자열에 대한 핍홀 최적화
- 이슈 #17430: 누락된 핍홀 최적화
용도 2: 전처리기
전처리기는 AST 변환기로 쉽게 구현할 수 있습니다. 전처리기에는 다양하고 서로 다른 용도가 있습니다.
몇 가지 예는 다음과 같습니다.
- 프로덕션 환경에서 코드를 더 빠르게 실행하기 위해 어서션이나 로그 같은 디버그 코드 제거
- 꼬리 호출 최적화
- 프로파일링 코드 추가
- 지연 평가: lazy_python (바이트코드 변환기)와 MacroPy의 lazy 매크로 (AST 변환기)를 참고하십시오.
- 딕셔너리 리터럴을 collection.OrderedDict 인스턴스로 변경
- 상수 선언: codetransformer의 @asconstants를 참고하십시오.
- SQL 쿼리 같은 도메인 특화 언어(DSL). Python 언어 자체를 수정할 필요는 없습니다. PEP 335 - Overloadable Boolean Operators처럼 SQL용 DSL을 구현하려던 이전 시도는 거부되었습니다.
- 함수형 언어의 패턴 매칭
- 문자열 보간. 단, PEP 498이 이미 Python 3.6에 병합되었습니다.
MacroPy에는 예제와 사용 사례가 길게 나열되어 있습니다.
이 PEP는 새로운 코드 변환기를 추가하지 않습니다. 코드 변환기를 사용하려면 외부 모듈이 필요하며, 이를 수동으로 등록해야 합니다.
PyXfuscator도 참고하십시오: Python 난독화기, 난독화 해제기, 사용자 보조 디컴파일러입니다.
용도 3: 모든 최적화 비활성화
Ned Batchelder는 핍홀 최적화기가 코드 커버리지 구현을 더 어렵게 만들기 때문에 이를 비활성화하는 옵션을 추가해 달라고 요청했습니다. python-ideas 메일링 리스트의 논의를 참고하십시오: Disable all peephole optimizations.
이 PEP는 핍홀 최적화기를 비활성화하는 새로운 -o noopt 명령줄 옵션을 추가합니다. Python에서는 다음과 같이 간단합니다.:
sys.set_code_transformers([])
이는 Issue #2506(최적화를 비활성화하는 메커니즘 추가)을 해결할 것입니다.
사용 사례 4: Python으로 새로운 바이트코드 최적화기 작성하기
Python 3.6은 핍홀 최적화기를 사용하여 코드를 최적화합니다. 정의상 핍홀 최적화기는 코드를 좁은 범위로만 보기 때문에 기본적인 최적화만 구현할 수 있습니다. 최적화기는 바이트코드를 다시 작성합니다. 최적화기는 C로 작성되어 있기 때문에 개선하기가 어렵습니다.
이 PEP를 통해 순수 Python으로 새로운 바이트코드 최적화기를 구현하고 새로운 최적화를 실험할 수 있게 됩니다.
상수 폴딩과 같은 일부 최적화는 AST에서 구현하기가 더 쉽지만, 바이트코드에 대한 최적화도 여전히 유용합니다. 예를 들어 AST가 바이트코드로 컴파일될 때 컴파일러가 단순하여 아무것도 최적화하려 하지 않기 때문에 불필요한 점프가 생성될 수 있습니다.
사용 사례
이 절에서는 코드 변환기가 언제, 어떻게 사용될지를 설명하는 사용 사례의 예를 제시합니다.
대화형 인터프리터
Python에서 널리 쓰이고 Python을 시연할 때 흔히 사용되는 대화형 인터프리터에서도 코드 변환기를 사용할 수 있게 됩니다.
코드는 런타임에 변환되므로, 비용이 큰 코드 변환기를 사용하면 인터프리터가 느려질 수 있습니다.
변환된 패키지 빌드
변환된 코드로 패키지를 빌드할 수 있게 될 것입니다.
변환기는 구성을 가질 수 있습니다. 구성은 패키지에 저장되지 않습니다.
패키지의 모든 .pyc 파일은 동일한 코드 변환기와 동일한 변환기 구성으로 변환되어야 합니다.
서로 다른 최적화기 태그를 사용하여 서로 다른 .pyc 파일을 빌드할 수 있습니다. 예를 들어 기본 구성에는 fat을, 함수 인라이닝이 활성화된 다른 구성에는 fat_inline을 사용합니다.
패키지는 서로 다른 최적화기 태그를 가진 .pyc 파일을 포함할 수 있습니다.
변환된 .pyc 파일을 포함하는 패키지 설치
변환된 .pyc 파일을 포함하는 패키지를 설치할 수 있게 될 것입니다.
현재 최적화기 태그의 파일만이 아니라, 패키지에 포함된 모든 최적화기 태그의 .pyc 파일이 설치됩니다.
패키지 설치 시 .pyc 파일 빌드
패키지에 현재 최적화기 태그의 .pyc 파일이 하나도 없으면(또는 일부 .pyc 파일이 누락되었으면) 설치 중에 .pyc가 생성됩니다.
최적화기 태그의 코드 변환기가 필요합니다. 그렇지 않으면 설치가 오류와 함께 실패합니다.
변환된 코드 실행
변환된 코드를 실행할 수 있게 됩니다.
현재 최적화기 태그의 .pyc 파일이 없고 코드를 변환하는 데 필요한 코드 변환기도 없으면 임포트 시 ImportError 예외를 발생시킵니다.
여기서 흥미로운 점은 필요한 .pyc 파일이 모두 이미 있다면 변환된 코드를 실행하는 데 코드 변환기가 필요하지 않다는 것입니다.
코드 변환기 API
코드 변환기는 ast_transformer() 메서드와 code_transformer() 메서드 중 하나 또는 둘 모두(API는 아래에 설명합니다)와 name 속성을 가진 클래스입니다.
효율을 위해 아무 일도 하지 않는 code_transformer() 또는 ast_transformer() 메서드는 정의하지 마십시오.
name 속성(str)은 최적화기를 식별하는 데 사용되는 짧은 문자열이어야 합니다. 이 어트리뷰트는 .pyc 파일 이름을 만드는 데 사용됩니다. 이름에는 점('.'), 대시('-'), 디렉터리 구분자가 들어가면 안 됩니다. 점은 .pyc 파일 이름에서 필드를 구분하는 데 사용되고, 대시는 코드 변환기 이름을 이어 붙여 최적화기 태그를 만드는 데 사용되기 때문입니다.
Note
임포트 시 모듈을 변환하는 데 AST 변환기가 사용될 때 context에 모듈의 완전한 정규화된 이름을 전달할 수 있으면 좋겠지만, PyParser_ASTFromStringObject()에서는 그 정보를 사용할 수 없는 것으로 보입니다.
code_transformer() 메서드
프로토타입:
def code_transformer(self, code, context):
...
new_code = ...
...
return new_code
매개변수:
- code: 코드 객체
- context: 최적화 수준(0, 1 또는 2)을 나타내는 optimize 속성(
int)을 가진 객체입니다. optimize 속성의 값은compile()함수의 optimize 매개변수에서 오며, 기본값은sys.flags.optimize와 같습니다.
각 Python 구현은 context에 추가 속성을 더할 수 있습니다. 예를 들어 CPython에서는 context가 다음 속성도 가집니다:
- interactive (
bool): 대화형 모드이면 참입니다
XXX 플래그를 더 추가해야 합니까?
XXX 정수형 플래그를 하위 네임스페이스나 특정 속성으로 대체할까요?
이 메서드는 코드 객체를 반환해야 합니다.
코드 변환기는 바이트코드로 컴파일한 후에 실행됩니다
ast_transformer() 메서드
프로토타입:
def ast_transformer(self, tree, context):
...
return tree
매개변수:
- tree: AST 트리
- context:
filename속성(str)을 가진 객체입니다.
AST 트리를 반환해야 합니다. AST 트리를 제자리에서 수정하거나 새 AST 트리를 만들 수 있습니다.
AST 변환기는 파서가 AST를 생성한 후, 바이트코드로 컴파일하기 전에 호출됩니다. 향후 context에 새로운 속성이 추가될 수 있습니다.
변경 사항
요약하면 다음을 추가합니다:
- -o OPTIM_TAG 명령줄 옵션
- sys.implementation.optim_tag
- sys.get_code_transformers()
- sys.set_code_transformers(transformers)
- ast.PyCF_TRANSFORMED_AST
코드 변환기를 가져오고 설정하는 API
코드 변환기를 등록하는 새 함수를 추가합니다:
sys.set_code_transformers(transformers): 코드 변환기 목록을 설정하고sys.implementation.optim_tag를 갱신합니다sys.get_code_transformers(): 코드 변환기 목록을 가져옵니다.
코드 변환기의 순서가 중요합니다. 변환기 A를 실행한 다음 변환기 B를 실행하면, 변환기 B를 실행한 다음 변환기 A를 실행했을 때와 다른 결과가 나올 수 있습니다.
새 코드 변환기를 앞에 추가하는 예:
transformers = sys.get_code_transformers()
transformers.insert(0, new_cool_transformer)
sys.set_code_transformers(transformers)
모든 AST 변환기가 순차적으로 실행되고(예: 두 번째 변환기는 첫 번째 변환기의 입력을 받습니다), 그다음 모든 바이트코드 변환기가 순차적으로 실행됩니다.
최적화기 태그
변경 사항:
sys.implementation.optim_tag(str)를 추가합니다: 최적화 태그입니다. 기본 최적화 태그는'opt'입니다.sys.implementation.optim_tag를 설정하는 새-o OPTIM_TAG명령줄 옵션을 추가합니다.
importlib의 변경 사항:
importlib은 모듈을 임포트할 때 사용할.pyc파일 이름을 만들 때 항상opt를 사용하는 대신sys.implementation.optim_tag를 사용합니다. 또한 코드를 단순화하기 위해, 기본 최적화기 태그'opt'를 사용하는 최적화기 수준0에 대한 특수한 경우를 제거합니다.- 모듈을 로드할 때
.pyc파일은 없지만.py가 있으면, 코드 최적화기의 최적화기 태그가 현재 태그와 같을 때만.py를 사용하고, 그렇지 않으면ImportError예외가 발생합니다.
모듈을 임포트하기 위해 .py 파일을 컴파일할 수 있는지 결정하는 use_py() 함수의 의사 코드:
def transformers_tag():
transformers = sys.get_code_transformers()
if not transformers:
return 'noopt'
return '-'.join(transformer.name
for transformer in transformers)
def use_py():
return (transformers_tag() == sys.implementation.optim_tag)
sys.get_code_transformers()의 순서가 중요합니다. 예를 들어 fat 변환기 다음에 pythran 변환기가 오면 최적화기 태그는 fat-pythran이 됩니다.
기본 최적화기 태그('opt')에서는 importlib 모듈의 동작이 바뀌지 않습니다.
핍홀 최적화기
기본적으로 sys.implementation.optim_tag는 opt이고, sys.get_code_transformers()는 코드 변환기 하나, 즉 핍홀 최적화기(바이트코드 최적화)만 담긴 리스트를 반환합니다.
핍홀 최적화기를 비활성화하려면 -o noopt를 사용하십시오. 이 경우 최적화기 태그는 noopt이고 코드 변환기는 하나도 등록되지 않습니다.
-o opt 옵션을 사용해도 아무런 효과가 없습니다.
AST 개선 사항
AST 변환기 구현을 단순화하기 위한 개선 사항:
- 변환된 AST를 얻을 수 있도록 새 컴파일러 플래그
PyCF_TRANSFORMED_AST를 추가합니다.PyCF_ONLY_AST는 변환기를 적용하기 전의 AST를 반환합니다.
예시
.pyc 파일 이름
os 모듈의 .pyc 파일 이름 예시입니다.
기본 최적화기 태그가 'opt'인 경우:
| .pyc 파일 이름 | 최적화 수준 |
|---|---|
os.cpython-36.opt-0.pyc |
0 |
os.cpython-36.opt-1.pyc |
1 |
os.cpython-36.opt-2.pyc |
2 |
'fat' 최적화기 태그를 사용하는 경우:
| .pyc 파일 이름 | 최적화 수준 |
|---|---|
os.cpython-36.fat-0.pyc |
0 |
os.cpython-36.fat-1.pyc |
1 |
os.cpython-36.fat-2.pyc |
2 |
바이트코드 변환기
모든 문자열을 "Ni! Ni! Ni!"로 바꾸는 무시무시한 바이트코드 변환기:
import sys
import types
class BytecodeTransformer:
name = "knights_who_say_ni"
def code_transformer(self, code, context):
consts = ['Ni! Ni! Ni!' if isinstance(const, str) else const
for const in code.co_consts]
return types.CodeType(code.co_argcount,
code.co_kwonlyargcount,
code.co_nlocals,
code.co_stacksize,
code.co_flags,
code.co_code,
tuple(consts),
code.co_names,
code.co_varnames,
code.co_filename,
code.co_name,
code.co_firstlineno,
code.co_lnotab,
code.co_freevars,
code.co_cellvars)
# replace existing code transformers with the new bytecode transformer
sys.set_code_transformers([BytecodeTransformer()])
# execute code which will be transformed by code_transformer()
exec("print('Hello World!')")
출력:
Ni! Ni! Ni!
AST 변환기
바이트코드 변환기 예제와 마찬가지로, AST 변환기도 모든 문자열을 "Ni! Ni! Ni!"로 바꿉니다.:
import ast
import sys
class KnightsWhoSayNi(ast.NodeTransformer):
def visit_Str(self, node):
node.s = 'Ni! Ni! Ni!'
return node
class ASTTransformer:
name = "knights_who_say_ni"
def __init__(self):
self.transformer = KnightsWhoSayNi()
def ast_transformer(self, tree, context):
self.transformer.visit(tree)
return tree
# replace existing code transformers with the new AST transformer
sys.set_code_transformers([ASTTransformer()])
# execute code which will be transformed by ast_transformer()
exec("print('Hello World!')")
출력:
Ni! Ni! Ni!
다른 Python 구현
PEP 511은 모든 Python 구현에서 구현되어야 하지만, 바이트코드와 AST는 표준화되어 있지 않습니다.
덧붙여, CPython의 마이너 버전 사이에서도 AST API에 변경 사항이 있습니다. 차이는 있지만, 사소한 차이에 불과합니다. 예를 들어 Python 2.7과 Python 3.5에서 모두 동작하는 AST 변환기를 작성하는 것은 꽤 쉽습니다.
논의
선행 사례
AST 최적화기
이슈 #17515 “Add sys.setasthook() to allow to use a custom AST” optimizer는 코드 변환기를 위한 API의 첫 시도였지만, AST에 한정된 것이었습니다.
2015년에 Victor Stinner는 가드를 사용해 함수를 특수화하는 AST 최적화기인 fatoptimizer 프로젝트를 작성했습니다.
2014년에 Kevin Conway는 PyCC 최적화기를 만들었습니다.
2012년에 Victor Stinner는 다양한 최적화를 구현한 AST 최적화기인 astoptimizer 프로젝트를 작성했습니다. 무언가가 바뀌었을 때 최적화를 비활성화하는 가드를 사용하지 않기 때문에, 대부분의 흥미로운 최적화는 Python 의미를 깨뜨립니다.
2011년에 Eugene Toder는 일부 핍홀(peephole) 최적화를 새로운 AST 최적화기로 다시 작성할 것을 제안했습니다: 이슈 #11549, Build-out an AST optimizer, moving some functionality out of the peephole optimizer. 이 패치는 ast.Lit을 추가합니다(이를 ast.Literal로 이름을 바꾸자는 제안이 있었습니다).
Python 전처리기
- MacroPy: MacroPy는 Python 프로그래밍 언어에서 구문 매크로를 구현한 것입니다. MacroPy는 사용자 정의 함수(매크로)가 임포트 시점에 Python 프로그램의 추상 문법 트리(AST)를 변환할 수 있게 하는 메커니즘을 제공합니다.
- pypreprocessor:
#define이나#ifdef같은 C 스타일 전처리기 지시문을 Python에서 사용할 수 있게 합니다.
바이트코드 변환기
- codetransformer:
ast모듈의NodeTransformer에서 영감을 받은 CPython용 바이트코드 변환기입니다. - byteplay: Byteplay는 Python 코드 객체를 다루기 쉬운 동등한 객체로 변환하고, 그 객체를 다시 실제로 동작하는 Python 코드 객체로 변환할 수 있게 해 줍니다. Python 함수에 기발한 변환을 적용할 때 유용하며, Python 바이트코드의 복잡한 세부 사항을 익히는 데에도 유용합니다. byteplay 문서를 참조하십시오.
함께 보기:
Copyright
This document has been placed in the public domain.