1 คะแนน โดย GN⁺ 2024-08-23 | 1 ความคิดเห็น | แชร์ทาง WhatsApp

พรีโปรเซสเซอร์ของ Python

  • ข้ออ้างที่ว่า Python ไม่มีพรีโปรเซสเซอร์นั้นไม่เป็นความจริง
  • Python มีพรีโปรเซสเซอร์ที่ทรงพลังมาก

การเข้ารหัสซอร์สโค้ดของ Python

  • ด้วย PEP-0263 จึงสามารถกำหนดการเข้ารหัสของซอร์สโค้ดได้
  • สามารถตั้งค่าการเข้ารหัสได้โดยเพิ่ม magic comment ไว้ในสองบรรทัดแรก
  • ตัวอย่าง: # coding=utf8, # -*- coding: utf8 -*-, # vim: set fileencoding=utf8 :

ไฟล์กำหนดค่า path (.pth)

  • เมื่อ Python interpreter เริ่มทำงานโดยไม่มีออปชัน -S จะโหลดแพ็กเกจ site โดยอัตโนมัติ
  • สามารถเพิ่มไฟล์ .pth ในโฟลเดอร์ site-packages เพื่อขยายเส้นทางค้นหาโมดูลได้
  • บรรทัดที่ขึ้นต้นด้วย import ในไฟล์ .pth จะถูกนำไปทำงาน
  • ทำให้สามารถรันโค้ดใดก็ได้ระหว่างการเริ่มต้นทำงานของ Python interpreter

การกำหนด codec แบบกำหนดเอง

  • มีความต้องการสองอย่างที่ Python interpreter ต้องพอใจ:
    • ฟังก์ชัน decode(data: bytes) -> tuple[str, int]
    • คลาส incremental decoder
  • ใช้ codecs.utf_8_decode เพื่อทำการถอดรหัสจริง จากนั้นส่งสตริงผลลัพธ์ไปยังพรีโปรเซสเซอร์
  • ควรจับ exception แล้วพิมพ์ออกมาก่อนโยนซ้ำอีกครั้ง

การจัดเตรียม incremental decoder

  • สืบทอดจาก codecs.BufferedIncrementalDecoder เพื่อทำ incremental decoder
  • รวบรวมข้อมูลไว้ในบัฟเฟอร์ แล้วพรีโปรเซสทั้งไฟล์เมื่อมีการเรียกถอดรหัสครั้งสุดท้าย

การขยาย Python

  • การขยาย Python โดยใช้ standard library ของ Python นั้นค่อนข้างง่าย
  • สามารถใช้โมดูล tokenize เพื่อแก้ไข token stream ของไฟล์ หรือใช้โมดูล ast เพื่อแก้ไข abstract syntax tree ได้
การเพิ่มและลดค่าแบบ unary
  • Python ไม่มีตัวดำเนินการเพิ่มและลดค่าแบบ unary
  • x++, x-- ใช้ไม่ได้
  • ++x, --x ใช้ได้ แต่มีความหมายต่างออกไป
  • สามารถแปลงนิพจน์เพิ่มและลดค่าแบบ unary ให้เป็นนิพจน์ Python ได้
ตัวอย่าง
  • ไฟล์อินพุต incdec.py:
    # coding: magic.incdec
    i = 6
    assert i-- == 6
    assert i == 5
    assert ++i == 6
    assert --i == 5
    assert i++ == 5
    assert i == 6
    assert (++i, 'i++') == (7, 'i++')
    print("PASSED")
    
  • ไฟล์ที่แปลงแล้ว:
    i = 6
    assert ((i, i := i - 1)[0]) == 6
    assert i == 5
    assert ((i, i := i + 1)[1]) == 6
    assert ((i, i := i - 1)[1]) == 5
    assert ((i, i := i + 1)[0]) == 5
    assert i == 6
    assert (((i, i := i + 1)[1]), 'i++') == (7, 'i++')
    print("PASSED")
    

