2 điểm bởi GN⁺ 2025-04-11 | 3 bình luận | Chia sẻ qua WhatsApp
  • PEP 750 giới thiệu một literal chuỗi mới trong Python là chuỗi mẫu (t"...")
  • Đây là dạng khái quát hóa của f-string, tạo ra kiểu Template để có thể xử lý trước việc kết hợp chuỗi và các giá trị nội suy
  • Có thể hữu ích cho template web, kiểm tra bảo mật, DSL (Domain-Specific Language), v.v.

Quan hệ với các PEP khác

  • f-string được giới thiệu trong PEP 498, và cú pháp được mở rộng trong PEP 701
  • PEP 501 từng đề xuất chuỗi mẫu tổng quát (i-string) nhưng đã bị hoãn
  • PEP 750 hiện tại là phiên bản đơn giản hóa và khái quát hóa của PEP 501, phát triển dựa trên ý tưởng trước đó

Động cơ và nhu cầu

  • f-string tuy đơn giản nhưng không thể xử lý trước các giá trị nội suy, nên có thể phát sinh vấn đề bảo mật
  • Có lo ngại về các lỗ hổng như SQL injection, tấn công XSS, v.v.
  • Khi dùng chuỗi mẫu, có thể xử lý trước các giá trị nội suy để sử dụng an toàn hơn

Ví dụ:

  • evil = "<script>alert('evil')</script>"
  • template = t"<p>{evil}</p>"
  • assert html(template) == "<p>&lt;script&gt;alert('evil')&lt;/script&gt;</p>"

Đặc tả của chuỗi mẫu

Literal chuỗi mẫu

  • Được định nghĩa bằng tiền tố t hoặc T
  • Được đánh giá thành kiểu string.templatelib.Template
  • Hỗ trợ cú pháp tương tự f-string và cũng có thể lồng nhau
  • Có thể kết hợp với tiền tố r (rt, tr)
  • Không thể kết hợp với tiền tố u, b
  • Không thể dùng lẫn f-string và chuỗi mẫu

Kiểu Template

  • Là kiểu bất biến và có các thuộc tính sau:
    • strings: tuple các mảnh chuỗi
    • interpolations: tuple các đối tượng giá trị nội suy
    • values: tuple giá trị của các phần nội suy
    • __iter__(): iterator trả về lần lượt chuỗi và giá trị nội suy theo thứ tự

Kiểu Interpolation

  • value: kết quả đã được đánh giá
  • expression: chuỗi biểu thức nội suy gốc
  • conversion: cách chuyển đổi (r, s, a hoặc None)
  • format_spec: chuỗi định dạng

Ví dụ:

  • name = "World"
  • template = t"Hello {name!r}"
  • assert template.interpolations[0].conversion == "r"

Bộ chỉ định debug =

  • t"{value=}" được diễn giải thành t"value={value!r}"
  • Khoảng trắng cũng được giữ nguyên (t"{value = }""value = {value!r}")

Nối chuỗi mẫu

  • Có thể kết hợp Template với str, hoặc Template với Template bằng toán tử +
  • Kết quả sau khi nối luôn là kiểu Template
  • Cũng hỗ trợ nối chuỗi ngầm định (t"Hello " t"World")

Cách xử lý chuỗi mẫu

Ví dụ: hàm xử lý chữ hoa/chữ thường

  • def lower_upper(template):
    • parts = []
    • for s in template:
      • if isinstance(s, str): parts.append(s.lower())
      • else: parts.append(str(s.value).upper())
    • return "".join(parts)

Ví dụ: triển khai xử lý giống hệt f-string

  • Có thể dùng hàm f() để tạo ra kết quả giống hệt f-string

Ví dụ: logging có cấu trúc

  • Dùng chuỗi mẫu có thể xuất đồng thời thông điệp log và các giá trị có cấu trúc
  • Có thể triển khai bằng StructuredMessage hoặc lớp con của logging.Formatter

Ví dụ: xử lý template HTML

  • Hàm html() sẽ escape nội dung hoặc xử lý như thuộc tính một cách phù hợp tùy theo vị trí nội suy
  • Cũng hỗ trợ template lồng nhau

Các mẫu sử dụng nâng cao

  • Khuyến nghị dùng structural pattern matching (câu lệnh match)
  • Chuỗi tĩnh có thể dùng làm khóa cache, hỗ trợ memoization hiệu quả
  • Có thể parse và xử lý thành biểu diễn trung gian như AST
  • Có thể dùng lambda, await để đánh giá Lazy hoặc Async

Quan hệ giữa chuỗi mẫu và các chuỗi định dạng hiện có

  • Có thể định nghĩa hàm template theo cách tương tự .format() hiện có
  • Cũng có thể có from_format() để parse chuỗi bên ngoài và chuyển thành Template

Tính tương thích, bảo mật, học tập

  • Trên các phiên bản Python cũ có thể phát sinh lỗi cú pháp
  • Về mặt bảo mật, xử lý template giúp tăng độ an toàn
  • Cú pháp tương tự f-string nên dễ học

