5 คะแนน โดย GN⁺ 2025-03-31 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • แม้แต่สคริปต์เล็ก ๆ สำหรับทดสอบ API ก็ยังต้องจัดสภาพแวดล้อมรันให้ตรงกันเพราะมี dependency แต่ถ้าใช้ uv execution shebang ก็ทำให้เป็นรูปแบบที่รันได้ทันทีโดยไม่ต้องมีขั้นตอนติดตั้ง
  • ตัวอย่าง jam_users.py ใช้ httpx, IPython, loguru เพื่อทดสอบ endpoint /users ของ Go API โดยลบและสร้างผู้ใช้ แล้วเข้าสู่ REPL
  • วิธีเดิมต้องติดตั้งแพ็กเกจแบบ global ลงในระบบ Python หรือเตรียม virtual environment เอง ทำให้ทั้งการแชร์สคริปต์และการรันซ้ำทำได้ยุ่งยาก
  • หากประกาศ dependencies = ["ipython", "httpx", "loguru"] ในส่วนหัว # /// script แล้วรัน uv run jam_users.py uv จะจัดการทั้งสภาพแวดล้อมแบบแยกและการติดตั้ง dependency ให้
  • ถ้าเพิ่ม shebang #!/usr/bin/env -S uv run --script และสิทธิ์ execute ก็จะสามารถรันแบบ ./jam_users.py ได้บนระบบ Unix ในสภาพที่ ติดตั้งแค่ uv

ประกาศ dependency ภายในสคริปต์

  • ตัวอย่าง jam_users.py เตรียมข้อมูลผู้ใช้ทดสอบสำหรับ local API http://localhost:4000/v1/users
    • ส่งคำขอ API ด้วย httpx
    • เข้า REPL ของ IPython เพื่อตรวจดู response และทดสอบต่อ
    • ใช้ loguru บันทึก log การลบและการสร้าง
  • สคริปต์พื้นฐานมีลำดับการทำงานเป็นการรีเซ็ตรายการผู้ใช้ก่อน แล้วเติมข้อมูลทดสอบกลับเข้าไป
    • ดึงรายการผู้ใช้เดิมด้วย GET /v1/users
    • เรียก DELETE /v1/users/{id} สำหรับผู้ใช้แต่ละคน
    • ส่งรายการผู้ใช้ที่เตรียมไว้ไปยัง POST /v1/users ในรูปแบบ JSON
    • จากนั้นเปิด REPL ด้วย IPython.embed()
  • หากจะรันด้วย python jam_users.py จำเป็นต้องติดตั้ง httpx, IPython, loguru ไว้ในสภาพแวดล้อมรันล่วงหน้าแล้ว
  • จะติดตั้งแบบ global ลงในระบบ Python หรือสร้าง virtual environment แยกต่างหากก็ได้ แต่ทั้งสองวิธีต้องมีการเตรียมพร้อมก่อนรัน
    • การติดตั้งแบบ global อาจทำให้ระบบ Python มีแพ็กเกจปะปนจนรก
    • วิธี virtual environment ต้องจัดการขั้นตอนสร้าง เปิดใช้งาน ติดตั้ง และรันด้วยตัวเอง
    • ทั้งสองวิธียังต้องมีระบบ Python ที่เข้ากันได้กับแพ็กเกจที่ต้องใช้ด้วย

สร้างสคริปต์ที่รันได้โดยตรงด้วย uv

  • ถ้าใส่แท็ก # /// script ของ uv ไว้ที่ด้านบนของสคริปต์ ก็สามารถประกาศ dependency ไว้ในไฟล์ได้
# /// script

# dependencies = ["ipython", "httpx", "loguru"]

# ///
  • เมื่อมีส่วนหัวนี้ ก็สามารถรันด้วย uv run jam_users.py ได้
    • uv จะสร้าง virtual environment แบบแยก สำหรับสคริปต์
    • ดาวน์โหลดและติดตั้ง dependency ที่จำเป็น
    • รันสคริปต์ภายใต้บริบทของ virtual environment นั้น
  • shebang Python ทั่วไปอย่าง #!/usr/bin/env python ใช้งานส่วนหัวสคริปต์ของ uv ไม่ได้ เพราะ Python จะมองข้ามคอมเมนต์ # /// script
  • หากใส่การเรียก uv ลงใน shebang โดยตรง ก็จะทำให้สคริปต์ถูกใช้งานเหมือนไฟล์ executable ได้
