Python 3.12 で実は大きく変わった f-string
文字列表示 と f-string
Python で文字列を扱うとき、 f-string 使っていますか?
age = 25 print(f"私は{age}歳です。{'ベテラン' if age >= 30 else '若手'}です。")
とでもしておけば、
私は25歳です。若手です。
と出力されるというものです。
ちなみに JavaScript や Ruby といった多言語でも似たような構文自体はあります。
const age = 25 console.log(`私は${age}歳です。${age >= 30 ? 'ベテラン': '若手'}です。`)
のようにすれば同じです。
f-string のような書き方のいいところは、
import datetime from zoneinfo import ZoneInfo now = datetime.datetime.now(ZoneInfo("Asia/Tokyo")) print(f"ただいまの時刻は {now} 分です。")
としておけば
ただいまの時刻は 2025-05-23 09:16:54.704617+09:00 分です。
のように、勝手に string に変換してくれるところです。
format指定も可能なので、
>>> f"{123:04d}" '0123'
のように、ゼロ埋めも簡単にできます。
今回はこの f-string が 3.12 で大きく挙動を変えているという話です。
f-string の歴史と変化
f-string の登場
f-string は 「PEP 498 – Literal String Interpolation」 にて初めて提案され、 Python 3.6 のアップデートに含まれています。
Python 3.6 のリリースは 2016年12月なので、もう8年半前です。(2016年12月からもう8年も経っているのか…という気持ちになります)
ちなみに、 What’s new in Python 3.6 の日本語訳上は「フォーマット済み文字列リテラル」です。
f-string の課題
そんな f-string ですが、字句解析(lexer)によって処理を行っていました。
つまるところ、あくまで文字列の中にある {}
で囲まれた部分をパースして評価、ということになります。
対応としては少しアドホックですので、3.11以前だと、 バックスラッシュを含むだけで怒られる など、たまに不便な点がありました。
>>> f"{'foo\nbar'}" File "<stdin>", line 1 f"{'foo\nbar'}" ^ SyntaxError: f-string expression part cannot include a backslash
ほかにも、f-string で使っているクオートと、 {}
の中の クオートで同一種類を混ぜてしまうと字句解析出来なくなりますのでやはりエラーになります。
>>> f"{"foo"}" File "<stdin>", line 1 f"{"foo"}" ^^^ SyntaxError: f-string: expecting '}'
要するに、上記の例だと、 f"{"
でまず文字列が切れてしまう、ということですね。
課題と向き合うPEP
Python 3.6 がリリースされてから6年経過した、2022年11月に 「PEP 701 – Syntactic formalization of f-strings」が提案されます。
ここでは、字句解析ではなくてちゃんと文法(Syntactic)レベルで解析処理して、柔軟性を上げていこうぜ、という提案になっています。
上記のバックスラッシュが使えない問題や同一種類のクオート使用不可問題に対応しています。
私だったら 「まあそんなもんだよな」と流してしまうところを、ちゃんとより良い動きにしていこうと提案する人が居る というところがすごいなと感じます。。
そして、晴れて2023年10月にリリースされた Python 3.12 にこの内容が含まれ、 字句解析ではなく、文法レベルでの解析が行われるようになった ということです。
Python 3.12 での f-string の挙動
というわけで、早速 Python3.12 での f-string の挙動を確認します。
バックスラッシュの使用
まずは先ほどのバックスラッシュを含む文字列問題です。
>>> print(f"{'foo\nbar'}") foo bar
このように確かに問題無く利用できます。
同一クオートの使用
このまま、同一種類のクオート (”
) を利用してみます。
>>> print(f"{"foo\nbar"}") foo bar
まったく怒られません。
個人的には、 dict のキー指定などでこの罠によくハマっていた ので、このアップデートは地味に嬉しいです。
>>> person = {"name": "Takuya", "age": 30, "city": "Osaka"} ... print(f"Hello, {person["name"]}! You are {person["age"]} years old and live in {person["city"]}.") ... Hello, Takuya! You are 30 years old and live in osaka.
いやー、最高ですね。 (とかいいつつ、 syntax highlight が対応していなかったりすると、クオートを使い分けそうですが…)
コメントの使用
文法レベルで対応したということは、コメントも使えます。
person = {"name": "Takuya", "age": 30, "city": "Osaka"} print(f"""Hello, { person["name"] # TODO: 姓名両方を表示するように変更する }!""")
のようなことですね。
Python 3.14 では t-string が登場
そして、今年の10月リリース予定の Python 3.14 では、 t-string が登場します。
「PEP 750 – Template Strings」で提案されているものです。
テンプレート型のオブジェクトにすることによって、f-string のように即時で評価せずに、遅延評価で文字列を埋め込んでいくことができます。
といってもピンとこないかもしれません。
「Processing Template Strings」のセクションに 具体的な例として、下記が上げられていますが
from string.templatelib import Template, Interpolation def lower_upper(template: Template) -> str: parts: list[str] = [] for item in template: if isinstance(item, Interpolation): parts.append(str(item.value).upper()) else: parts.append(item.lower()) return "".join(parts) name = "world" print(lower_upper(t"HELLO {name}")) # 出力: hello WORLD
要するに、 t"HELLO {name}"
ですぐ HELLO world
という文字列を生成するわけではなく、
t"HELLO {name}"
で生成したテンプレート内の item
が isinstance(item, Interpolation)
、つまり、動的な内容の場合は大文字・静的な内容の場合は小文字とする…といった変換を行って出力する、みたいなことができるということですね。
HTMLやSQLにおけるインジェクション対策 として有効に使えそうです。
まとめ
今回は Python 3.12 のアップデートに含まれていた、久々の f-string の変更について扱いました。
Pythonは3にバージョンアップされてから久しいですが、以降も着実にバージョンアップを重ねているので、マイナーバージョンアップがあった場合にも、 What’s new を見ておくと何かと良いことがあるように思います。
何事も過去のやり方に囚われすぎないことが大事ですね…