본문 바로가기
카테고리 없음

Python 3.14 t-strings 알아보기: f-string의 진화, 더 안전한 문자열 처리

by redcubes 2026. 1. 17.

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'>&lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;</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. 요약

  1. 문법: t"..." 또는 T"..." (f-string과 동일한 보간 문법)
  2. 반환: 문자열이 아닌 Template 객체
  3. 핵심: 정적/동적 부분 분리로 안전한 문자열 처리
  4. 활용: SQL, HTML, 로깅, 국제화 등 보안이 중요한 영역
  5. 모듈: string.templatelibTemplate, Interpolation 정의

t-strings는 개발자가 직접 파싱 로직을 짜기보다는, 프레임워크와 라이브러리들이 내부적으로 보안 기능을 강화하는 데 주로 사용될 것입니다. 일반 개발자는 f"..." 대신 sql(t"...")html(t"...")처럼 사용하여 훨씬 안전한 코드를 작성하게 될 것입니다.


참고 자료

추천 영상

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월)까지 릴리스되었습니다.

🎬 Source : Netflix <흑백요리사 2> 📝 Script : 최강록 셰프 칼럼 (채널예스) 🤖 Tools : AI 자막 합성