#!/usr/bin/env -S uv run --script

# /// script

# dependencies = ["ipython", "httpx", "loguru"]

# ///
  • แฟลก -S ของ env ใช้แยกสตริงที่ตามมาออกเป็นอาร์กิวเมนต์ต่างหากแล้วส่งต่อให้ env
  • หลังให้สิทธิ์ execute ด้วย chmod +x jam_users.py แล้ว ก็สามารถรันได้โดยตรงแบบนี้
./jam_users.py
  • วิธีนี้ทำให้บนระบบ Unix ที่ ติดตั้ง uv อยู่แล้ว สามารถรันสคริปต์ได้โดยไม่ต้องติดตั้ง dependency เพิ่มหรือจัดการ virtual environment เอง
  • เมื่อต้องส่งต่อสคริปต์ Python ที่ซับซ้อนให้ผู้ใช้อื่น ก็ช่วยลดภาระในการอธิบายขั้นตอนเตรียมระบบก่อนรันที่มักจะยาวและยุ่งยาก

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

 
GN⁺ 2025-03-31
ความคิดเห็นบน Hacker News
  • ไม่ใช่แค่ตัว UV เอง แต่โดยรวมแล้วรู้สึกขัดใจกับวิธีที่ ใช้คอมเมนต์ควบคุมการรันโค้ด
    การใช้คอมเมนต์สำหรับคำสั่งให้ linter หรือบันทึกสำหรับนักพัฒนานั้นโอเค แต่ถ้าเป็นข้อมูลเกี่ยวกับการตั้งค่าหรือการรัน ผมมองว่ารูปแบบอย่าง UV_ENV = { "dependencies": { "requests": "2.32.3", "pandas": "2.2.3" } } ดีกว่ามาก
    วิธีนี้เป็นไวยากรณ์ Python ที่ถูกต้อง ใช้โครงสร้างข้อมูลมาตรฐานแทนการ parse คอมเมนต์ตามอำเภอใจ จึงสร้างและตรวจสอบได้ง่ายกว่า และที่สำคัญที่สุดคือยังรักษาหลักการที่ว่า แม้ลบคอมเมนต์ทั้งหมดออก โค้ดก็ควรรันได้เหมือนเดิม

    • เห็นด้วย แต่อยากไปไกลกว่านั้นอีกขั้น
      วิธีที่เสนอมาก็ยังเป็น ค่าคงที่แบบเมจิก ที่ตอน runtime ไม่ได้ทำอะไรเลย และจะถูก parse ด้วย static analysis เท่านั้น แถมเครื่องมืออื่นอาจมองว่าเป็นโค้ดที่ไม่ได้ใช้แล้วลบทิ้งไป แทนที่ uv จะได้อ่าน
      น่าจะให้เรียก uv โดยตรงว่าต้องทำอะไรไปเลย เช่น import uv แล้วตามด้วย uv.exec(dependencies=["clown"], python=">=3.10")
      การรันครั้งแรกให้ทำบน Python runtime ใดก็ได้ที่หาแพ็กเกจ uv สมมตินี้เจอ จากนั้นแพ็กเกจ uv ก็เตรียม virtual environment และ Python runtime แล้วติด flag เช่น environment variable ก่อน re-exec(3)
      ใน runtime รอบที่สอง uv.exec ที่ตรวจพบ flag แล้วก็ไม่ต้องทำอะไร
    • นี่ไม่ใช่สิ่งที่ uv คิดขึ้นเอง แต่เป็นการใช้มาตรฐาน PEP 723 เหมือนเครื่องมืออื่น ๆ
      https://peps.python.org/pep-0723/
    • ฟังขึ้น แต่ผมมองว่าการ parse และประเมินโค้ดแบบ imperative นั้นยากกว่าและยืดหยุ่นน้อยกว่าการเก็บเป็นข้อมูลแบบ declarative ตาม หลักการให้สิทธิ์น้อยที่สุด มาก
      อีกอย่าง บรรทัด shebang เองก็ทำงานเป็นคอมเมนต์อยู่แล้วในความเป็นจริง
      แค่ฝังรากลึกมา 45 ปีจนคนไม่ค่อยตระหนักว่ามันคือคอมเมนต์ของ shell เท่านั้นเอง
    • เงื่อนไขที่ว่า “แม้ลบคอมเมนต์ออก โค้ดก็ควรรันได้เหมือนเดิม” ยังถูกต้องอยู่
      ถ้าติดตั้ง dependency ชุดเดียวกัน โค้ดก็รันเหมือนเดิม
      มันไม่ได้เปลี่ยนความหมายของตัวโค้ดโดยตรง แต่เปลี่ยน สภาพแวดล้อม ที่โค้ดถูกรัน และในแง่นั้นก็ไม่ต่างจากคอมเมนต์ #!/bin/bash ที่อยู่บรรทัดแรกของ shell script
    • เห็นด้วยเต็มที่ และหวังว่ารูปแบบแบบนี้จะถูกทำให้เป็นมาตรฐานในที่สุด
      อย่างไรก็ตาม uv น่าจะไม่อยากรันโค้ดเพื่อค้นหา dependency ดังนั้นมันคงต้องเป็น subset ที่จำกัดมากของไวยากรณ์ Python
      ความจริงที่ว่าเราต้องมีสิ่งแบบนี้ตั้งแต่แรก สะท้อนจุดอ่อนของภาษา
      คำสั่ง import เองควรจะสื่อข้อมูลทั้งหมดเกี่ยวกับ dependency ได้
  • ช่วงไม่กี่เดือนที่ผ่านมา หัวข้อนี้โผล่บน HN บ่อยมาก และตัวอย่างล่าสุดมีดังนี้
    https://news.ycombinator.com/item?id=43500124
    https://news.ycombinator.com/item?id=42463975
    ผมชอบ uv แต่ไม่ค่อยเห็นด้วยกับคำว่า self-contained ด้วยเหตุผลสองข้อ
    ข้อแรก ถ้าจะรันสคริปต์ uv ต้องถูกติดตั้งไว้ก่อนแล้ว
    จะทำเป็น shell script ให้ตรวจว่ามี uv ติดตั้งอยู่ไหม และถ้าไม่มีก็ให้ติดตั้งด้วย curlpipe ก็ได้ แต่ boilerplate จะเพิ่มขึ้นพอสมควร และวิธี curlpipe เองก็ไม่ดีด้วย
    ข้อสอง การสร้าง virtual environment อัตโนมัติไว้ที่ไหนสักแห่งใน home directory ไม่ใช่ self-contained จริง ๆ
    ถ้ารันครั้งเดียวแล้วลบสคริปต์ทิ้ง virtual environment นั้นก็ยังคงอยู่และกินพื้นที่ และผมหาหลักประกันจากเอกสารของ uv ไม่เจอว่ามันจะล้าง virtual environment ชั่วคราวแบบนี้ให้อัตโนมัติ

    • ข้อกังวลที่ว่าต้องมี uv และถ้าไม่มีก็ต้องติดตั้งเองหรือใช้ curl | sh นั้นสมเหตุสมผล
      แต่เมื่อ package manager เริ่มรวม uv ไว้ใน repository ปัญหานี้ก็จะค่อย ๆ ลดลง
      ตัวอย่างเช่น uv มีให้ใช้แล้วใน Alpine Linux และ Homebrew: https://repology.org/project/uv/versions
      อีกอย่าง inline script metadata เป็นมาตรฐานของ Python
      ถ้าในระบบไม่มี uv และไม่ได้ถูกแพ็กเกจไว้ แต่มี Python เวอร์ชันที่ตรงกับสคริปต์ ก็สามารถรันด้วย pipx ได้: https://pipx.pypa.io/stable/examples/#pipx-run-examples
      pipx ถูกแพ็กเกจไว้แพร่หลายกว่ามาก: https://repology.org/project/pipx/versions
    • เพื่อแก้ปัญหานี้ ผมลองฝืนจับ docker+uv shebang มาผูกกัน และมันก็ทำงานได้ในระดับหนึ่ง
      บนเครื่องนักพัฒนาทั่วไป Docker อาจมีอยู่บ่อยกว่า uv และต้นฉบับก็พูดถึงโปรเจกต์ของบริษัท จึงดูมีเหตุผล
      แต่ตอนนี้ยังไม่ cache อะไรเลย ทำให้ต้องดาวน์โหลดทุกครั้งที่รัน ซึ่งดูไม่ค่อยเหมาะ และน่าจะแก้ได้ด้วย volume
      ประมาณนี้: https://hugojosefson.github.io/docker-shebang/#python
    • โดยปกติถ้าจะรันโปรแกรมบนคอมพิวเตอร์ก็ต้องติดตั้งอะไรบางอย่างก่อนอยู่แล้ว ดังนั้นการติดตั้ง uv เองจึงดูไม่ได้แย่นัก
      แต่ถ้าตอนรันต้องดาวน์โหลดของที่ไม่รู้แน่ชัดจากอินเทอร์เน็ต ก็เรียกว่า self-contained ได้ยาก และ self-contained แบบสมบูรณ์จริง ๆ น่าจะใกล้เคียงกับ AppImage มากกว่า
    • เห็นด้วย 100%
      ถ้าใช้ของอย่าง py2exe ก็สามารถทำ “สคริปต์ Python” แบบ self-contained ได้
      สำหรับนักพัฒนาจะมีปัญหาเกิดขึ้นเยอะ แต่สำหรับผู้ใช้ปัญหาจะน้อยที่สุด
    • เสริมเล็กน้อยว่า ด้วย การ deduplicate แพ็กเกจ ของ uv ตราบใดที่ไม่มี dependency เฉพาะตัว virtualenv ก็จะไม่กินพื้นที่
  • Nix ก็ใช้วิธีเดียวกัน โดยบรรทัด shebang จะหน้าตาแบบนี้
    #! nix-shell -i python3 -p "python312.withPackages (pkgs: [ pkgs.boto3 pkgs.click ])"
    ถ้าทำแบบนี้ สิ่งที่ระบบต้องมีมีแค่ Nix เท่านั้น ไม่จำเป็นต้องติดตั้ง Python ไว้ด้วย

    • ถูกต้อง แต่ยังมี แพ็กเกจ PyPI จำนวนมากที่ยังไม่ได้ถูกแพ็กเกจใน nixpkgs จึงไม่ใช่แนวทางที่ใช้ได้ทั่วไปเท่า uv
    • ตามที่ต้นฉบับบอกไว้อย่างตรงเป๊ะ uv จะติดตั้ง Python ให้ทันทีเมื่อจำเป็น
    • เทคนิคเดียวกันนี้ใช้ได้กับภาษาใดก็ได้
      ตัวอย่างที่ obvious ที่สุดคือ bash ที่ระบุ dependency ทั้งหมดไว้ และเคยลองทำสคริปต์ Rust แบบไฟล์เดียวที่รันได้เร็วด้วย Nix shebang ด้วย
      https://nixos.wiki/wiki/Nix-shell_shebang
    • เงื่อนไขว่าต้องติดตั้ง Nix ไว้ เป็นข้อกำหนดที่หนักกว่าเงื่อนไขว่าต้องมี uv อยู่มาก
    • อยากรู้วิธีทำสิ่งเดียวกันด้วยคำสั่งแบบ flake อย่าง nix shell แทน nix-shell
  • เช่นเดียวกับคอมเมนต์อื่น ๆ คำกล่าวว่า “self-contained” นั้นพึ่งพาการที่ติดตั้ง uv ไว้แล้ว
    ถ้าต้องการสคริปต์ Python ที่ self-contained จริง ๆ ก็น่าลองดู คอมไพเลอร์ Nuitka
    ใช้อยู่ในโปรดักชันของบริการ gRPC โดยไม่มีปัญหา และแค่รัน nuitka --onefile run.py ก็จบ
    เพราะเป็นคอมไพเลอร์ ไบนารีที่ได้จึงอาจเร็วกว่าตัวโปรแกรม Python เดิมที่แพ็กด้วย PyInstaller ด้วย
    หน้า GitHub ของผู้เขียนมีข้อความว่า “ภารกิจชีวิตของผมคือการพยายามจนแก่ตายเพื่อสร้าง Python Compiler ที่ดีที่สุดเท่าที่เป็นไปได้”
    https://nuitka.net/
    https://github.com/kayhayen

  • ชอบแพตเทิร์นนี้มากจริง ๆ แต่น่าเสียดายที่ยังทำให้ทำงานกับ LSP ได้อย่างถูกต้องไม่ได้
    ใช้ pyright ใน Helix อยู่ และแม้จะเปิดเอดิเตอร์ด้วย uv run hx script.py ก็ยังไม่ได้
    จะทำแบบ uv run --with whatever-it-is-i-need hx script.py ก็ได้ แต่จะเริ่มมีความซ้ำซ้อนมากขึ้นเรื่อย ๆ

    • ใช้สคริปต์ uve หน้าตาไม่สวยที่ทำเองอยู่
      $ cat ~/.local/bin/uve
      #!/bin/bash
      temp=$(mktemp)
      uv export --script $1 --no-hashes > $temp
      uv run --with-requirements $temp vim $1
      unlink $temp
      หวังว่าเอดิเตอร์จะรองรับ uv python find --script ในเร็ว ๆ นี้
  • ดูมีประโยชน์ทีเดียว
    สงสัยว่า uv จะเป็นตัวเลือกที่ปลอดภัยกว่าสำหรับการเผยแพร่โปรเจกต์ที่ใช้ Python ในระยะยาวหรือไม่
    นึกถึง rug pull ตอนที่เคยใช้ Anaconda จัดการ dependency เมื่อราว 5 ปีก่อน แล้วต่อมามีกฎเปลี่ยนไป ทำให้ลูกค้าองค์กรที่มีพนักงานเกิน 200 คนไม่สามารถใช้ Anaconda ฟรีได้อีกต่อไป และต้องจ่ายไลเซนส์เชิงพาณิชย์

    • uv ใช้ไลเซนส์ MIT หรือ Apache-2.0
      อาจหยุดพัฒนา หรือย้ายงานในอนาคตไปยัง fork ที่ใช้ไลเซนส์อื่นได้ แต่ไม่สามารถเปลี่ยนไลเซนส์ย้อนหลังได้ ดังนั้นสิ่งที่มีอยู่ตอนนี้จึงรับประกันได้ว่าเป็นโอเพนซอร์ส
      ถ้ากังวลจริง ๆ ก็ fork แล้วคอย sync ต่อไปได้
      จริง ๆ แล้วเรื่องนี้ใช้ได้กับโปรเจกต์ OSS อื่น ๆ แทบเหมือนกัน จึงไม่คิดว่าน่ากังวลมาก
      เท่าที่รู้ conda ไม่เคยเป็นโอเพนซอร์ส และแจกจ่ายเป็นไบนารีมาตลอด
      https://github.com/astral-sh/uv?tab=readme-ov-file#license
    • มองว่า rug pull ของ Anaconda น่าจะเกิดที่ฝั่ง repository
      แพ็กเกจของ conda-forge ยังใช้ได้ฟรีอยู่
      uv ใช้ PyPI เฉย ๆ ดังนั้นถ้ามีปัญหา ก็เปลี่ยนจาก uv ไปเป็น pip หรือ Poetry ฯลฯ ได้ และยังดึงแพ็กเกจจากที่เดิมต่อไป
    • เข้าใจว่าถ้าโปรเจกต์มีข้อตกลงไลเซนส์ผู้มีส่วนร่วม (CLA) และบังคับให้โอนลิขสิทธิ์ของ contribution ให้เจ้าของโปรเจกต์ ก็สามารถ relicense ได้
      เจ้าของแบบนั้นสุดท้ายก็จะถูกคนรวยที่แย่ที่สุดเท่าที่นึกออกซื้อไป
      ลองไล่ดูคู่มือการมีส่วนร่วมและ issue ของ uv แล้ว แต่ไม่เห็น CLA ส่วน PyTorch มี CLA อยู่บนสุดของคู่มือการมีส่วนร่วม
      ถึงอย่างนั้นก็ควรมี fork จากชุมชนของ Anaconda เวอร์ชัน FOSS ล่าสุด
      Redis เคยเกิดแบบนั้น และ Redis ก็ใช้ CLA: https://github.com/redis/redis/blob/unstable/CONTRIBUTING.md#redis-software-grant-and-contributor-license-agreement
      ไม่ควรเซ็น CLA เด็ดขาด
      คิดว่าไป contribute ให้เฉพาะโปรเจกต์ copyleft ยังดีกว่า
      เราได้รับค่าจ้างมากเกินไปสำหรับการทำงานฟรี
  • สำหรับยูทิลิตีเล็ก ๆ ดูเหมือนเป็น ทางเลือกด้านแพ็กเกจจิง ที่ดีแทนการทำเป็นคอนเทนเนอร์
    คราวนี้ต้องไปโน้มน้าวเพื่อนร่วมงานทุกคนให้ติดตั้ง uv

    • uv เร็วอย่างน่าทึ่ง น่าจะช่วยได้
    • สำหรับเรา อุปสรรคคือ สแกนเนอร์ช่องโหว่ SCA ยังทำงานกับ uv ไม่ได้
  • ดูคล้ายกับ bundler/inline ของฝั่ง Ruby
    ดีใจที่ Python ก็มีอะไรคล้าย ๆ กัน และในทางปฏิบัติก็สะดวกมากจริง ๆ
    https://bundler.io/guides/bundler_in_a_single_file_ruby_script.html

  • อยากรู้ว่ามีใครทำให้สิ่งนี้ทำงานบน Windows ได้บ้างไหม
    อยากใช้ทริกนี้กับเครื่องมือสำหรับม็อดเกมที่กำลังทำอยู่ แต่ทำให้ทริก shebang ทำงานไม่ได้

    • ใช้บ่อยทั้งบน Windows และ Linux โดยทำแบบนี้
      $> uv init --script .py
      $> uv add --script .py ...
      $> uv add --script .py --dev ...
      $> uv run .py
      หวังว่าจะช่วยได้
      ที่มา: https://docs.astral.sh/uv/guides/scripts/
    • ตัวติดตั้ง CPython ปกติสำหรับ Windows จะติดตั้ง py launcher และเชื่อมโยงกับไฟล์ .py
      py launcher รองรับบรรทัด shebang
      บล็อกโพสต์ในหัวข้อเดียวกันที่เพิ่งลงเมื่อไม่กี่วันก่อนก็พูดถึงเรื่องนี้ และตามนั้นต้องตัด -S ออก: https://thisdavej.com/share-python-scripts-like-a-pro-uv-and-pep-723-for-easy-deployment/
      https://news.ycombinator.com/item?id=43500124
      ยังไม่ได้ลองเอง แต่เปลี่ยน file association แทน เพื่อให้ไฟล์ .py ทั้งหมดเปิดด้วย uv run เป็นค่าเริ่มต้น
      https://docs.python.org/3/using/windows.html#python-launcher-for-windows
      https://peps.python.org/pep-0397/
    • ไม่ได้พัฒนา Windows มาหลายปีแล้ว แต่เท่าที่จำได้ Windows ไม่รองรับ shebang
    • ใช้อะไรอย่าง PyInstaller ก็ได้
      https://pyinstaller.org
    • ไม่รู้ว่าจะช่วยได้ไหม แต่มีคนโพสต์วิธีทำสิ่งคล้ายกันใน Racket ด้วย PowerScript
      https://onor.io/2025/01/more-scripting-with-racket.html
  • กรณีใช้งานนี้ทำให้ชอบ uv ขึ้นมา แต่รู้สึกว่าการที่ PEP อย่างเป็นทางการและมีประโยชน์มากไม่ถูกรองรับในเครื่องมือ Python อย่างเป็นทางการนั้นขัดกับ Zen of Python
    ในมุมมองของผม นี่เป็นครั้งแรกที่ Python ไม่ได้ “batteries included”
    ตอนนี้ในระบบก็มีตัวจัดการ dependency ของ Python สองตัวแล้ว
    รู้ว่ามีเรื่องให้พูดได้เยอะเกี่ยวกับการจัดการ dependency ของ Python แต่ตลอดหลายปีที่ผ่านมา ถ้าโปรเจกต์มีแค่ requirements.txt ก็อยู่ได้ด้วย pip+venv พื้นฐาน

    • ในสเปกการ build ของ Python ก็เคยมีแนวทางแบบนั้นอยู่เหมือนกัน
      จำได้ว่า pyproject.toml ออกมาก่อนไลบรารี tomllib
      ดังนั้นอยู่หลายเวอร์ชันที่ต้องระบุโมดูลด้วยภาษาที่ Python อ่านเองเป็นค่าเริ่มต้นไม่ได้
      เป็นสถานการณ์ที่แย่กว่าการมีวิธีมาตรฐานสำหรับใส่ metadata ที่ไม่ได้ใช้งานเสียอีก
      ก็เพราะมันเป็น metadata ตั้งแต่แรกนั่นแหละ ไม่อย่างนั้นก็คงเป็นไวยากรณ์ Python ไปแล้ว