2 คะแนน โดย GN⁺ 2025-04-11 | 3 ความคิดเห็น | แชร์ทาง WhatsApp
  • 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>&lt;script&gt;alert('evil')&lt;/script&gt;</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 ความคิดเห็น

 
carnoxen 2025-04-11

การฟอร์แมตที่น่าพอใจที่สุดคงมีแค่ JavaScript กับ Python เท่านั้น ภาษาอื่นนี่ก็แบบว่า...

 
kandk 2025-04-11

ควรมีวิธีที่ชัดเจนในการทำสิ่งนั้น—และถ้าเป็นไปได้ ก็ควรมีเพียงวิธีเดียวที่ชัดเจน

 
GN⁺ 2025-04-11
ความคิดเห็นจาก Hacker News
  • น่าสนใจที่แต่ละภาษาจัดการกับการจัดรูปแบบสตริงต่างกัน

    • Java กำลังพยายามเพิ่ม f/t-strings แต่กำลังเจอปัญหาเพราะความต้องการความสมบูรณ์แบบที่พยายามแก้ทุกปัญหา
    • ดูเหมือนว่านักพัฒนา Go แทบไม่ได้พิจารณาปัญหานี้และมองข้ามมันไป
    • Python ใช้แนวทางที่สมดุล โดยถกเถียงวิธีจัดรูปแบบสตริงแบบใหม่และเลือกใช้การใช้งานที่เหมาะสม
    • แทบจะปฏิเสธไม่ได้ว่าเห็นด้วยกับแนวทางของ Python และกำลังได้รับประโยชน์จาก .format(), f-strings และ t-strings
  • Nick Humrich เป็นหนึ่งในผู้เขียนที่นำ t-strings มาใช้ผ่านการเขียน PEP 501 ขึ้นใหม่ และดีใจมากที่ PEP นี้ได้รับการยอมรับ

    • เริ่มทำงานกับ PEP 501 มาตั้งแต่ 4 ปีก่อน
  • ยังไม่แน่ใจว่าฟีเจอร์ระดับภาษานี้มีคุณค่าหรือไม่

    • สามารถได้ผลลัพธ์เดียวกันด้วยฟังก์ชันที่คืนค่า f-string
    • หากต้องการความปลอดภัยจากการแทรกข้อมูล ก็ใช้ tagged type และฟังก์ชัน sanitize ที่คืนค่าสตริงได้
    • แม้จะกระชับ แต่การใช้ตัวอักษรตัวเดียวเพื่อแยกระหว่างการประมวลผลทันทีและการประมวลผลแบบหน่วงเวลา อาจทำให้อ่านยากสำหรับคนที่ไม่คุ้นเคยกับ Python
  • ชอบ f-strings แต่มีปัญหาที่ไม่สามารถหน่วงเวลาการประเมินผลได้

    • เลยมีบางกรณีที่ต้องใช้ str.format ซึ่งไม่สะดวก
  • ในฐานะผู้ดูแล lit-html รู้สึกว่าน่าสนใจที่มันคล้ายกับ tagged template literals ของ JavaScript

    • วิธีที่คลาส Template ของ Python แยก tagged function ออกจากอาร์กิวเมนต์นั้นมีความเป็นเอกลักษณ์เมื่อเทียบกับ JavaScript
    • ในโครงสร้างเทมเพลตที่ซ้อนกัน อาจไม่จำเป็นต้องใช้ฟังก์ชัน html()
  • คาดหวังว่าสิ่งที่ tagged template literals ของ JavaScript ช่วยได้ เช่น การ escape HTML อัตโนมัติหรือการทำ SQL parameterization จะนำมาใช้กับ Python ได้ด้วย

  • มีความเห็นว่า Python กำลังกลายเป็น PHP

    • f-strings และ t-strings เพิ่มความซับซ้อนให้ภาษา
    • มองว่า string.format ดีที่สุด และ % ก็ยอมรับได้เพราะใช้งานกันมานาน
    • อยากให้ทีมภาษามุ่งไปที่เรื่องที่สำคัญกว่า
  • ไม่พอใจกับการเพิ่มสิ่งใหม่เข้ามาในภาษาอย่างต่อเนื่อง

    • รู้สึกว่าภาษาเหมือนถูกออกแบบโดยคณะกรรมการ
  • มีความเห็นว่า PEP นี้คล้ายกับ P1819 ของ C++

  • มีความเห็นว่าโค้ดใน PEP ยืดยาวเกินไป

    • ดูเหมือนว่า Python กำลังแสดงความฟุ่มเฟือยที่ไม่จำเป็น มากกว่าจะเป็น pseudocode ที่รันได้
    • เมื่อเทียบกับโค้ดของ Ruby แล้ว โค้ด Python ดูยืดยาวกว่า