Vì sao cần cách tiếp cận template mới?

  • Các template hiện có như Jinja chủ yếu dành cho tùy biến người dùng hoặc cho nhà thiết kế
  • Cần hỗ trợ ở cấp độ ngôn ngữ Python để lập trình viên có thể trực tiếp xử lý template
  • Có thể tận dụng các ưu điểm như tính biểu đạt và kiểm tra kiểu

Tóm tắt các mẫu ví dụ

  • Structural pattern matching và khớp các thuộc tính con
  • Tái sử dụng template như một hàm
  • Hỗ trợ template lồng nhau
  • Hỗ trợ đánh giá Lazy/Async
  • Tách phần tĩnh/động để tối ưu cache

Các cân nhắc thiết kế khác

  • Template không được chuyển thành chuỗi, và __str__() không được triển khai
  • Các lớp liên quan được cung cấp trong mô-đun string.templatelib
  • Template, Interpolation được so sánh dựa trên tính đồng nhất đối tượng
  • Không hỗ trợ toán tử == hoặc <

Bản triển khai tham chiếu và ví dụ

Các ý tưởng bị bác bỏ

  • Dùng tiền tố tùy ý (my_tag"...")
  • Đánh giá trì hoãn cho mọi biểu thức nội suy
  • Triển khai bằng protocol
  • Ghi đè __eq__, __hash__
  • Khôi phục hoàn toàn chuỗi gốc
  • Thêm kiểu Decoded
  • Hỗ trợ chuỗi mẫu nhị phân
  • Tính năng chỉ định loại định dạng ("html", "sql", v.v.)
  • Hạn chế nối chuỗi
  • Cho phép bộ chuyển đổi tùy ý (!x)

3 bình luận

 
carnoxen 2025-04-11

Cách định dạng khiến mình hài lòng nhất có lẽ chỉ có JavaScript và Python thôi. Các ngôn ngữ khác thì hơi...

 
kandk 2025-04-11

Sẽ có một cách rõ ràng — và tốt nhất là chỉ nên có một cách rõ ràng duy nhất — để làm điều đó. (There should be one-- and preferably only one --obvious way to do it.)

 
GN⁺ 2025-04-11
Ý kiến trên Hacker News
  • Thật thú vị khi nhiều ngôn ngữ xử lý việc định dạng chuỗi theo những cách khác nhau

    • Java đang cố gắng bổ sung f/t-strings, nhưng gặp khó khăn vì chủ nghĩa hoàn hảo muốn giải quyết mọi vấn đề
    • Có vẻ như các nhà phát triển Go hầu như không cân nhắc vấn đề này và đã bỏ qua nó
    • Python chọn cách tiếp cận cân bằng, thảo luận về các phương pháp định dạng chuỗi mới và sử dụng cách triển khai phù hợp sau khi lựa chọn
    • Khó mà không đồng tình với cách tiếp cận của Python, và đang nhận được giá trị từ .format(), f-strings và t-strings
  • Nick Humrich là một trong những tác giả đã viết lại PEP 501 để giới thiệu t-strings, và rất vui mừng khi PEP này được chấp nhận

    • Đã bắt đầu làm việc với PEP 501 từ 4 năm trước
  • Không chắc liệu tính năng ở cấp độ ngôn ngữ có thực sự có giá trị hay không

    • Có thể đạt được kết quả tương tự bằng một hàm trả về f-string
    • Nếu muốn an toàn trước tiêm nhiễm, có thể dùng kiểu thẻ và hàm làm sạch trả về chuỗi
    • Dù ngắn gọn, việc dùng một ký tự để phân biệt thực thi ngay và thực thi trì hoãn có thể khiến người không quen Python thấy khó đọc
  • Thích f-strings, nhưng có vấn đề là không thể trì hoãn việc đánh giá

    • Có những lúc phải dùng str.format nên khá bất tiện
  • Với tư cách là người bảo trì lit-html, thấy điểm tương đồng với tagged template literals của JavaScript rất thú vị

    • Cách lớp Template của Python tách hàm gắn thẻ và đối số ra khỏi nhau là điều khá độc đáo so với JavaScript
    • Trong cấu trúc template lồng nhau, có thể không cần hàm html()
  • Kỳ vọng rằng những lợi ích của tagged template literals trong JavaScript, như tự động escape HTML hay tham số hóa SQL, cũng sẽ áp dụng được cho Python

  • Có ý kiến cho rằng Python đang dần biến thành PHP

    • f-strings và t-strings làm tăng độ phức tạp của ngôn ngữ
    • Nghĩ rằng string.format là tối ưu nhất, và % cũng có thể chấp nhận được vì đã được dùng từ lâu
    • Mong đội ngũ ngôn ngữ tập trung vào những thứ quan trọng hơn
  • Có sự bất mãn với việc liên tục thêm cái mới vào ngôn ngữ

    • Cảm giác như ngôn ngữ đang được thiết kế bởi một ủy ban
  • Có ý kiến cho rằng PEP này tương tự P1819 của C++

  • Có ý kiến cho rằng phần mã trong PEP quá dài dòng

    • Python có vẻ đang diễn đạt quá nhiều thứ không cần thiết thay vì là mã giả có thể thực thi
    • So với mã Ruby, mã Python dài dòng hơn