2P by GN⁺ | ★ favorite | 댓글 1개
  • WAT는 Python 런타임에서 알 수 없는 객체의 정체를 빠르게 파악하기 위한 inspector로, 타입·값·속성·메서드·부모 타입·시그니처·문서·소스 코드까지 한 번에 확인할 수 있음
  • 기본 사용법은 wat / object이며 wat(object)와 동일하게 동작하고, wat.short / 'foo', 'foo' | wat.short, wat('foo', short=True) 같은 여러 문법을 지원함
  • .short, .dunder, .long, .code, .caller, .public, .all, .ret, .strmodifier를 체인으로 붙여 출력 범위, 반환 방식, 컬러 출력, 호출 위치 표시를 조정할 수 있음
  • 설치는 pip install watimport wat으로 가능하며, 빠른 디버깅을 위해 Insta-Load 스니펫을 Python 세션에 붙여 넣어 같은 세션에서 설치 없이 사용할 수도 있음
  • Django User, re.match, pathlib, colorsys.hsv_to_rgb, typing.List[str], str | None 같은 예시는 WAT가 디버깅·REPL 탐색·Python 내부 학습에 쓰일 수 있음을 보여줌

WAT가 하는 일

  • WAT는 Python 객체를 런타임에서 탐색하고 검사하기 위한 도구임
  • 알 수 없는 객체가 무엇인지 파악하기 어려울 때 Python 콘솔에서 wat inspector로 객체의 정체를 조사할 수 있음
  • 임의의 object에 대해 wat / object를 실행하면 다음 정보를 확인할 수 있음
    • 객체의 type
    • 포맷된 값
    • 변수와 메서드
    • 부모 타입
    • 시그니처
    • 문서
    • 소스 코드
  • 같은 심층 검사는 wat(object) 문법으로도 사용할 수 있음
  • Wat은 영어 what의 변형으로, 혼란이나 불쾌감을 표현할 때 쓰이는 말로 소개됨

기본 사용법과 문법

  • 빠른 입력을 위해 나눗셈 연산자를 사용함
    • wat / foowat(foo)와 동일함
  • 동일한 검사에 여러 문법을 사용할 수 있음
    • wat.short / 'foo': 빠른 입력용 문법
    • wat.short('foo')
    • wat('foo', short=True): 자연스러운 Python 문법
    • 'foo' | wat.short: Unix 파이프 스타일 문법
  • wat.modifier / foo 형태로 검사 동작을 조정할 수 있음
  • modifier는 체인으로 연결할 수 있으며, 예시는 wat.short.str.gray / 'foo'
  • Python에서 객체는 데이터 구조뿐 아니라 함수, 클래스, 모듈, 내장 타입 등을 포함하므로 wat는 어떤 객체든 탐색할 수 있음
  • 인터프리터에서 wat를 입력하면 wat 객체 자체에 대한 도움말을 볼 수 있음

Modifier로 조정하는 검사 범위

  • .short 또는 .s는 객체 내부의 변수와 메서드 같은 속성을 숨기고 값, 타입, 부모 타입, 시그니처, 문서만 출력함
  • .dunder__로 시작하는 dunder 속성을 표시함
  • .long은 축약하지 않은 값과 docstring을 표시함
  • .code는 함수, 메서드, 클래스의 소스 코드를 보여줌
  • .nodocs는 함수와 클래스의 문서를 숨김
  • .caller는 검사가 호출된 방식과 위치를 보여주며, REPL이 아닌 파일에서 동작함
  • .public은 private 속성을 숨기고 public 속성만 보여줌
  • .all은 가능한 모든 정보를 포함함
  • .ret는 검사 후 객체를 다시 반환함
  • .str은 출력 대신 결과 문자열을 반환함
  • .gray는 콘솔의 컬러 출력을 비활성화함
  • .color는 콘솔의 컬러 출력을 강제함
  • wat.locals는 로컬 변수를 검사하고, wat.globals는 전역 변수를 검사함

