2 điểm bởi GN⁺ 2024-07-26 | 1 bình luận | Chia sẻ qua WhatsApp
  • WAT là một inspector để nhanh chóng xác định bản chất của các đối tượng chưa rõ trong runtime Python, cho phép xem cùng lúc kiểu, giá trị, thuộc tính, phương thức, kiểu cha, chữ ký, tài liệu và cả mã nguồn
  • Cách dùng cơ bản là wat / object, hoạt động tương đương với wat(object), đồng thời hỗ trợ nhiều cú pháp như wat.short / 'foo', 'foo' | wat.short, wat('foo', short=True)
  • Có thể nối chuỗi các modifier như .short, .dunder, .long, .code, .caller, .public, .all, .ret, .str để điều chỉnh phạm vi hiển thị, cách trả về kết quả, đầu ra màu và hiển thị vị trí gọi
  • Cài đặt bằng pip install wat rồi import wat, và để debug nhanh còn có thể dán snippet Insta-Load vào phiên Python để dùng ngay trong cùng phiên mà không cần cài đặt
  • Các ví dụ như Django User, re.match, pathlib, colorsys.hsv_to_rgb, typing.List[str], str | None cho thấy WAT có thể được dùng để debug, khám phá trong REPL và học về nội bộ Python

WAT làm gì

  • WAT là công cụ để khám phá và kiểm tra đối tượng Python ngay trong runtime
  • Khi khó xác định một đối tượng chưa rõ là gì, có thể dùng inspector wat trong console Python để điều tra bản chất của đối tượng đó
  • Với một object bất kỳ, chạy wat / object sẽ cho biết các thông tin sau
    • type của đối tượng
    • giá trị đã được định dạng
    • biến và phương thức
    • kiểu cha
    • chữ ký
    • tài liệu
    • mã nguồn
  • Việc kiểm tra chuyên sâu tương tự cũng có thể thực hiện bằng cú pháp wat(object)
  • Wat được giới thiệu là một biến thể của từ tiếng Anh what, dùng để diễn tả sự bối rối hoặc khó chịu

Cách dùng cơ bản và cú pháp

  • Dùng toán tử chia để nhập nhanh
    • wat / foo tương đương với wat(foo)
  • Có nhiều cú pháp cho cùng một thao tác kiểm tra
    • wat.short / 'foo': cú pháp để nhập nhanh
    • wat.short('foo')
    • wat('foo', short=True): cú pháp Python tự nhiên
    • 'foo' | wat.short: cú pháp kiểu pipe Unix
  • Có thể điều chỉnh hành vi kiểm tra với dạng wat.modifier / foo
  • Các modifier có thể nối chuỗi với nhau, ví dụ wat.short.str.gray / 'foo'
  • Trong Python, đối tượng không chỉ là cấu trúc dữ liệu mà còn bao gồm hàm, class, module, kiểu built-in..., nên wat có thể khám phá mọi loại đối tượng
  • wat trong interpreter sẽ hiển thị trợ giúp về chính đối tượng wat

Điều chỉnh phạm vi kiểm tra bằng Modifier

  • .short hoặc .s ẩn các thuộc tính như biến và phương thức bên trong đối tượng, chỉ in ra giá trị, kiểu, kiểu cha, chữ ký và tài liệu
  • .dunder hiển thị các thuộc tính dunder bắt đầu bằng __
  • .long hiển thị giá trị và docstring không bị rút gọn
  • .code hiển thị mã nguồn của hàm, phương thức và class
  • .nodocs ẩn tài liệu của hàm và class
  • .caller hiển thị cách và vị trí việc kiểm tra được gọi, hoạt động trong file chứ không phải REPL
  • .public ẩn thuộc tính private và chỉ hiển thị thuộc tính public
  • .all bao gồm mọi thông tin có thể
  • .ret trả lại đối tượng sau khi kiểm tra
  • .str trả về chuỗi kết quả thay vì in ra
  • .gray tắt đầu ra màu trong console
  • .color ép bật đầu ra màu trong console
  • wat.locals kiểm tra biến cục bộ, còn wat.globals kiểm tra biến toàn cục

