1 điểm bởi GN⁺ 2024-08-23 | 1 bình luận | Chia sẻ qua WhatsApp

Bộ tiền xử lý của Python

  • Khẳng định rằng Python không có bộ tiền xử lý là không đúng
  • Python có một bộ tiền xử lý rất mạnh

Mã hóa mã nguồn Python

  • Nhờ PEP-0263, có thể định nghĩa mã hóa cho mã nguồn
  • Có thể thiết lập mã hóa bằng cách thêm magic comment vào hai dòng đầu tiên
  • Ví dụ: # coding=utf8, # -*- coding: utf8 -*-, # vim: set fileencoding=utf8 :

Tệp cấu hình đường dẫn (.pth)

  • Khi trình thông dịch Python khởi động mà không có tùy chọn -S, nó sẽ tự động nạp gói site
  • Có thể mở rộng đường dẫn tìm kiếm mô-đun bằng cách thêm tệp .pth vào thư mục site-packages
  • Các dòng trong tệp .pth bắt đầu bằng import sẽ được thực thi
  • Nhờ đó có thể chạy mã tùy ý khi trình thông dịch Python khởi tạo

Định nghĩa codec tùy chỉnh

  • Trình thông dịch Python cần thỏa mãn hai yêu cầu:
    • hàm decode(data: bytes) -> tuple[str, int]
    • một lớp bộ giải mã tăng dần
  • Dùng codecs.utf_8_decode để thực hiện việc giải mã thực tế, rồi chuyển chuỗi kết quả cho bộ tiền xử lý
  • Nên bắt ngoại lệ, in ra rồi phát sinh lại

Cung cấp bộ giải mã tăng dần

  • Kế thừa codecs.BufferedIncrementalDecoder để triển khai bộ giải mã tăng dần
  • Thu thập dữ liệu vào bộ đệm và tiền xử lý toàn bộ tệp ở lần gọi giải mã cuối cùng

Mở rộng Python

  • Việc mở rộng Python bằng thư viện chuẩn của Python là tương đối dễ
  • Có thể dùng mô-đun tokenize để sửa luồng token của tệp hoặc dùng mô-đun ast để sửa cây cú pháp trừu tượng
Tăng và giảm đơn ngôi
  • Python không có toán tử tăng và giảm đơn ngôi
  • x++, x-- là không hợp lệ
  • ++x, --x là hợp lệ nhưng mang ý nghĩa khác
  • Có thể chuyển các biểu thức tăng và giảm đơn ngôi sang biểu thức Python
Ví dụ
  • Tệp đầu vào 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")
    
  • Tệp sau khi chuyển đổi:
    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 dùng dấu ngoặc nhọn (Bython)

  • Có thể dùng dấu ngoặc nhọn để chỉ định phạm vi thay cho thụt lề của Python
  • Dùng tokenize.generate_tokens để sửa luồng token
Ví dụ
  • Tệp đầu vào 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()})
    }
    
  • Tệp sau khi chuyển đổi:
    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()})
    

Diễn giải ngôn ngữ khác

  • Có thể khiến trình thông dịch Python diễn giải các ngôn ngữ khác
  • Ví dụ: C, C++, TOML
Ví dụ
  • Tệp C++ test.cpp:
    #define CODEC "coding:magic.cpp"
    #include <cstdio>
    int main() {
      puts("Hello World");
    }
    
  • Tệp sau khi chuyển đổi:
    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()
    

Kiểm chứng dữ liệu

  • Có thể kiểm chứng dữ liệu định dạng TOML bằng JSON Schema
  • Dùng jsonschema để thực hiện việc kiểm chứng thực tế
Ví dụ
  • Tệp schema 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"]
        }
      }
    }
    
  • Tệp dữ liệu hợp lệ data_valid.toml:
    # coding: magic.toml
    name = "John Doe"
    age = 42
    scores = [40, 20, 80, 90]
    [address]
    street = "Grove St. 4"
    postcode = 19201
    
  • Tệp dữ liệu không hợp lệ data_invalid.toml:
    # coding: magic.toml
    name = "John Doe"
    age = 42
    scores = [40, "20", 80, 90]
    [address]
    street = "Grove St. 4"
    postcode = 19201
    