설치와 Insta-Load

  • pip 설치 방식은 다음 흐름임
    • pip install wat
    • Python에서 import wat
  • wat 패키지는 외부 의존성이 없음
  • 빠른 디버깅을 위해 같은 Python 세션에서 설치 없이 사용할 수 있는 Insta-Load 방식을 제공함
  • Insta-Load는 base64, zlib를 import한 뒤 압축·인코딩된 코드 문자열을 복원하고 exec(..., globals())로 실행하는 Python snippet을 인터프리터에 붙여 넣는 방식임
  • Insta-Load snippet 실행 후에는 wat object를 사용할 수 있음
  • snippet 실행 전에는 실행할 내용을 검증하는 것이 권장됨
    • print(zlib.decompress(base64.b64decode(code)).decode())로 추출된 코드 내용을 미리 확인할 수 있음
    • inspection.py의 내용을 인터프리터에 붙여 넣어도 같은 효과를 냄
    • pip로 패키지를 설치하고 코드를 검토하는 방법도 제시됨
  • WAT는 단일 Unicode glyph에서 로드할 수 있음
  • Unicode 문자열 기반 로더는 긴 이모지·결합 문자 문자열을 ord(c) & 255로 바이트화하고 zlib.decompress(...)exec(...)로 실행하는 형태임

객체 타입과 사용법 파악

  • 동적 타입 언어인 Python에서는 객체 타입을 파악하기 어려울 때가 있으며, WAT Inspector는 타입 이름과 해당 타입이 온 모듈을 보여줌
  • 타입 확인 예시는 값, 타입, 길이를 함께 보여줌
    • wat.short / (1,)는 값 (1,), 타입 tuple, 길이 1을 출력함
    • wat.short / {None}은 값 {None}, 타입 set, 길이 1을 출력함
  • Django User 객체 예시에서 wat.short / userstr: admin, repr: <User: admin>, 타입 django.contrib.auth.models.User, 부모 타입 목록을 출력함
  • 실제 타입을 확인한 뒤 코드에 타입 애너테이션을 넣어 이후 혼란을 줄일 수 있음
  • 알 수 없는 객체 사용법을 파악할 때 메서드 목록, 시그니처, docstring을 출력할 수 있음
    • 예시로 wat / ['foo']가 제시됨
    • 전체 docstring을 보고 싶으면 wat.long을 사용함
  • 함수 사용법을 파악하기 위해 함수의 docstring과 시그니처를 볼 수 있음
    • 예시로 wat / str.split이 제시됨

속성, 모듈, 소스 코드 탐색

  • 검사 대상 객체 내부를 확인하기 위해 속성과 각 속성의 타입을 나열할 수 있음
    • 예시로 wat / re.match('(\d)_(.*)', '1_title')가 제시됨
  • 모듈 탐색 용도로도 사용할 수 있으며, 선택한 모듈의 함수, 클래스, 하위 모듈을 나열할 수 있음
    • import pathlibwat / pathlib을 실행하는 예시가 있음
    • 이후 wat / pathlib.fnmatch처럼 더 깊이 탐색할 수 있음
  • WAT Inspector는 기본적으로 __로 시작하는 속성을 숨김
    • wat.dunder / {}로 dunder 속성을 볼 수 있음
  • 함수가 실제로 어떻게 동작하는지 확인하기 위해 소스 코드를 볼 수 있음
    • import colorsyswat.code / colorsys.hsv_to_rgb를 실행하는 예시가 있음
  • 중첩된 dict와 list는 들여쓰기된 읽기 쉬운 형태로 포맷됨

디버깅 세션과 변수 검사

  • Python의 breakpoint()로 대화형 디버거를 실행한 뒤, 그 자리에서 객체를 검사할 수 있음
  • Pdb 예시는 import wat 또는 Insta-Load 스니펫 붙여넣기 후 wat / foo로 로컬 변수를 검사하고, c로 실행을 계속함
  • 로컬 변수와 전역 변수는 각각 wat.locals, wat.globals로 확인할 수 있음
  • wat()를 인자 없이 호출하면 호출자 스택의 로컬 변수를 Local variables 제목으로 출력함

