PEP 750 – Phê duyệt chuỗi mẫu (t-strings)
(peps.python.org)- 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><script>alert('evil')</script></p>"
Đặc tả của chuỗi mẫu
Literal chuỗi mẫu
- Được định nghĩa bằng tiền tố
thoặcT - Đượ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ỗiinterpolations: tuple các đối tượng giá trị nội suyvalues: 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ốcconversion: cách chuyển đổi (r,s,ahoặ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ànht"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
Templatevớistr, hoặcTemplatevớiTemplatebằ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
StructuredMessagehoặc lớp con củalogging.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ànhTemplate
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ụ
- Triển khai CPython
- Có cung cấp ví dụ và test
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
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...
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.)
Ý 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
.format(), f-strings và t-stringsNick 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
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
Thích f-strings, nhưng có vấn đề là không thể trì hoãn việc đánh giá
str.formatnên khá bất tiệnVớ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ị
Templatecủ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 JavaScripthtml()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
string.formatlà tối ưu nhất, và%cũng có thể chấp nhận được vì đã được dùng từ lâuCó 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ó ý 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