Cài đặt và Insta-Load

  • Quy trình cài bằng pip như sau
    • pip install wat
    • trong Python, import wat
  • Gói wat không có phụ thuộc bên ngoài
  • Để debug nhanh, công cụ còn cung cấp cách Insta-Load giúp dùng ngay trong cùng phiên Python mà không cần cài đặt
  • Insta-Load hoạt động bằng cách import base64, zlib, sau đó khôi phục chuỗi mã đã được nén và mã hóa rồi chạy bằng exec(..., globals()) trong một snippet Python dán vào interpreter
  • Sau khi chạy snippet Insta-Load, có thể sử dụng đối tượng wat
  • Trước khi chạy snippet, nên kiểm tra nội dung sắp thực thi
    • Có thể xem trước mã đã giải ra bằng print(zlib.decompress(base64.b64decode(code)).decode())
    • Cũng có thể dán nội dung của inspection.py vào interpreter để đạt hiệu quả tương tự
    • Một cách khác được đề xuất là cài package bằng pip và tự rà soát mã
  • WAT có thể được nạp từ một Unicode glyph duy nhất
  • Loader dựa trên chuỗi Unicode sẽ chuyển chuỗi dài gồm emoji và ký tự kết hợp thành byte bằng ord(c) & 255, sau đó chạy zlib.decompress(...) rồi exec(...)

Xác định kiểu đối tượng và cách sử dụng

  • Trong Python là ngôn ngữ định kiểu động, đôi khi khó xác định kiểu của đối tượng; WAT Inspector sẽ hiển thị tên kiểu và module mà kiểu đó thuộc về
  • Ví dụ kiểm tra kiểu cho thấy cả giá trị, kiểu và độ dài
    • wat.short / (1,) in ra giá trị (1,), kiểu tuple, độ dài 1
    • wat.short / {None} in ra giá trị {None}, kiểu set, độ dài 1
  • Trong ví dụ với đối tượng Django User, wat.short / user in ra str: admin, repr: <User: admin>, kiểu django.contrib.auth.models.User và danh sách kiểu cha
  • Sau khi xác nhận được kiểu thực tế, có thể thêm type annotation vào code để giảm nhầm lẫn về sau
  • Khi muốn hiểu cách dùng của một đối tượng chưa rõ, có thể in danh sách phương thức, chữ ký và docstring
    • Ví dụ được đưa ra là wat / ['foo']
    • Nếu muốn xem toàn bộ docstring thì dùng wat.long
  • Để nắm cách dùng của hàm, có thể xem docstring và chữ ký của hàm
    • Ví dụ là wat / str.split

Khám phá thuộc tính, module và mã nguồn

  • Để xem bên trong đối tượng đang kiểm tra, có thể liệt kê thuộc tính và kiểu của từng thuộc tính
    • Ví dụ là wat / re.match('(\\d)_(.*)', '1_title')
  • Công cụ cũng có thể dùng để khám phá module, liệt kê các hàm, class và submodule trong module đã chọn
    • Có ví dụ chạy import pathlib rồi wat / pathlib
    • Sau đó có thể đi sâu hơn như wat / pathlib.fnmatch
  • Theo mặc định, WAT Inspector ẩn các thuộc tính bắt đầu bằng __
    • Có thể xem thuộc tính dunder bằng wat.dunder / {}
  • Để kiểm tra hàm thực sự hoạt động như thế nào, có thể xem mã nguồn của nó
    • Ví dụ là chạy import colorsys rồi wat.code / colorsys.hsv_to_rgb
  • Các dictlist lồng nhau sẽ được định dạng thụt lề để dễ đọc

Phiên debug và kiểm tra biến

  • Có thể chạy debugger tương tác của Python bằng breakpoint() rồi kiểm tra đối tượng ngay tại đó
  • Ví dụ Pdb cho thấy sau khi import wat hoặc dán snippet Insta-Load, có thể dùng wat / foo để kiểm tra biến cục bộ rồi nhấn c để tiếp tục chạy
  • Biến cục bộ và biến toàn cục có thể xem lần lượt bằng wat.locals, wat.globals
  • Gọi wat() không có đối số sẽ in các biến cục bộ trong stack của caller dưới tiêu đề Local variables

