GN⁺: Python의 전처리기
(pydong.org)Python의 전처리기
- Python에는 전처리기가 없다는 주장은 사실이 아님
- Python은 매우 강력한 전처리기를 가지고 있음
Python 소스 코드 인코딩
- PEP-0263 덕분에 소스 코드 인코딩을 정의할 수 있음
- 첫 두 줄에 매직 코멘트를 추가하여 인코딩을 설정할 수 있음
- 예:
# coding=utf8
,# -*- coding: utf8 -*-
,# vim: set fileencoding=utf8 :
경로 구성 파일 (.pth)
- Python 인터프리터가
-S
옵션 없이 시작되면site
패키지를 자동으로 로드함 -
site-packages
폴더에.pth
파일을 추가하여 모듈 검색 경로를 확장할 수 있음 -
.pth
파일의import
로 시작하는 줄은 실행됨 - 이를 통해 Python 인터프리터 초기화 시 임의의 코드를 실행할 수 있음
사용자 정의 코덱 정의
- Python 인터프리터가 만족할 두 가지 필요:
-
decode(data: bytes) -> tuple[str, int]
함수 - 증분 디코더 클래스
-
-
codecs.utf_8_decode
를 사용하여 실제 디코딩을 수행하고, 결과 문자열을 전처리기에 전달 - 예외를 잡아 출력하고 다시 발생시키는 것이 좋음
증분 디코더 제공
-
codecs.BufferedIncrementalDecoder
를 상속하여 증분 디코더를 구현 - 버퍼에 데이터를 수집하고 최종 디코드 호출 시 전체 파일을 전처리
Python 확장
- Python의 표준 라이브러리를 사용하여 Python을 확장하는 것은 비교적 쉬움
-
tokenize
모듈을 사용하여 파일의 토큰 스트림을 수정하거나ast
모듈을 사용하여 추상 구문 트리를 수정할 수 있음
단항 증가 및 감소
- Python에는 단항 증가 및 감소 연산자가 없음
-
x++
,x--
는 유효하지 않음 -
++x
,--x
는 유효하지만 다른 의미를 가짐 - 단항 증가 및 감소 표현을 Python 표현으로 변환할 수 있음
예제
- 입력 파일
incdec.py
:# coding: magic.incdec i = 6 assert i-- == 6 assert i == 5 assert ++i == 6 assert --i == 5 assert i++ == 5 assert i == 6 assert (++i, 'i++') == (7, 'i++') print("PASSED")
- 변환된 파일:
i = 6 assert ((i, i := i - 1)[0]) == 6 assert i == 5 assert ((i, i := i + 1)[1]) == 6 assert ((i, i := i - 1)[1]) == 5 assert ((i, i := i + 1)[0]) == 5 assert i == 6 assert (((i, i := i + 1)[1]), 'i++') == (7, 'i++') print("PASSED")
중괄호를 사용하는 Python (Bython)
- Python의 들여쓰기 대신 중괄호를 사용하여 범위를 지정할 수 있음
-
tokenize.generate_tokens
를 사용하여 토큰 스트림을 수정
예제
- 입력 파일
test.by
:# coding: magic.braces def print_message(num_of_times) { for i in range(num_of_times) { print("braces ftw") } print({'x': 3}) } x = { 'foo': 42, 'bar': 5 } if __name__ == "__main__" { print_message(2) print({k: v for k, v in x.items()}) }
- 변환된 파일:
def print_message(num_of_times): for i in range(num_of_times): print("braces ftw") print({'x': 3}) x = { 'foo': 42, 'bar': 5 } if __name__ == "__main__": print_message(2) print({k: v for k, v in x.items()})
다른 언어 해석
- Python 인터프리터가 다른 언어를 해석하도록 할 수 있음
- 예: C, C++, TOML
예제
- C++ 파일
test.cpp
:#define CODEC "coding:magic.cpp" #include <cstdio> int main() { puts("Hello World"); }
- 변환된 파일:
import cppyy cppyy.cppdef(r""" #define CODEC "coding:magic.cpp" #include <cstdio> int main() { puts("Hello World"); } """) from cppyy.gbl import main if __name__ == "__main__": main()
데이터 검증
- TOML 형식의 데이터를 JSON 스키마를 사용하여 검증할 수 있음
-
jsonschema
를 사용하여 실제 검증 수행
예제
- 스키마 파일
schema.json
:{ "type": "object", "properties": { "name": {"type": "string"}, "age": {"type": "number"}, "scores": { "type": "array", "items": {"type": "number"} }, "address": {"$ref": "#/$defs/address"} }, "required": ["name"], "$defs": { "address": { "type": "object", "properties": { "street": {"type": "string"}, "postcode": {"type": "number"} }, "required": ["street"] } } }
- 유효한 데이터 파일
data_valid.toml
:# coding: magic.toml name = "John Doe" age = 42 scores = [40, 20, 80, 90] [address] street = "Grove St. 4" postcode = 19201
- 유효하지 않은 데이터 파일
data_invalid.toml
:# coding: magic.toml name = "John Doe" age = 42 scores = [40, "20", 80, 90] [address] street = "Grove St. 4" postcode = 19201
결론
- 사용자 정의 코덱과 경로 구성 파일을 사용하여 Python 인터프리터의 동작을 크게 변경할 수 있음
- 예시로는
pythonql
,future-typing
,future-fstrings
,future-annotations
등이 있음 -
magic_codec
을 사용하여 자신의 전처리기를 쉽게 실험할 수 있음
GN⁺의 정리
- Python의 전처리기를 활용하여 다양한 언어 확장 및 데이터 검증을 수행할 수 있음
- 사용자 정의 코덱을 통해 Python 인터프리터의 동작을 변경하는 방법을 설명함
- 이 기사는 Python 개발자에게 유용한 도구와 기법을 제공함
- 비슷한 기능을 가진 프로젝트로는
pythonql
,future-typing
등이 있음
Hacker News 의견
-
from __future__ import braces
문법 오류 메시지는 2001년부터 cpython에 하드코딩되어 있음- Jeremy Hylton이 작성했으며, 현재 Google에서 AI 검색 품질을 담당하는 수석 엔지니어로 일하고 있음
- 24년 동안 한 사람의 경력이 특정 문법 금지를 기념하는 것에서 시작해 전용 문법이 필요 없는 검색 시스템 작업으로 발전한 것에 놀라움
-
import-hooks를 사용한 창의적인 해고 방법에 대해 생각했으나, codec regex가 "μtf8" 같은 것을 사용하지 못하게 막아 아쉬움
- import hooks, preprocessors, sys.settrace를 사용해 모든 함수를 이전에 호출된 함수로 원숭이 패치하고, 17분마다 stdout과 stderr를 교체하는 방법을 사용해야 함
-
python이 전처리기 훅을 노출하지 않은 이유가 있으며, 합리적인 성인은 이를 피해야 한다고 생각함
- 그러나 합리적인 성인과는 상관없이 재미를 추구하고 싶음
-
전처리기가 더 편리하고 유용함
- ast 모듈을 사용해 코드를 재작성하고 exec로 실행한 후 exit()을 삽입하는 방식으로 해킹을 했음
- dicts가 모두 정렬되기 전에는 ast 재작성 기능을 사용해 유용하게 사용했음
-
python의 유연성을 사랑함
- 가장 저주받은 작업은 문자열을 제자리에서 변형하는 것이었으며, mmap을 남용해 스크립트를 스스로 변형하게 만들었음
- 이제는 Lisp 인터프리터를 작성하고 싶음
-
pyxl을 사용한 최고의 사례는 jsx에서 영감을 받음
-
# coding: pyxl
을 사용해 HTML 코드를 작성할 수 있음
-
-
Python 2에서 3으로의 전환을 더 잘 처리할 수 있었을지 궁금함
-
# coding: six.python2
는 Python2 코드를 Python3에서 유효하게 만들고,# coding: six.python3
는 Python3 코드를 Python2에서 실행 가능하게 조정할 수 있음
-
-
이 아이디어를 좋아해줘서 기쁨, 더 많은 내용이 곧 나올 예정임
-
오랜만에 완전히 새로운 아이디어에 놀라움을 느낌
-
Python에서 인라인 코드 생성을 원한다면, Ned Batchelder의 cog를 사용할 수 있음
-
이 코딩 훅 전략으로 도입된 종속성이
pip freeze
나 uv에 의해 감지되는지 궁금함- 그렇지 않다면, 즐겁게 사용하길 바람. 라이브러리를 재작성하는 것이 더 쉬울 수 있음 (이런 함정이 있다면 다른 함정도 있을 가능성이 높음)