Show HN: Wat – Python 객체의 심층 검사
(github.com/igrek51)- 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,.str등 modifier를 체인으로 붙여 출력 범위, 반환 방식, 컬러 출력, 호출 위치 표시를 조정할 수 있음- 설치는
pip install wat후import 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 콘솔에서
watinspector로 객체의 정체를 조사할 수 있음 - 임의의
object에 대해wat / object를 실행하면 다음 정보를 확인할 수 있음- 객체의 type
- 포맷된 값
- 변수와 메서드
- 부모 타입
- 시그니처
- 문서
- 소스 코드
- 같은 심층 검사는
wat(object)문법으로도 사용할 수 있음 Wat은 영어what의 변형으로, 혼란이나 불쾌감을 표현할 때 쓰이는 말로 소개됨
기본 사용법과 문법
- 빠른 입력을 위해 나눗셈 연산자를 사용함
wat / foo는wat(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 실행 후에는
watobject를 사용할 수 있음 - 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 / user는str: 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 pathlib후wat / pathlib을 실행하는 예시가 있음- 이후
wat / pathlib.fnmatch처럼 더 깊이 탐색할 수 있음
- WAT Inspector는 기본적으로
__로 시작하는 속성을 숨김wat.dunder / {}로 dunder 속성을 볼 수 있음
- 함수가 실제로 어떻게 동작하는지 확인하기 위해 소스 코드를 볼 수 있음
import colorsys후wat.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 / type은type자체의 값, 타입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이 나면 예외 객체를 값으로 사용함
- dunder 속성은
- 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를 정규식으로 제거함
영감
- WAT는 Rich Inspect에서 영감을 받음
댓글과 토론
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/- Jupyter에서 객체를 살펴볼 때 NumPy 계열 작업이 많다면 penzai도 볼 만함: https://github.com/google-deepmind/penzai
-
재미있어 보인다. Python에서 dir을 항상 쓰고, 문서가 별로인 경우에는 공식 문서보다 더 유용할 때도 있음
대화형 셸이 Python의 진짜 강점 중 하나인데, 그 주변에 이런 새 도구나 혁신이 더 많지 않은 게 의외임- help() 함수도 있음. 정말 유용함
-
오래된 icecream의 더 화려한 버전처럼 보임
https://github.com/gruns/icecream
모른다면 아래쪽의 다른 언어용 구현 목록도 보면 됨
https://github.com/gruns/icecream#icecream-in-other-language...- 괜찮다. 몇 년 전에 Java용으로 비슷하지만 웹 기반인 걸 만들었음: https://scg.unibe.ch/wiki/projects/DoodleDebug
-
이런 종류의 도구는 유용함
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가 가리키는 디렉터리에 넣어둠
계속 쓰게 될지는 봐야 함
- 심지어 꽤 멋진 base64 인라인 임포터를 추가할 수도 있음
-
와, Python을 배울 때 이런 도구가 있었다면 판도를 바꿨을 것 같음. 언어를 배울 때 내부에서 무슨 일이 일어나는지 보는 게 핵심 경로인데, Python 기본 디버깅은 좋게 봐도 실망스러운 편임
대신 pry를 설치하고 열성적인 Ruby 팬이 됐지만, 이 도구라면 Python을 다시 시도해보게 만들 수도 있겠음 -
작성자는 기능을 제공하기 위해 내부적으로 표준 라이브러리의 Python inspect 모듈을 쓰고 있음. 물론 그 위에 많은 부가 가치를 더했음
wat 모듈의inspection.py를 보면 됨
2번째 줄에 이렇게 있음:
import inspect as std_inspect -
“빠르게 뭔가를 디버그하고 싶다면, 같은 세션에서 아무것도 설치하지 않고 이 인스펙터를 쓸 수 있다”
“이 스니펫을 Python 인터프리터에 붙여넣어 즉석에서 로드하라”
프로젝트 README에 프로젝트 전체 사본을 base64로 인코딩한 압축 데이터로 넣어두는 발상은 꽤 기발함
특히 꼭 필요해질 환경에 미리 넣어둘 생각을 못 했을 수 있는 이런 프로젝트에는 잘 맞음