Ví dụ học về nội bộ Python

  • Có kèm các ví dụ phục vụ việc học cách Python vận hành bên trong
  • reversed([]) == reversed([])False, và wat.s / reversed([]) cho thấy giá trị là đối tượng list_reverseiterator và kiểu cũng là list_reverseiterator
  • wat / type('ObjectCreator', (), {}) hiển thị giá trị của class được tạo động, kiểu type, chữ ký signature: class ObjectCreator()
  • wat / type hiển thị giá trị của chính type, kiểu type, chữ ký class type(…), tài liệu type(object) -> the object's type, type(name, bases, dict, **kwds) -> a new type, cùng các thuộc tính public như mro
  • wat.s / List[str] hiển thị giá trị typing.List[str], kiểu typing._GenericAlias, kiểu cha typing._BaseGenericAlias, typing._Final, chữ ký def List(*args, **kwargs)
  • wat(str | None) hiển thị giá trị str | None, kiểu types.UnionType
  • Các ví dụ khám phá đối tượng built-in của Python gồm wat / __builtins__, wat / ...
  • Có thể kiểm tra cả chính WAT
    • Ví dụ như wat.dunder / wat, wat.code / wat.__truediv__

Tóm tắt cách hoạt động bên trong

  • inspect_format(obj, *, short=False, dunder=False, nodocs=False, long=False, code=False, caller=False, public=False, all=False) tạo chuỗi kết quả kiểm tra đối tượng
    • Nếu all=True thì dunder, long, code, caller sẽ cùng được bật
    • Nếu public=True thì đầu ra private sẽ bị tắt
    • Nếu sys.stdout.isatty() là đúng, công cụ sẽ lấy độ rộng terminal và thêm các đường phân cách ở trên và dưới đầu ra
  • Kết quả kiểm tra được tạo theo thứ tự: giá trị đối tượng, biểu diễn chuỗi, kiểu, kiểu cha, độ dài, chữ ký, tài liệu, mã nguồn, rồi đến phần thuộc tính
  • Việc kiểm tra thuộc tính sẽ duyệt dir(obj) theo thứ tự tên
    • Thuộc tính dunder sẽ bị loại nếu tùy chọn dunder đang tắt
    • Thuộc tính private bắt đầu bằng _ sẽ bị loại nếu tùy chọn private đang tắt
    • Nếu getattr(obj, key) ném BaseException, đối tượng ngoại lệ sẽ được dùng làm giá trị
  • Với đối tượng callable, công cụ định dạng chữ ký dựa trên inspect.signature(obj)
    • Nếu thất bại, nó trả về chữ ký thay thế dạng (...)
    • Với class sẽ thêm tiền tố class , với coroutine function là async def , còn hàm, phương thức, builtin hoặc đối tượng có __name__ sẽ thêm tiền tố def
  • Khi code=True và đối tượng là class hoặc callable, mã nguồn sẽ được lấy bằng inspect.getsource(obj)
    • Nếu xảy ra OSError, TypeError, IndentationError thì sẽ trả về thông báo thất bại
  • Formatter cho dict và list sẽ trả về ERROR: too deeply nested nếu độ sâu thụt lề vượt quá 30

Đầu ra màu và theme

  • Có thể điều khiển đầu ra màu bằng biến môi trường
    • WAT_COLOR="false" tắt đầu ra màu trong console
    • WAT_COLOR="true" ép bật màu ngay cả trong môi trường non-tty
  • Có thể tùy biến theme màu bằng biến môi trường WAT_COLORS
  • Theme mặc định là ánh xạ mã màu ANSI dạng 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
  • _strip_color(text) loại bỏ ANSI escape sequence bằng regex

Cảm hứng

