2 คะแนน โดย GN⁺ 2024-07-26 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • WAT เป็น inspector สำหรับทำความเข้าใจตัวตนของอ็อบเจกต์ที่ไม่รู้จักใน Python runtime ได้อย่างรวดเร็ว โดยดูได้ในคราวเดียวทั้ง type, ค่า, attribute, method, parent type, signature, เอกสาร และ source code
  • วิธีใช้พื้นฐานคือ wat / object ซึ่งทำงานเหมือนกับ wat(object) และรองรับ syntax หลายแบบ เช่น wat.short / 'foo', 'foo' | wat.short, wat('foo', short=True)
  • สามารถ chain modifier เช่น .short, .dunder, .long, .code, .caller, .public, .all, .ret, .str เพื่อปรับขอบเขตการแสดงผล วิธีคืนค่า การแสดงสี และการแสดงตำแหน่งที่เรียกใช้ได้
  • ติดตั้งได้ด้วย pip install wat แล้ว import wat และสำหรับการ debug อย่างรวดเร็ว ยังสามารถวาง snippet แบบ Insta-Load ลงใน Python session เพื่อใช้งานใน session เดียวกันโดยไม่ต้องติดตั้งได้ด้วย
  • ตัวอย่างอย่าง Django User, re.match, pathlib, colorsys.hsv_to_rgb, typing.List[str], str | None แสดงให้เห็นว่า WAT ใช้ได้กับการ debug, การสำรวจใน REPL และการเรียนรู้ internals ของ Python

WAT ทำอะไร

  • WAT เป็นเครื่องมือสำหรับสำรวจและตรวจสอบอ็อบเจกต์ Python ขณะ runtime
  • เมื่อยากที่จะรู้ว่าอ็อบเจกต์ที่ไม่รู้จักคืออะไร สามารถใช้ wat inspector ใน Python console เพื่อตรวจสอบตัวตนของอ็อบเจกต์ได้
  • เมื่อรัน wat / object กับ object ใด ๆ จะดูข้อมูลต่อไปนี้ได้
    • type ของอ็อบเจกต์
    • ค่าที่จัดรูปแบบแล้ว
    • ตัวแปรและเมธอด
    • parent type
    • signature
    • เอกสาร
    • source code
  • การตรวจสอบเชิงลึกแบบเดียวกันใช้ได้ด้วย syntax wat(object) เช่นกัน
  • Wat ถูกแนะนำว่าเป็นรูปแปรของคำอังกฤษ what ซึ่งใช้แสดงความสับสนหรือความไม่พอใจ

วิธีใช้พื้นฐานและ syntax

  • ใช้ operator หารเพื่อให้พิมพ์ได้เร็ว
    • wat / foo เหมือนกับ wat(foo)
  • การตรวจสอบแบบเดียวกันสามารถใช้ syntax ได้หลายแบบ
    • wat.short / 'foo': syntax สำหรับพิมพ์เร็ว
    • wat.short('foo')
    • wat('foo', short=True): syntax แบบ Python ที่เป็นธรรมชาติ
    • 'foo' | wat.short: syntax สไตล์ Unix pipe
  • สามารถปรับพฤติกรรมการตรวจสอบได้ในรูปแบบ wat.modifier / foo
  • modifier สามารถเชื่อมต่อเป็น chain ได้ ตัวอย่างคือ wat.short.str.gray / 'foo'
  • ใน Python อ็อบเจกต์ไม่ได้หมายถึงแค่โครงสร้างข้อมูล แต่รวมถึงฟังก์ชัน คลาส โมดูล built-in type ฯลฯ ดังนั้น wat จึงสำรวจอ็อบเจกต์ใดก็ได้
  • เมื่อพิมพ์ wat ใน interpreter จะดู help ของอ็อบเจกต์ wat เองได้

ปรับขอบเขตการตรวจสอบด้วย Modifier

  • .short หรือ .s จะซ่อน attribute อย่างตัวแปรและเมธอดภายในอ็อบเจกต์ และแสดงเฉพาะค่า, type, parent type, signature และเอกสาร
  • .dunder แสดง dunder attribute ที่ขึ้นต้นด้วย __
  • .long แสดงค่าและ docstring แบบไม่ย่อ
  • .code แสดง source code ของฟังก์ชัน เมธอด และคลาส
  • .nodocs ซ่อนเอกสารของฟังก์ชันและคลาส
  • .caller แสดงวิธีและตำแหน่งที่การตรวจสอบถูกเรียกใช้ และทำงานในไฟล์ที่ไม่ใช่ REPL
  • .public ซ่อน private attribute และแสดงเฉพาะ public attribute
  • .all รวมข้อมูลทั้งหมดเท่าที่เป็นไปได้
  • .ret คืนอ็อบเจกต์กลับหลังการตรวจสอบ
  • .str คืนสตริงผลลัพธ์แทนการพิมพ์ออกมา
  • .gray ปิดการแสดงสีใน console
  • .color บังคับให้แสดงสีใน console
  • wat.locals ตรวจสอบ local variable และ wat.globals ตรวจสอบ global variable

