Show HN: Wat – การตรวจสอบเชิงลึกสำหรับอ็อบเจกต์ Python
(github.com/igrek51)- 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
- เมื่อยากที่จะรู้ว่าอ็อบเจกต์ที่ไม่รู้จักคืออะไร สามารถใช้
watinspector ใน 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บังคับให้แสดงสีใน consolewat.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,), typetuple, ความยาว1wat.short / {None}แสดงค่า{None}, typeset, ความยาว1
- ในตัวอย่างอ็อบเจกต์ Django
User,wat.short / userแสดงstr: admin,repr: <User: admin>, typedjango.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 / {}
- สามารถดู dunder attribute ได้ด้วย
- สามารถดู 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_reverseiteratorwat / type('ObjectCreator', (), {})แสดงค่าของคลาสที่สร้างแบบ dynamic, typetype, และsignature: class ObjectCreator()wat / typeแสดงค่าของtypeเอง, typetype, signatureclass type(…), เอกสารtype(object) -> the object's type,type(name, bases, dict, **kwds) -> a new typeและ public attribute เช่นmrowat.s / List[str]แสดงค่าtyping.List[str], typetyping._GenericAlias, parent typetyping._BaseGenericAlias,typing._Final, และ signaturedef List(*args, **kwargs)wat(str | None)แสดงค่าstr | Noneและ typetypes.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 เป็นค่า
- dunder attribute จะถูกยกเว้นหากปิดการตั้งค่า
- callable object จะจัดรูปแบบ signature จาก
inspect.signature(obj)- หากล้มเหลว จะคืน signature สำรองรูปแบบ
(...) - คลาสจะเติม prefix
class, coroutine function เติมasync defและฟังก์ชัน·เมธอด·builtin·อ็อบเจกต์ที่มี__name__เติม prefixdef
- หากล้มเหลว จะคืน signature สำรองรูปแบบ
- หาก
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"ปิดการแสดงสีใน consoleWAT_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 ความคิดเห็น
ความคิดเห็นบน 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/
ดูน่าสนุกดี ใน Python ผมใช้ dir ตลอด และบางครั้งก็มีประโยชน์กว่าเอกสารทางการด้วยซ้ำในกรณีที่เอกสารไม่ค่อยดี
interactive shell เป็นหนึ่งในจุดแข็งจริง ๆ ของ Python เลย แต่ก็น่าแปลกที่ไม่มีเครื่องมือใหม่ ๆ หรือการสร้างสรรค์รอบ ๆ ส่วนนี้มากกว่านี้
ดูเหมือน icecream รุ่นเก่าที่ฉูดฉาดกว่า
https://github.com/gruns/icecream
ถ้ายังไม่รู้จัก ลองดูรายการ implementation สำหรับภาษาอื่น ๆ ด้านล่างด้วยก็ได้
https://github.com/gruns/icecream#icecream-in-other-language...
เครื่องมือประเภทนี้มีประโยชน์
เมื่อ 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
//เป็นตัวเลือกที่แปลก แต่ก็เสียดายที่ ไม่สามารถ overloadisได้ เอาเข้าจริงwat(foo)ก็น่าจะพอแล้วถ้าอยากเลี่ยง import ที่ยุ่งยาก ก็เพิ่มสิ่งนี้ลงในไฟล์
$PYTHONSTARTUPได้try:from wat import watexcept ImportError:passสุดท้ายผมพิมพ์ 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 นั้นค่อนข้างแหวกแนว
โดยเฉพาะกับโปรเจกต์แบบนี้ที่เหมาะมาก เพราะคุณอาจไม่ได้นึกจะเตรียมใส่มันไว้ล่วงหน้าในสภาพแวดล้อมที่จำเป็นต้องใช้จริง ๆ