PEP 750 – อนุมัติ Template Strings (t-strings)
(peps.python.org)- PEP 750 เพิ่ม string literal แบบใหม่ให้กับ Python คือ template strings (
t"...") - เป็นรูปแบบที่ทำให้ f-string ถูกทำให้ทั่วไปยิ่งขึ้น โดยสร้างชนิด
Templateเพื่อให้สามารถประมวลผลค่าที่แทรกและสตริงก่อนนำมารวมกันได้ - สามารถนำไปใช้ได้อย่างมีประโยชน์กับเว็บเทมเพลต การตรวจสอบความปลอดภัย และ DSL (Domain-Specific Language)
ความสัมพันธ์กับ PEP อื่น ๆ
- f-string ถูกนำเข้ามาด้วย PEP 498 และมีการขยายไวยากรณ์ใน PEP 701
- PEP 501 เคยเสนอ general template strings (
i-string) แต่ถูกพักไว้ - PEP 750 ในปัจจุบันเป็นรูปแบบที่ทำให้ PEP 501 เรียบง่ายขึ้นและเป็นทั่วไปมากขึ้น โดยต่อยอดจากแนวคิดเดิม
แรงจูงใจและความจำเป็น
- f-string ใช้งานง่าย แต่ไม่สามารถประมวลผลค่าที่แทรกไว้ล่วงหน้าได้ จึงอาจก่อให้เกิดปัญหาด้านความปลอดภัย
- มีความเสี่ยงต่อช่องโหว่ด้านความปลอดภัย เช่น SQL injection และการโจมตีแบบ XSS
- เมื่อใช้ template strings จะสามารถประมวลผลค่าที่แทรกไว้ล่วงหน้าเพื่อใช้งานได้อย่างปลอดภัย
ตัวอย่าง:
evil = "<script>alert('evil')</script>"template = t"<p>{evil}</p>"assert html(template) == "<p><script>alert('evil')</script></p>"
สเปกของ template strings
template string literals
- นิยามโดยใช้ prefix
tหรือT - ประเมินค่าเป็นชนิด
string.templatelib.Template - รองรับไวยากรณ์คล้าย f-string และสามารถซ้อนกันได้
- ใช้ร่วมกับ prefix
rได้ (rt,tr) - ใช้ร่วมกับ prefix
u,bไม่ได้ - ไม่สามารถผสมใช้ f-string กับ template strings ได้
ชนิด Template
- เป็นชนิด immutable และมีคุณสมบัติดังนี้:
strings: ทูเพิลของชิ้นส่วนสตริงinterpolations: ทูเพิลของอ็อบเจ็กต์ค่าที่แทรกvalues: ทูเพิลของค่าที่แทรก__iter__(): iterator ที่คืนค่าสตริงและค่าที่แทรกตามลำดับ
ชนิด Interpolation
value: ผลลัพธ์ที่ประเมินแล้วexpression: สตริงของ expression ต้นฉบับที่แทรกไว้conversion: วิธีแปลงค่า (r,s,aหรือ None)format_spec: สตริงรูปแบบ
ตัวอย่าง:
name = "World"template = t"Hello {name!r}"assert template.interpolations[0].conversion == "r"
debug specifier =
t"{value=}"จะถูกตีความเป็นt"value={value!r}"- ช่องว่างจะถูกเก็บไว้ตามเดิมด้วย (
t"{value = }"→"value = {value!r}")
การต่อ template strings
- ใช้ตัวดำเนินการ
+เพื่อรวมTemplateกับstrหรือTemplateกับTemplateได้ - ผลลัพธ์จากการต่อจะเป็นชนิด
Templateเสมอ - รองรับ implicit string concatenation (
t"Hello " t"World") ด้วย
วิธีประมวลผล template strings
ตัวอย่าง: ฟังก์ชันแปลงเป็นตัวพิมพ์เล็ก/ใหญ่
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)
ตัวอย่าง: ทำงานแบบเดียวกับ f-string
- สามารถใช้ฟังก์ชัน
f()เพื่อสร้างผลลัพธ์แบบเดียวกับ f-string ได้
ตัวอย่าง: structured logging
- เมื่อใช้ template strings จะสามารถแสดงข้อความล็อกและค่าที่มีโครงสร้างพร้อมกันได้
- สามารถทำได้ผ่าน
StructuredMessageหรือ subclass ของlogging.Formatter
ตัวอย่าง: ประมวลผล HTML template
- ฟังก์ชัน
html()จะจัดการ escape เนื้อหาหรือแปลงเป็น attribute อย่างเหมาะสมตามตำแหน่งที่แทรก - รองรับ nested template ด้วย
รูปแบบการใช้งานขั้นสูง
- แนะนำให้ใช้ structural pattern matching (
match) - สตริงแบบคงที่สามารถใช้เป็น cache key เพื่อทำ memoization ได้อย่างมีประสิทธิภาพ
- สามารถ parse และประมวลผลผ่านตัวแทนกลาง เช่น AST ได้
- สำหรับการประเมินค่าแบบ lazy หรือ async สามารถใช้
lambda,awaitได้
ความสัมพันธ์ระหว่าง template strings กับ format strings แบบเดิม
- สามารถนิยามฟังก์ชัน template ในลักษณะคล้าย
.format()แบบเดิมได้ - ยังสามารถมี
from_format()สำหรับ parse สตริงภายนอกแล้วแปลงเป็นTemplateได้ด้วย
ความเข้ากันได้ ความปลอดภัย และการเรียนรู้
- ใน Python เวอร์ชันเก่าอาจเกิด syntax error ได้
- การประมวลผล template ช่วยเพิ่มความปลอดภัย
- ด้วยไวยากรณ์ที่คล้าย f-string จึงเรียนรู้ได้ง่าย
ทำไมต้องเป็นแนวทาง template แบบใหม่?
- เทมเพลตแบบเดิมอย่าง Jinja มีไว้สำหรับการปรับแต่งโดยผู้ใช้หรือดีไซเนอร์
- จึงจำเป็นต้องมีการรองรับในระดับภาษา Python เพื่อให้นักพัฒนาจัดการ template ได้โดยตรง
- ทำให้ใช้ประโยชน์จากข้อดีอย่างพลังในการแสดงออกและการตรวจสอบชนิดได้
สรุปรูปแบบตัวอย่าง
- structural pattern matching และการแมตช์คุณสมบัติย่อย
- นำ template กลับมาใช้ซ้ำแบบฟังก์ชัน
- รองรับ nested template
- รองรับการประเมินค่าแบบ Lazy/Async
- ปรับ cache ให้เหมาะสมด้วยการแยกส่วน static/dynamic
ข้อพิจารณาด้านการออกแบบอื่น ๆ
- template จะไม่ถูกแปลงเป็นสตริง และไม่มีการ implement
__str__() - มีคลาสที่เกี่ยวข้องอยู่ในโมดูล
string.templatelib Template,Interpolationถูกเปรียบเทียบตาม object identity- ไม่รองรับตัวดำเนินการ
==หรือ<
implementation อ้างอิงและตัวอย่าง
แนวคิดที่ถูกปฏิเสธ
- การใช้ prefix แบบกำหนดเอง (
my_tag"...") - การประเมินค่าแบบเลื่อนเวลาสำหรับทุก expression ที่แทรก
- การ implement ด้วย protocol
- การ override
__eq__,__hash__ - การกู้คืนสตริงต้นฉบับแบบสมบูรณ์
- การเพิ่มชนิด
Decoded - การรองรับ binary template strings
- ฟังก์ชันกำหนดชนิดฟอร์แมต (
"html","sql"เป็นต้น) - การจำกัดการต่อสตริง
- การอนุญาตตัวแปลงแบบกำหนดเอง (
!x)
3 ความคิดเห็น
การฟอร์แมตที่น่าพอใจที่สุดคงมีแค่ JavaScript กับ Python เท่านั้น ภาษาอื่นนี่ก็แบบว่า...
ควรมีวิธีที่ชัดเจนในการทำสิ่งนั้น—และถ้าเป็นไปได้ ก็ควรมีเพียงวิธีเดียวที่ชัดเจน
ความคิดเห็นจาก Hacker News
น่าสนใจที่แต่ละภาษาจัดการกับการจัดรูปแบบสตริงต่างกัน
Nick Humrich เป็นหนึ่งในผู้เขียนที่นำ t-strings มาใช้ผ่านการเขียน PEP 501 ขึ้นใหม่ และดีใจมากที่ PEP นี้ได้รับการยอมรับ
ยังไม่แน่ใจว่าฟีเจอร์ระดับภาษานี้มีคุณค่าหรือไม่
ชอบ f-strings แต่มีปัญหาที่ไม่สามารถหน่วงเวลาการประเมินผลได้
ในฐานะผู้ดูแล lit-html รู้สึกว่าน่าสนใจที่มันคล้ายกับ tagged template literals ของ JavaScript
คาดหวังว่าสิ่งที่ tagged template literals ของ JavaScript ช่วยได้ เช่น การ escape HTML อัตโนมัติหรือการทำ SQL parameterization จะนำมาใช้กับ Python ได้ด้วย
มีความเห็นว่า Python กำลังกลายเป็น PHP
%ก็ยอมรับได้เพราะใช้งานกันมานานไม่พอใจกับการเพิ่มสิ่งใหม่เข้ามาในภาษาอย่างต่อเนื่อง
มีความเห็นว่า PEP นี้คล้ายกับ P1819 ของ C++
มีความเห็นว่าโค้ดใน PEP ยืดยาวเกินไป