การติดตั้งและ Insta-Load

  • ขั้นตอนการติดตั้งด้วย pip มีดังนี้
    • pip install wat
    • ใน Python ให้ import wat
  • แพ็กเกจ wat ไม่มี dependency ภายนอก
  • สำหรับการ debug อย่างรวดเร็ว มีวิธี Insta-Load ที่ใช้ได้ใน Python session เดียวกันโดยไม่ต้องติดตั้ง
  • Insta-Load เป็นวิธีที่ import base64, zlib จากนั้นกู้คืนสตริงโค้ดที่ถูกบีบอัดและเข้ารหัส แล้วรันด้วย exec(..., globals()) โดยวาง Python snippet ลงใน interpreter
  • หลังรัน Insta-Load snippet แล้ว จะใช้อ็อบเจกต์ wat ได้
  • ก่อนรัน snippet แนะนำให้ตรวจสอบสิ่งที่จะรันก่อน
    • สามารถดูเนื้อหาโค้ดที่ถูกแยกออกมาล่วงหน้าได้ด้วย print(zlib.decompress(base64.b64decode(code)).decode())
    • การวางเนื้อหาของ inspection.py ลงใน interpreter ก็ให้ผลแบบเดียวกัน
    • มีการเสนอวิธีติดตั้งแพ็กเกจด้วย pip แล้วตรวจทานโค้ดด้วย
  • WAT สามารถโหลดได้จาก Unicode glyph เพียงตัวเดียว
  • loader ที่อิง Unicode string อยู่ในรูปแบบที่แปลงสตริง emoji และ combining character ยาว ๆ เป็น byte ด้วย ord(c) & 255 แล้วรันด้วย exec(...) หลัง zlib.decompress(...)

ทำความเข้าใจชนิดอ็อบเจกต์และวิธีใช้

  • ใน Python ซึ่งเป็นภาษาชนิดข้อมูลแบบ dynamic บางครั้งอาจยากที่จะระบุ type ของอ็อบเจกต์ และ WAT Inspector จะแสดงชื่อ type พร้อมโมดูลที่ type นั้นมาจาก
  • ตัวอย่างการตรวจสอบ type แสดงค่า, type และความยาวพร้อมกัน
    • wat.short / (1,) แสดงค่า (1,), type tuple, ความยาว 1
    • wat.short / {None} แสดงค่า {None}, type set, ความยาว 1
  • ในตัวอย่างอ็อบเจกต์ Django User, wat.short / user แสดง str: admin, repr: <User: admin>, type django.contrib.auth.models.User และรายการ parent type
  • หลังตรวจสอบ type จริงแล้ว สามารถใส่ type annotation ในโค้ดเพื่อลดความสับสนภายหลังได้
  • เมื่อต้องการเข้าใจวิธีใช้อ็อบเจกต์ที่ไม่รู้จัก สามารถแสดงรายการเมธอด, signature และ docstring ได้
    • มีตัวอย่างเป็น wat / ['foo']
    • หากต้องการดู docstring ทั้งหมดให้ใช้ wat.long
  • เมื่อต้องการเข้าใจวิธีใช้ฟังก์ชัน สามารถดู docstring และ signature ของฟังก์ชันได้
    • มีตัวอย่างเป็น wat / str.split

สำรวจ attribute, โมดูล และ source code

  • สามารถแสดงรายการ attribute และ type ของแต่ละ attribute เพื่อดูภายในอ็อบเจกต์ที่ตรวจสอบได้
    • มีตัวอย่างเป็น wat / re.match('(\d)_(.*)', '1_title')
  • ใช้สำหรับสำรวจโมดูลได้ด้วย โดยสามารถแสดงรายการฟังก์ชัน คลาส และ submodule ของโมดูลที่เลือก
    • มีตัวอย่างหลัง import pathlib แล้วรัน wat / pathlib
    • จากนั้นสามารถสำรวจลึกลงไป เช่น wat / pathlib.fnmatch
  • โดยค่าเริ่มต้น WAT Inspector จะซ่อน attribute ที่ขึ้นต้นด้วย __
    • สามารถดู dunder attribute ได้ด้วย wat.dunder / {}
  • สามารถดู source code เพื่อเข้าใจว่าฟังก์ชันทำงานจริงอย่างไร
    • มีตัวอย่างหลัง import colorsys แล้วรัน wat.code / colorsys.hsv_to_rgb
  • dict และ list ที่ซ้อนกันจะถูกจัดรูปแบบให้เป็นรูปแบบที่อ่านง่ายพร้อม indentation