Python 내부 학습 예시

  • Python 내부 동작을 이해하는 학습 용도 예시가 포함됨
  • reversed([]) == reversed([])False이며, wat.s / reversed([])는 값이 list_reverseiterator 객체이고 타입이 list_reverseiterator임을 보여줌
  • wat / type('ObjectCreator', (), {})는 동적으로 만든 클래스의 값, 타입 type, signature: class ObjectCreator()를 보여줌
  • wat / typetype 자체의 값, 타입 type, class type(…) 시그니처, type(object) -> the object's type, type(name, bases, dict, **kwds) -> a new type 문서, public 속성 mro 등을 보여줌
  • wat.s / List[str]는 값 typing.List[str], 타입 typing._GenericAlias, 부모 타입 typing._BaseGenericAlias, typing._Final, 시그니처 def List(*args, **kwargs)를 보여줌
  • wat(str | None)은 값 str | None, 타입 types.UnionType을 보여줌
  • Python 내장 객체 탐색 예시로 wat / __builtins__, wat / ...가 제시됨
  • WAT 자체도 검사할 수 있음
    • 예시로 wat.dunder / wat, wat.code / wat.__truediv__가 제시됨

내부 동작 요약

  • inspect_format(obj, *, short=False, dunder=False, nodocs=False, long=False, code=False, caller=False, public=False, all=False)는 객체 검사 결과를 문자열로 구성함
    • all=True이면 dunder, long, code, caller가 함께 활성화됨
    • public=True이면 private 출력이 비활성화됨
    • sys.stdout.isatty()가 참이면 터미널 너비를 얻고 출력 위아래에 구분선을 추가함
  • 검사 출력은 객체 값, 문자열 표현, 타입, 부모 타입, 길이, 시그니처, 문서, 소스 코드, 속성 섹션 순서로 생성됨
  • 속성 검사는 dir(obj)를 이름순으로 순회함
    • dunder 속성은 dunder 설정이 꺼져 있으면 제외함
    • _로 시작하는 private 속성은 private 설정이 꺼져 있으면 제외함
    • getattr(obj, key)에서 BaseException이 나면 예외 객체를 값으로 사용함
  • callable 객체는 inspect.signature(obj)를 기반으로 시그니처를 포맷함
    • 실패 시 (...) 형태의 대체 시그니처를 반환함
    • 클래스는 class , coroutine function은 async def , 함수·메서드·builtin·__name__ 보유 객체는 def 접두어를 붙임
  • code=True이고 객체가 클래스이거나 callable이면 inspect.getsource(obj)로 소스 코드를 출력함
    • OSError, TypeError, IndentationError 발생 시 실패 메시지를 반환함
  • dict와 list 포맷터는 들여쓰기 깊이가 30을 넘으면 ERROR: too deeply nested를 반환함

컬러 출력과 테마

  • 환경 변수로 컬러 출력을 제어할 수 있음
    • WAT_COLOR="false"는 콘솔의 컬러 출력을 비활성화함
    • WAT_COLOR="true"는 non-tty 환경에서도 컬러 출력을 강제함
  • WAT_COLORS 환경 변수로 컬러 테마를 커스터마이즈할 수 있음
  • 기본 테마는 BAR=0;34,TRAIT=1;34,HEAD=1;37,STR=0;32,NUMBER=0;31,NONE=0;35,TRUE=1;32,FALSE=1;31,DOCS=2;37,KEYWORD=0;34,CALLABLE=1;32,VARIABLE=1;33,CODE=0;33 형태의 ANSI 컬러 코드 매핑임
  • _strip_color(text)는 ANSI escape sequence를 정규식으로 제거함

영감

댓글과 토론