1 bình luận

 
GN⁺ 2024-07-26
Ý kiến trên Hacker News
  • Wow, rất hay. Trước đây tôi từng dùng python-ls[0] cho mục đích tương tự, nhưng vì lý do nào đó tôi không nhớ, nó bị hỏng và cũng không còn được bảo trì nữa
    Tôi định thêm nó vào bộ công cụ debug của mình, vốn chủ yếu gồm snoop[1] và pdbpp. Điều tôi mong ở wat là một ipy widget giúp việc khám phá object trong Jupyter dễ hơn
    Tôi cũng thích mẹo hack exec bằng base64. Dùng Python lâu rồi mà đến giờ tôi vẫn chưa từng nghĩ tới hay thấy cách này, nên chắc chắn sẽ thử dùng cho vài mục đích trong tương lai
    [0] https://github.com/gabrielcnr/python-ls
    [1] https://pypi.org/project/snoop/

  • Trông thú vị. Trong Python tôi luôn dùng dir, và khi tài liệu không tốt lắm thì đôi khi nó còn hữu ích hơn tài liệu chính thức
    Interactive shell là một trong những điểm mạnh thật sự của Python, nên hơi ngạc nhiên là không có nhiều công cụ mới hay đổi mới hơn quanh nó

    • Còn có hàm help() nữa. Thực sự hữu ích
  • Trông giống một phiên bản hào nhoáng hơn của icecream
    https://github.com/gruns/icecream
    Nếu chưa biết, bạn cũng có thể xem danh sách các bản triển khai cho ngôn ngữ khác ở phía dưới
    https://github.com/gruns/icecream#icecream-in-other-language...

  • Những công cụ kiểu này rất hữu ích
    20 năm trước tôi từng làm một object introspector cho Zope
    Dạo này tôi dùng devtools hằng ngày, còn icecream và q thì thỉnh thoảng. Tôi cũng sẽ thử wat

  • from wat import wat
    Với tính chất dự án ngầu như vậy, tôi hơi ngạc nhiên là nó không cung cấp luôn cú pháp import wat với cách dùng tương tự. Như thế còn có thể khiến người dùng tò mò thử wat/wat rồi phát hiện ra trò này

    • import wat thì hay thật, nhưng trong Python có hạn chế là không thể làm cho module gọi được như hàm. Vì vậy mới phải dùng dạng dài hơn from wat import wat
      Không chắc, nhưng import wat; wat.wat / object có khi lại tiện hơn
  • Trông rất hữu ích, nhưng không biết có phải chỉ mình tôi thấy khó chịu với xu hướng gần đây là nhân danh khả năng đọc mà overload các toán tử hoàn toàn không liên quan, ở đây là toán tử / hay không

    • Tôi đồng ý rằng overload / trong trường hợp này là một lựa chọn lạ. Dù vậy, cũng tiếc là không thể overload is. Thực tế thì chỉ wat(foo) có lẽ cũng đủ rồi
  • Nếu muốn tránh việc import phiền phức, bạn cũng có thể thêm đoạn sau vào file $PYTHONSTARTUP
    try:
    from wat import wat
    except ImportError:
    pass

    • Thậm chí có thể thêm một base64 inline importer khá hay
      Cuối cùng tôi in output của nó ra rồi đặt vào một thư mục mà PYTHONPATH trỏ tới, để lúc nào cũng dùng được
      Còn phải xem có tiếp tục dùng không
  • Wow, nếu có công cụ như thế này khi tôi học Python thì chắc đã thay đổi cục diện. Khi học một ngôn ngữ, việc nhìn thấy những gì đang diễn ra bên trong là con đường cốt lõi, còn debug mặc định của Python thì nói nhẹ nhất cũng là đáng thất vọng
    Thay vào đó tôi đã cài pry và trở thành fan Ruby nhiệt thành, nhưng công cụ này có thể khiến tôi thử lại Python

  • Tác giả dùng module inspect của Python trong thư viện chuẩn ở bên trong để cung cấp chức năng. Tất nhiên là đã bổ sung rất nhiều giá trị gia tăng ở phía trên
    Hãy xem inspection.py của module wat
    Dòng thứ 2 có đoạn này:
    import inspect as std_inspect

  • “Nếu muốn debug nhanh thứ gì đó, bạn có thể dùng inspector này trong cùng phiên mà không cần cài đặt gì”
    “Dán snippet này vào Python interpreter để load ngay tại chỗ”
    Ý tưởng đưa toàn bộ bản sao dự án vào README của dự án dưới dạng dữ liệu nén mã hóa base64 khá sáng tạo
    Đặc biệt phù hợp với những dự án kiểu này, nơi bạn có thể đã không nghĩ đến việc cài sẵn nó trong môi trường mà rồi chắc chắn sẽ cần tới