Python ที่ใช้วงเล็บปีกกา (Bython)

  • สามารถใช้วงเล็บปีกกาเพื่อกำหนดขอบเขตแทนการเยื้องของ Python ได้
  • ใช้ tokenize.generate_tokens เพื่อแก้ไข token stream
ตัวอย่าง
  • ไฟล์อินพุต test.by:
    # coding: magic.braces
    def print_message(num_of_times) {
      for i in range(num_of_times) {
        print("braces ftw")
      }
      print({'x': 3})
    }
    x = {
      'foo': 42,
      'bar': 5
    }
    if __name__ == "__main__" {
      print_message(2)
      print({k: v for k, v in x.items()})
    }
    
  • ไฟล์ที่แปลงแล้ว:
    def print_message(num_of_times):
      for i in range(num_of_times):
        print("braces ftw")
      print({'x': 3})
    x = {
      'foo': 42,
      'bar': 5
    }
    if __name__ == "__main__":
      print_message(2)
      print({k: v for k, v in x.items()})
    

การตีความภาษาอื่น

  • สามารถทำให้ Python interpreter ตีความภาษาอื่นได้
  • เช่น C, C++, TOML
ตัวอย่าง
  • ไฟล์ C++ test.cpp:
    #define CODEC "coding:magic.cpp"
    #include <cstdio>
    int main() {
      puts("Hello World");
    }
    
  • ไฟล์ที่แปลงแล้ว:
    import cppyy
    cppyy.cppdef(r"""
    #define CODEC "coding:magic.cpp"
    #include <cstdio>
    int main() {
      puts("Hello World");
    }
    """)
    from cppyy.gbl import main
    if __name__ == "__main__":
      main()
    

การตรวจสอบความถูกต้องของข้อมูล

  • สามารถตรวจสอบข้อมูลในรูปแบบ TOML ด้วย JSON Schema ได้
  • ใช้ jsonschema เพื่อทำการตรวจสอบจริง
ตัวอย่าง
  • ไฟล์สคีมา schema.json:
    {
      "type": "object",
      "properties": {
        "name": {"type": "string"},
        "age": {"type": "number"},
        "scores": {
          "type": "array",
          "items": {"type": "number"}
        },
        "address": {"$ref": "#/$defs/address"}
      },
      "required": ["name"],
      "$defs": {
        "address": {
          "type": "object",
          "properties": {
            "street": {"type": "string"},
            "postcode": {"type": "number"}
          },
          "required": ["street"]
        }
      }
    }
    
  • ไฟล์ข้อมูลที่ถูกต้อง data_valid.toml:
    # coding: magic.toml
    name = "John Doe"
    age = 42
    scores = [40, 20, 80, 90]
    [address]
    street = "Grove St. 4"
    postcode = 19201
    
  • ไฟล์ข้อมูลที่ไม่ถูกต้อง data_invalid.toml:
    # coding: magic.toml
    name = "John Doe"
    age = 42
    scores = [40, "20", 80, 90]
    [address]
    street = "Grove St. 4"
    postcode = 19201
    

บทสรุป

  • สามารถใช้ codec แบบกำหนดเองและไฟล์กำหนดค่า path เพื่อเปลี่ยนพฤติกรรมของ Python interpreter ได้อย่างมาก
  • ตัวอย่างได้แก่ pythonql, future-typing, future-fstrings, future-annotations
  • สามารถใช้ magic_codec เพื่อทดลองพรีโปรเซสเซอร์ของตนเองได้อย่างง่ายดาย