Hacker News 의견들
  • 와, 정말 좋다. 예전에 비슷한 용도로 python-ls[0]를 썼는데, 기억나지 않는 이유로 뭔가 깨졌고 더 이상 유지보수도 안 됨
    주로 snoop[1]와 pdbpp로 구성된 디버깅 도구함에 추가할 예정임. wat에 바라는 건 Jupyter에서 객체 탐색을 더 쉽게 해주는 ipy 위젯 정도임
    base64 exec 해킹도 마음에 듦. Python을 오래 썼는데도 지금까지 생각해보거나 본 적이 없어서, 앞으로 몇 가지 용도로 꼭 써볼 생각임
    [0] https://github.com/gabrielcnr/python-ls
    [1] https://pypi.org/project/snoop/

  • 재미있어 보인다. Python에서 dir을 항상 쓰고, 문서가 별로인 경우에는 공식 문서보다 더 유용할 때도 있음
    대화형 셸이 Python의 진짜 강점 중 하나인데, 그 주변에 이런 새 도구나 혁신이 더 많지 않은 게 의외임

    • help() 함수도 있음. 정말 유용함
  • 오래된 icecream의 더 화려한 버전처럼 보임
    https://github.com/gruns/icecream
    모른다면 아래쪽의 다른 언어용 구현 목록도 보면 됨
    https://github.com/gruns/icecream#icecream-in-other-language...

  • 이런 종류의 도구는 유용함
    20년 전에는 Zope용 객체 인트로스펙터를 만들었음
    요즘은 devtools를 매일 쓰고, icecream과 q는 가끔 씀. wat도 한번 써볼 예정임

  • from wat import wat
    프로젝트 성격이 이렇게 멋진데, 같은 사용 문법으로 그냥 import wat을 제공하지 않는 게 의외임. 그러면 호기심 많은 사용자가 wat/wat을 해보면서 트릭을 발견하게 만들 수도 있었을 텐데

    • import wat이면 좋겠지만, Python에서는 모듈을 호출 가능하게 만들 수 없는 제약이 있음. 그래서 더 긴 from wat import wat으로 가게 됐음
      확실하진 않지만 import wat; wat.wat / object가 더 편할 수도 있음
  • 아주 유용해 보이지만, 가독성을 명목으로 전혀 관련 없는 연산자, 여기서는 / 연산자를 오버로드하는 최근 흐름이 거슬리는 사람이 나뿐인지 궁금함

    • 이 경우 / 오버로드는 이상한 선택이라는 데 동의함. 그래도 is를 오버로드할 수 없다는 점은 아쉬움. 현실적으로는 wat(foo)만으로도 충분했을 듯함
  • 번거로운 import를 피하려면 $PYTHONSTARTUP 파일에 아래를 추가할 수도 있음
    try:
    from wat import wat
    except ImportError:
    pass

    • 심지어 꽤 멋진 base64 인라인 임포터를 추가할 수도 있음
      결국 그 출력을 찍어서, 항상 쓸 수 있도록 PYTHONPATH가 가리키는 디렉터리에 넣어둠
      계속 쓰게 될지는 봐야 함
  • 와, Python을 배울 때 이런 도구가 있었다면 판도를 바꿨을 것 같음. 언어를 배울 때 내부에서 무슨 일이 일어나는지 보는 게 핵심 경로인데, Python 기본 디버깅은 좋게 봐도 실망스러운 편임
    대신 pry를 설치하고 열성적인 Ruby 팬이 됐지만, 이 도구라면 Python을 다시 시도해보게 만들 수도 있겠음

  • 작성자는 기능을 제공하기 위해 내부적으로 표준 라이브러리의 Python inspect 모듈을 쓰고 있음. 물론 그 위에 많은 부가 가치를 더했음
    wat 모듈의 inspection.py를 보면 됨
    2번째 줄에 이렇게 있음:
    import inspect as std_inspect

  • “빠르게 뭔가를 디버그하고 싶다면, 같은 세션에서 아무것도 설치하지 않고 이 인스펙터를 쓸 수 있다”
    “이 스니펫을 Python 인터프리터에 붙여넣어 즉석에서 로드하라”
    프로젝트 README에 프로젝트 전체 사본을 base64로 인코딩한 압축 데이터로 넣어두는 발상은 꽤 기발함
    특히 꼭 필요해질 환경에 미리 넣어둘 생각을 못 했을 수 있는 이런 프로젝트에는 잘 맞음