Kết luận

  • Có thể thay đổi đáng kể hành vi của trình thông dịch Python bằng codec tùy chỉnh và tệp cấu hình đường dẫn
  • Các ví dụ gồm pythonql, future-typing, future-fstrings, future-annotations
  • Có thể dễ dàng thử nghiệm bộ tiền xử lý của riêng mình bằng magic_codec

Tóm tắt của GN⁺

  • Có thể tận dụng bộ tiền xử lý của Python để thực hiện nhiều kiểu mở rộng ngôn ngữ và kiểm chứng dữ liệu khác nhau
  • Bài viết giải thích cách thay đổi hành vi của trình thông dịch Python thông qua codec tùy chỉnh
  • Bài viết này cung cấp các công cụ và kỹ thuật hữu ích cho lập trình viên Python
  • Các dự án có tính năng tương tự gồm pythonql, future-typing v.v.

1 bình luận

 
GN⁺ 2024-08-23
Bình luận trên Hacker News
  • Thông báo lỗi cú pháp của from __future__ import braces đã được hard-code trong cpython từ năm 2001

    • Do Jeremy Hylton viết, người hiện là kỹ sư cấp cao phụ trách chất lượng tìm kiếm AI tại Google
    • Thật đáng ngạc nhiên khi trong 24 năm, sự nghiệp của một người đã phát triển từ việc kỷ niệm lệnh cấm một cú pháp cụ thể sang làm việc trên hệ thống tìm kiếm không cần cú pháp chuyên dụng
  • Tôi đã nghĩ về một cách sa thải đầy sáng tạo bằng import-hooks, nhưng tiếc là regex của codec chặn việc dùng thứ như μtf8

    • Đành phải dùng import hooks, preprocessors và sys.settrace để monkey-patch mọi hàm thành hàm đã được gọi trước đó, rồi hoán đổi stdout và stderr mỗi 17 phút
  • Có lý do vì sao Python không phơi bày preprocessor hook, và tôi nghĩ người trưởng thành lý trí nên tránh nó

    • Nhưng bất kể người trưởng thành lý trí nghĩ gì, tôi vẫn muốn theo đuổi sự vui vẻ
  • Preprocessor tiện và hữu ích hơn

    • Tôi từng hack theo cách viết lại mã bằng mô-đun ast, chạy nó bằng exec rồi chèn exit()
    • Trước khi mọi dict đều được sắp thứ tự, tôi đã dùng tính năng viết lại ast một cách hữu ích
  • Tôi yêu sự linh hoạt của Python

    • Việc bị nguyền rủa nhất tôi từng làm là biến đổi chuỗi ngay tại chỗ, lạm dụng mmap để làm cho script tự biến đổi chính nó
    • Giờ tôi muốn viết một trình thông dịch Lisp
  • Trường hợp dùng hay nhất với pyxl được lấy cảm hứng từ jsx

    • Có thể viết mã HTML bằng # coding: pyxl
  • Tự hỏi liệu quá trình chuyển từ Python 2 sang 3 có thể được xử lý tốt hơn không

    • # coding: six.python2 có thể khiến mã Python2 hợp lệ trên Python3, còn # coding: six.python3 có thể điều chỉnh mã Python3 để chạy được trên Python2
  • Tôi rất vui vì mọi người thích ý tưởng này, sắp sẽ có thêm nội dung

  • Lâu rồi tôi mới thấy ngạc nhiên trước một ý tưởng hoàn toàn mới

  • Nếu muốn tạo mã inline trong Python, bạn có thể dùng cog của Ned Batchelder

  • Tôi tò mò liệu các dependency được đưa vào bằng chiến lược coding hook này có bị pip freeze hay uv phát hiện không

    • Nếu không thì cứ vui vẻ mà dùng. Có khi viết lại thư viện còn dễ hơn (nếu có loại cạm bẫy này thì rất có thể còn những cạm bẫy khác)