สรุปโดย GN⁺

  • สามารถใช้พรีโปรเซสเซอร์ของ Python เพื่อทำส่วนขยายภาษาหลากหลายแบบและตรวจสอบความถูกต้องของข้อมูลได้
  • อธิบายวิธีเปลี่ยนพฤติกรรมของ Python interpreter ผ่าน codec แบบกำหนดเอง
  • บทความนี้มอบเครื่องมือและเทคนิคที่เป็นประโยชน์ให้กับนักพัฒนา Python
  • โปรเจ็กต์ที่มีความสามารถคล้ายกัน ได้แก่ pythonql, future-typing เป็นต้น

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

 
GN⁺ 2024-08-23
ความคิดเห็นบน Hacker News
  • ข้อความแสดงข้อผิดพลาดทางไวยากรณ์ของ from __future__ import braces ถูกฮาร์ดโค้ดไว้ใน cpython มาตั้งแต่ปี 2001

    • เขียนโดย Jeremy Hylton และปัจจุบันทำงานที่ Google ในตำแหน่งวิศวกรอาวุโสที่ดูแลคุณภาพการค้นหา AI
    • น่าทึ่งที่เส้นทางอาชีพตลอด 24 ปีของคนคนหนึ่งเริ่มต้นจากการจารึกการห้ามใช้ไวยากรณ์บางอย่าง แล้วพัฒนามาสู่การทำงานกับระบบค้นหาที่ไม่ต้องการไวยากรณ์เฉพาะทาง
  • เคยคิดถึงวิธีไล่คนออกแบบสร้างสรรค์ด้วย import-hooks แต่ก็น่าเสียดายที่ codec regex กันไม่ให้ใช้สิ่งอย่าง μtf8

    • เลยต้องใช้วิธีนำ import hooks, preprocessors และ sys.settrace มาใช้ monkey patch ทุกฟังก์ชันให้กลายเป็นฟังก์ชันที่ถูกเรียกก่อนหน้า และสลับ stdout กับ stderr ทุก ๆ 17 นาที
  • มีเหตุผลที่ Python ไม่เปิดเผย preprocessor hook และคิดว่าผู้ใหญ่ที่มีเหตุผลควรหลีกเลี่ยงมัน

    • แต่ก็อยากไล่ตามความสนุกโดยไม่เกี่ยวกับว่าผู้ใหญ่ที่มีเหตุผลจะคิดอย่างไร
  • preprocessor สะดวกและมีประโยชน์กว่า

    • เคยแฮ็กด้วยวิธีเขียนโค้ดใหม่ด้วยโมดูล ast แล้วรันด้วย exec จากนั้นแทรก exit()
    • ก่อนที่ dict ทั้งหมดจะถูกจัดเรียงได้ เคยใช้ความสามารถในการเขียน ast ใหม่ให้เกิดประโยชน์
  • ชอบความยืดหยุ่นของ Python

    • งานที่ต้องสาปที่สุดคือการดัดแปลงสตริงแบบ in-place และได้ใช้ mmap อย่างเกินเลยจนทำให้สคริปต์แปลงตัวเอง
    • ตอนนี้อยากเขียน Lisp interpreter แล้ว
  • กรณีใช้งานที่ดีที่สุดของ pyxl ได้แรงบันดาลใจจาก jsx

    • สามารถเขียนโค้ด HTML ได้ด้วย # coding: pyxl
  • สงสัยว่าการเปลี่ยนผ่านจาก Python 2 ไป 3 จะจัดการได้ดีกว่านี้หรือไม่

    • # coding: six.python2 สามารถทำให้โค้ด Python2 ใช้ได้อย่างถูกต้องใน Python3 และ # coding: six.python3 ก็สามารถปรับโค้ด Python3 ให้รันได้ใน Python2
  • ดีใจที่มีคนชอบไอเดียนี้ และจะมีเนื้อหาเพิ่มเติมตามมาเร็ว ๆ นี้

  • รู้สึกทึ่งกับไอเดียใหม่อย่างแท้จริงหลังจากไม่ได้เจอมานาน

  • ถ้าต้องการการสร้างโค้ดแบบ inline ใน Python สามารถใช้ cog ของ Ned Batchelder ได้

  • สงสัยว่า dependency ที่ถูกนำเข้ามาด้วยกลยุทธ์ coding hook นี้จะถูกตรวจจับโดย pip freeze หรือ uv หรือไม่

    • ถ้าไม่ ก็ขอให้สนุกกับมันได้เลย การเขียนไลบรารีใหม่อาจง่ายกว่า (ถ้ามีกับดักแบบนี้ ก็น่าจะมีกับดักอื่นอีก)