1P by neo 3달전 | favorite | 댓글 1개

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에 의해 감지되는지 궁금함

    • 그렇지 않다면, 즐겁게 사용하길 바람. 라이브러리를 재작성하는 것이 더 쉬울 수 있음 (이런 함정이 있다면 다른 함정도 있을 가능성이 높음)