Python 3.14에서 템플릿 문자열 리터럴(t-strings)이 공식 도입되었습니다. PEP 750에 의해 제안된 이 기능은 제가 좋아하는 f-string의 편리함은 그대로 유지하면서, 보안성과 유연성을 대폭 강화한 새로운 문자열 처리 방식입니다.
t-strings가 무엇인지, 왜 필요한지, 어떻게 활용하는지 예시와 함께 살펴보겠습니다.
1. t-strings란 무엇인가?
t-strings의 핵심 개념을 한 문장으로 요약하면:
"문자열을 즉시 합치지 않고, 재료(템플릿과 값)를 그대로 전달한다"
기본 문법
- 접두사: 문자열 앞에
t또는T를 붙입니다 - 반환 타입:
str이 아닌string.templatelib.Template객체 - 보간 문법: f-string과 동일하게
{변수}사용
from string.templatelib import Template
name = "Alice"
# f-string: 즉시 문자열로 평가
f_result = f"Hello, {name}!"
print(type(f_result)) # <class 'str'>
print(f_result) # "Hello, Alice!"
# t-string: Template 객체 반환
t_result = t"Hello, {name}!"
print(type(t_result)) # <class 'string.templatelib.Template'>
2. f-string vs t-string: 핵심 차이점
가장 큰 차이는 "언제 합쳐지는가(Evaluation)"입니다.
| 특성 | f-string (f"...") |
t-string (t"...") |
|---|---|---|
| 평가 시점 | 즉시 (작성 시점에 문자열 완성) | 지연 (사용 시점에 객체로 전달) |
| 반환 타입 | str (단순 문자열) |
Template (구조화된 객체) |
| 보간값 접근 | 불가능 (이미 합쳐짐) | 가능 (.values, .interpolations) |
| 주요 용도 | 단순 출력, 디버깅 | SQL 쿼리, HTML 생성, 로깅, 국제화 |
| 보안성 | 취약할 수 있음 (수동 처리 필요) | 안전함 (구조와 값의 자동 분리) |
비유로 이해하기
f-string은 완성된 요리를 받는 것과 같습니다. 이미 모든 재료가 섞여 있어서 나중에 특정 재료만 빼거나 교체할 수 없습니다.
t-string은 재료가 분리된 밀키트를 받는 것과 같습니다. 레시피(정적 문자열)와 재료(보간값)가 따로 포장되어 있어서, 조리사(처리 함수)가 상황에 맞게 재료를 검수하고 안전하게 조리할 수 있습니다.
3. 왜 t-strings가 필요한가? (보안 문제)
t-strings의 진가는 보안(Security)에서 발휘됩니다. 특히 SQL 인젝션이나 XSS(Cross-Site Scripting) 공격을 원천적으로 차단할 수 있는 구조를 제공합니다.
SQL 인젝션 시나리오
악의적인 사용자가 ' OR 1=1 --를 입력했다고 가정해 봅시다.
❌ f-string 사용 시 (위험)
user_id = "' OR 1=1 --"
# 값이 쿼리문에 그대로 붙어버림 → 해킹 성공!
query = f"SELECT * FROM users WHERE id = '{user_id}'"
# 결과: "SELECT * FROM users WHERE id = '' OR 1=1 --'"
# → 모든 사용자 정보가 유출됨
execute_sql() 함수는 str을 입력으로 받기 때문에, 그 문자열의 어느 부분이 의도된 것이고 어느 부분이 악성 코드인지 구분할 방법이 없습니다.
✅ t-string 사용 시 (안전)
def sql(template):
"""파라미터화된 SQL 쿼리 생성"""
query = '?'.join(template.strings)
params = template.values
return query, params
user_id = "' OR 1=1 --"
query, params = sql(t"SELECT * FROM users WHERE id = {user_id}")
print(query) # "SELECT * FROM users WHERE id = ?"
print(params) # ("' OR 1=1 --",)
# DB 실행 시: cursor.execute(query, params)
# → 파라미터가 안전하게 바인딩되어 해킹 실패!
t-string은 쿼리 구조(정적 부분)와 데이터(동적 부분)가 분리되어 전달되므로, 처리 함수가 데이터를 안전하게 이스케이프하거나 Prepared Statement로 실행할 수 있습니다.
4. Template 객체 구조 뜯어보기
t-string이 반환하는 Template 객체의 내부 구조를 살펴봅시다.
from string.templatelib import Template, Interpolation
name = "World"
age = 25
template = t"Hello, {name}! You are {age} years old."
# 1. 고정된 문자열 부분 (strings)
print(template.strings)
# ('Hello, ', '! You are ', ' years old.')
# 2. 보간된 값들 (values)
print(template.values)
# ('World', 25)
# 3. 상세 보간 정보 (interpolations)
print(template.interpolations)
# (Interpolation('World', 'name', None, ''),
# Interpolation(25, 'age', None, ''))
# 4. 순회하며 접근
print(list(template))
# ['Hello, ', Interpolation('World', 'name', None, ''),
# '! You are ', Interpolation(25, 'age', None, ''),
# ' years old.']
Interpolation 객체의 속성
각 {...}는 Interpolation 객체로 기록됩니다:
pi = 3.14159
template = t"Value: {pi!r:.2f}"
interp = template.interpolations[0]
print(interp.value) # 3.14159 (평가된 값)
print(interp.expression) # 'pi' (원본 표현식 텍스트)
print(interp.conversion) # 'r' (!r, !s, !a 변환 지정자)
print(interp.format_spec) # '.2f' (포맷 지정자)
중요: t-string은 f-string과 달리 conversion이나 format_spec을 자동 적용하지 않습니다. 이 정보는 그냥 기록만 되고, 최종 처리 함수가 이를 해석하고 적용할지 결정합니다.
5. 실전 예시: 커스텀 템플릿 프로세서
t-string을 활용하려면 이를 처리하는 프로세서 함수가 필요합니다. 몇 가지 실용적인 예시를 살펴봅시다.
예시 1: 안전한 HTML 생성기
HTML 태그가 포함된 사용자 입력을 자동으로 이스케이프하는 함수입니다.
from html import escape
from string.templatelib import Interpolation
def html(template):
"""안전한 HTML 생성 - 보간값만 이스케이프"""
parts = []
for part in template:
if isinstance(part, Interpolation):
# 동적 부분(사용자 입력)만 이스케이프 처리
parts.append(escape(str(part.value), quote=True))
else:
# 정적 HTML 태그는 그대로 유지
parts.append(part)
return "".join(parts)
# 악성 스크립트가 포함된 사용자 입력
user_input = '<script>alert("XSS")</script>'
# t-string으로 안전하게 처리
output = html(t"<div class='message'>{user_input}</div>")
print(output)
# <div class='message'><script>alert("XSS")</script></div>
# → 스크립트가 실행되지 않고 안전하게 텍스트로 출력!
핵심은 정적 HTML 태그는 건드리지 않고, 위험한 사용자 입력은 보간값에서만 처리하기가 구조적으로 쉬워진다는 점입니다.
예시 2: 구조화된 로깅 시스템
import json
from datetime import datetime
from string.templatelib import Interpolation
def structured_log(level, template):
"""메타데이터가 포함된 구조화 로깅"""
# 메시지 조립
message = ''.join(
str(part) if isinstance(part, str) else str(part.value)
for part in template
)
# 메타데이터 자동 추출
metadata = {
interp.expression: interp.value
for interp in template.interpolations
}
log_entry = {
"timestamp": datetime.now().isoformat(),
"level": level,
"message": message,
"fields": metadata
}
print(json.dumps(log_entry, ensure_ascii=False, indent=2))
# 사용 예시
username = "hanz"
action = "login"
ip_address = "192.168.1.100"
structured_log("INFO", t"User {username} performed {action} from {ip_address}")
출력 결과:
{
"timestamp": "2025-01-17T14:30:00.123456",
"level": "INFO",
"message": "User hanz performed login from 192.168.1.100",
"fields": {
"username": "hanz",
"action": "login",
"ip_address": "192.168.1.100"
}
}
로깅 시스템이 변수명과 값을 자동으로 추출하여 검색 가능한 메타데이터로 저장할 수 있습니다!
예시 3: 대소문자 변환 (공식 문서 예시)
Python 3.14 공식 문서에 나온 예시로, 정적 부분과 동적 부분을 다르게 처리하는 방법을 보여줍니다.
from string.templatelib import Interpolation
def lower_upper(template):
"""정적 부분은 소문자, 보간값은 대문자로"""
parts = []
for part in template:
if isinstance(part, Interpolation):
parts.append(str(part.value).upper())
else:
parts.append(part.lower())
return "".join(parts)
name = "Wensleydale"
result = lower_upper(t"Mister {name}")
print(result) # "mister WENSLEYDALE"
6. f-string과의 평가 규칙 차이 (고급)
t-string과 f-string 사이에는 몇 가지 중요한 평가 규칙 차이가 있습니다.
1) format() 프로토콜 자동 적용 안 함
# f-string: 즉시 format() 적용
f"{3.14159:.2f}" # "3.14"
# t-string: format_spec만 기록, 적용은 처리 함수가 결정
t"{3.14159:.2f}".interpolations[0].format_spec # ".2f"
2) 중첩 포맷 지정은 미리 평가됨
amount = 12.3456
precision = 2
tpl = t"{amount:.{precision}f}"
# precision이 먼저 평가되어 format_spec이 완성됨
tpl.interpolations[0].format_spec # '.2f'
3) 디버그 표현식 (=)
x = 10
tpl = t"{x=}"
# 'x=' 텍스트가 직전 문자열에 붙고, 기본 conversion이 'r'(repr)이 됨
print(tpl.strings) # ('x=', '')
print(tpl.interpolations) # (Interpolation(10, 'x', 'r', ''),)
7. Raw t-strings
정규표현식이나 파일 경로 작업 시 유용한 Raw t-string도 지원됩니다.
path = "documents"
# 일반 t-string: \n이 개행으로 해석됨
template1 = t"C:\new\{path}"
# Raw t-string: 백슬래시가 그대로 유지됨
template2 = rt"C:\new\{path}" # 또는 tr"..."
8. 실제 라이브러리 지원 현황
Python 3.14 출시 이후, 여러 라이브러리에서 t-strings 지원을 시작하거나 논의 중입니다:
- Psycopg 3 (PostgreSQL): t-string으로 안전한 SQL 쿼리 작성 지원
- SQLAlchemy: GitHub 이슈에서 t-strings 지원 논의 진행 중
- Django: 포럼에서
format_html()등에 t-string 지원 논의 중
# Psycopg 3에서의 사용 예시
cursor.execute(t"SELECT * FROM users WHERE id = {user_id}")
cursor.execute(
t"INSERT INTO users (name, email) VALUES ({name}, {email})"
)
시간이 지나면서 더 많은 프레임워크와 라이브러리에서 t-strings를 활용한 보안 기능이 도입될 것으로 예상됩니다.
9. 언제 무엇을 사용해야 할까?
| 상황 | 추천 |
|---|---|
| 단순 출력, 디버깅, 로그 메시지 | f-string |
| SQL 쿼리 생성 | t-string |
| HTML/XML 템플릿 | t-string |
| 구조화된 로깅 | t-string |
| 국제화(i18n) 처리 | t-string |
| 사용자 입력이 포함된 문자열 | t-string |
10. 요약
- 문법:
t"..."또는T"..."(f-string과 동일한 보간 문법) - 반환: 문자열이 아닌
Template객체 - 핵심: 정적/동적 부분 분리로 안전한 문자열 처리
- 활용: SQL, HTML, 로깅, 국제화 등 보안이 중요한 영역
- 모듈:
string.templatelib에Template,Interpolation정의
t-strings는 개발자가 직접 파싱 로직을 짜기보다는, 프레임워크와 라이브러리들이 내부적으로 보안 기능을 강화하는 데 주로 사용될 것입니다. 일반 개발자는 f"..." 대신 sql(t"...")나 html(t"...")처럼 사용하여 훨씬 안전한 코드를 작성하게 될 것입니다.
참고 자료
- PEP 750 – Template Strings
- Python 3.14 공식 문서 - string.templatelib
- What's New in Python 3.14
- Python's new t-strings (Dave Peck - PEP 750 공동 저자)
- Real Python - Template Strings Guide
- Python 3.14.2 다운로드 (최신 버전)
추천 영상
Python 3.14's new template string feature (YouTube) - t-string이 f-string과 어떻게 다르며, 실제로 안전한 HTML 템플릿을 만드는 방법을 시각적으로 잘 설명하는 영상입니다.
Python 3.14.0은 2025년 10월 7일에 정식 출시되었으며, 현재 3.14.2 버전(2025년 12월)까지 릴리스되었습니다.