Debugging session และการตรวจสอบตัวแปร

  • หลังรัน interactive debugger ด้วย breakpoint() ของ Python สามารถตรวจสอบอ็อบเจกต์ ณ ตรงนั้นได้
  • ตัวอย่าง Pdb คือหลัง import wat หรือวาง Insta-Load snippet แล้วใช้ wat / foo ตรวจสอบ local variable และใช้ c เพื่อรันต่อ
  • local variable และ global variable ดูได้ด้วย wat.locals และ wat.globals ตามลำดับ
  • เมื่อเรียก wat() โดยไม่มี argument จะแสดง local variable ของ caller stack ภายใต้หัวข้อ Local variables

ตัวอย่างการเรียนรู้ internals ของ Python

  • มีตัวอย่างการใช้งานเพื่อการเรียนรู้และทำความเข้าใจการทำงานภายในของ Python
  • reversed([]) == reversed([]) เป็น False และ wat.s / reversed([]) แสดงว่าค่านั้นเป็นอ็อบเจกต์ list_reverseiterator และ type คือ list_reverseiterator
  • wat / type('ObjectCreator', (), {}) แสดงค่าของคลาสที่สร้างแบบ dynamic, type type, และ signature: class ObjectCreator()
  • wat / type แสดงค่าของ type เอง, type type, signature class type(…), เอกสาร type(object) -> the object's type, type(name, bases, dict, **kwds) -> a new type และ public attribute เช่น mro
  • wat.s / List[str] แสดงค่า typing.List[str], type typing._GenericAlias, parent type typing._BaseGenericAlias, typing._Final, และ signature def List(*args, **kwargs)
  • wat(str | None) แสดงค่า str | None และ type types.UnionType
  • ตัวอย่างการสำรวจ built-in object ของ 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() เป็นจริง จะดึงความกว้างของ terminal และเพิ่มเส้นคั่น ที่ด้านบนและล่างของ output
  • output ของการตรวจสอบถูกสร้างตามลำดับส่วนของค่าอ็อบเจกต์, string representation, type, parent type, ความยาว, signature, เอกสาร, source code และ attribute
  • การตรวจสอบ attribute จะวนผ่าน dir(obj) ตามลำดับชื่อ
    • dunder attribute จะถูกยกเว้นหากปิดการตั้งค่า dunder
    • private attribute ที่ขึ้นต้นด้วย _ จะถูกยกเว้นหากปิดการตั้งค่า private
    • หาก getattr(obj, key) เกิด BaseException จะใช้ exception object เป็นค่า
  • callable object จะจัดรูปแบบ signature จาก inspect.signature(obj)
    • หากล้มเหลว จะคืน signature สำรองรูปแบบ (...)
    • คลาสจะเติม prefix class , coroutine function เติม async def และฟังก์ชัน·เมธอด·builtin·อ็อบเจกต์ที่มี __name__ เติม prefix def
  • หาก code=True และอ็อบเจกต์เป็นคลาสหรือ callable จะพิมพ์ source code ด้วย inspect.getsource(obj)
    • หากเกิด OSError, TypeError, IndentationError จะคืนข้อความล้มเหลว
  • formatter ของ dict และ list จะคืน ERROR: too deeply nested หากความลึกของ indentation เกิน 30

การแสดงสีและธีม

  • สามารถควบคุมการแสดงสีด้วย environment variable
    • WAT_COLOR="false" ปิดการแสดงสีใน console
    • WAT_COLOR="true" บังคับให้แสดงสีแม้ใน environment แบบ non-tty
  • สามารถปรับแต่งธีมสีได้ด้วย environment variable WAT_COLORS
  • ธีมเริ่มต้นเป็นการ mapping ANSI color code ในรูปแบบ 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) ลบ ANSI escape sequence ด้วย regular expression

แรงบันดาลใจ

  • WAT ได้แรงบันดาลใจจาก Rich Inspect

1 ความคิดเห็น

 
GN⁺ 2024-07-26
ความคิดเห็นบน Hacker News
  • ว้าว ดีมากเลย ก่อนหน้านี้เคยใช้ python-ls[0] เพื่อจุดประสงค์คล้าย ๆ กัน แต่ด้วยเหตุผลที่จำไม่ได้ มันพังไปบางอย่าง และไม่ได้รับการดูแลต่อแล้ว
    ตั้งใจจะเพิ่มสิ่งนี้เข้าไปในชุดเครื่องมือดีบักของผม ซึ่งหลัก ๆ ประกอบด้วย snoop[1] และ pdbpp สิ่งที่อยากได้จาก wat คือ ipy widget ที่ช่วยให้สำรวจอ็อบเจกต์ใน Jupyter ได้ง่ายขึ้น
    ชอบ base64 exec hack ด้วย ใช้ 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 ตลอด และบางครั้งก็มีประโยชน์กว่าเอกสารทางการด้วยซ้ำในกรณีที่เอกสารไม่ค่อยดี
    interactive shell เป็นหนึ่งในจุดแข็งจริง ๆ ของ Python เลย แต่ก็น่าแปลกที่ไม่มีเครื่องมือใหม่ ๆ หรือการสร้างสรรค์รอบ ๆ ส่วนนี้มากกว่านี้

    • ยังมี ฟังก์ชัน help() ด้วย มีประโยชน์จริง ๆ
  • ดูเหมือน icecream รุ่นเก่าที่ฉูดฉาดกว่า
    https://github.com/gruns/icecream
    ถ้ายังไม่รู้จัก ลองดูรายการ implementation สำหรับภาษาอื่น ๆ ด้านล่างด้วยก็ได้
    https://github.com/gruns/icecream#icecream-in-other-language...

    • เจ๋งดี เมื่อหลายปีก่อนผมเคยทำของคล้าย ๆ กันสำหรับ Java แต่เป็นแบบเว็บ: https://scg.unibe.ch/wiki/projects/DoodleDebug
  • เครื่องมือประเภทนี้มีประโยชน์
    เมื่อ 20 ปีก่อนผมเคยทำ object introspector สำหรับ 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 อาจจะสะดวกกว่าก็ได้
  • ดูมีประโยชน์มาก แต่สงสัยว่ามีแค่ผมหรือเปล่าที่รำคาญเทรนด์ช่วงหลัง ๆ ที่เอา operator ที่ไม่เกี่ยวข้องเลยมา overload ในนามของความอ่านง่าย อย่างในกรณีนี้คือ operator /

    • กรณีนี้เห็นด้วยว่า overload / เป็นตัวเลือกที่แปลก แต่ก็เสียดายที่ ไม่สามารถ overload is ได้ เอาเข้าจริง wat(foo) ก็น่าจะพอแล้ว
  • ถ้าอยากเลี่ยง import ที่ยุ่งยาก ก็เพิ่มสิ่งนี้ลงในไฟล์ $PYTHONSTARTUP ได้
    try:
    from wat import wat
    except ImportError:
    pass

    • ถึงขั้นเพิ่ม base64 inline importer ที่ค่อนข้างเท่ได้ด้วย
      สุดท้ายผมพิมพ์ output นั้นออกมา แล้วเอาไปใส่ไว้ในไดเรกทอรีที่ PYTHONPATH ชี้อยู่ เพื่อให้ใช้ได้ตลอด
      ต้องดูต่อไปว่าจะใช้ต่อเนื่องไหม
  • ว้าว ถ้ามีเครื่องมือแบบนี้ตอนผมเรียน Python มันคง เปลี่ยนเกม ไปเลย การได้เห็นว่าข้างในเกิดอะไรขึ้นเป็นเส้นทางสำคัญตอนเรียนภาษา แต่การดีบักพื้นฐานของ Python นั้นน่าผิดหวังอย่างน้อยก็พูดได้แค่นั้น
    แทนที่จะเป็นแบบนั้น ผมไปติดตั้ง pry แล้วกลายเป็นแฟน Ruby ตัวยง แต่เครื่องมือนี้อาจทำให้ผมลองกลับมาใช้ Python อีกครั้งก็ได้

  • ผู้เขียนใช้ โมดูล Python inspect จาก standard library ภายในเพื่อ提供ฟีเจอร์ แน่นอนว่าได้เพิ่มคุณค่าอีกมากไว้ด้านบน
    ดู inspection.py ของโมดูล wat ได้
    บรรทัดที่ 2 มีแบบนี้:
    import inspect as std_inspect

  • “ถ้าคุณอยากดีบักอะไรบางอย่างอย่างรวดเร็ว คุณสามารถใช้ inspector นี้ใน session เดียวกันได้โดยไม่ต้องติดตั้งอะไร”
    “วาง snippet นี้ลงใน Python interpreter เพื่อโหลดแบบทันที”
    ไอเดียการใส่ สำเนาทั้งโปรเจกต์ ลงใน README ของโปรเจกต์ในรูปแบบข้อมูลบีบอัดที่เข้ารหัสเป็น base64 นั้นค่อนข้างแหวกแนว
    โดยเฉพาะกับโปรเจกต์แบบนี้ที่เหมาะมาก เพราะคุณอาจไม่ได้นึกจะเตรียมใส่มันไว้ล่วงหน้าในสภาพแวดล้อมที่จำเป็นต้องใช้จริง ๆ