Python 3.14.0rc2/3.13.7リリース:Python 3.14のテンプレート文字列についても解説:Deep Insider Brief ― 技術の“今”にひと言コメント
Pythonの最新リリース候補「3.14.0rc2」と安定版の最新バージョンである「3.13.7」が公開された。3.14.0rc2はバイトコード生成のバグフィックスを目的に早期のリリースとなった。3.13.7はSSL不具合が修正された。
Pythonの新しいリリースが2025年8月14日に発表された。今回の更新は、次期メジャーバージョンのリリース候補「Python 3.14.0rc2」と、安定版の「Python 3.13.7」の同時公開である。
Python 3.14.0rc2は最終版に向けた2回目のリリース候補であり、もともとは2025年8月26日リリースの予定だった。rc2が前倒しで公開されたのはバグ修正に伴うバイトコード形式の変更によるものだ。この後、2025年9月16日にrc3のリリースが、2025年10月7日にPython 3.14.0のリリースが予定されている。一方、3.13.7は、SSL通信でのブロッキング問題(暗号化接続でデータ読み込みが止まる不具合)を修正するための緊急リリースである。
Python 3.14.0rc2が早期にリリースされた理由
Python 3.14.0rc1には論理演算の中でブール値への暗黙のキャストが行われた際に発生した例外がtry-except文で正しく捕捉されないというバグがあった。
以下はバグを報告する「Python implicit boolean conversion in logical operations bypasses try/except on 3.14.0rc1」からのサンプルコードの引用である。
class Foo:
def __bool__(self):
raise NotImplementedError()
a = Foo()
b = Foo()
# Works
try:
c = bool(a)
except:
print("passed c = bool(a)")
# Fails
try:
c = a or b
except:
print("passed c = a or b")
Fooクラスではブール値へのキャスト時にNotImplementedError例外が送出されるようになっている。そして、Fooクラスのインスタンスを2つ生成し、2つのtry-except文でブール値へのキャストが例外を送出するかどうかを調べている。
1つ目は「c = bool(a)」としてFooクラスのインスタンスであるaをブール値に明示的にキャストしている。2つ目は「c = a or b」としてFooクラスのインスタンスaとbをorでつなぎ論理演算を行っている(このときに内部でブール値へのキャストが暗黙的に行われる)。
両方ともブール値へのキャストが発生するので、try節でNotImplementedError例外が発生し、それがexcept節で捕捉され、print関数が実行されるはずだ。実際、Python 3.13.7でこのコードを実行すると以下のようになる。
しかし、Python 3.14.0rc1でこれを実行すると次のようになる。
Python 3.14.0rc1ではexcept節でNotImplementedError例外が捕捉されていないことが分かる。これはPython 3.14.0rc1のバイトコード生成に含まれるバグであり、Python 3.14.0rc2でこれを修正するのに伴ってバイトコードのバージョンが変更された。
バイトコードのバージョンはimportlib.util.MAGIC_NUMBERの値として取得でき、.pycファイルにもこれが埋め込まれる。このことから、Python 3.14.0rc2以降とrc1で作成された.pycファイルとの間では互換性がなくなった。ただし、rc2環境でrc1のコードを実行しようとしたときに、既に生成済みの(.pycファイルに保存された)バイトコードに互換性がないことが分かれば、バイトコードの再生成(および.pycファイルとしての保存)が行われるので多くの場合は問題となることはない。バイトコードを生成できない(権限がない、書き込み不可などの)場合には.pycファイルを生成せずに毎回バイトコードのコンパイルが行われることになるので、実行速度に影響が出るかもしれない。
なお、Python 3.14ではフリースレッドPythonが正式にサポートされるようになった。この他、型アノテーションの評価の遅延、テンプレート文字列リテラル(t文字列)、ゼロオーバーヘッドで外部デバッガーがPythonプロセスにアタッチ可能なインタフェース、PyREPL(Python 3.13で初登場した新しいREPL環境)での構文ハイライト機能など、多くの新機能がサポートされている。
以下ではこの中からテンプレート文字列について簡単に解説しよう。
どうもHPかわさきです。
Python 3.14.0rc2のリリースにかこつけて、Python 3.14の新機能の一つであるテンプレート文字列(t文字列)を以下では紹介します。Python 3.14ではここで紹介するテンプレート文字列だけでなく、フリースレッドなPythonが正式にサポートされるなど、今回は結構大きめの変化があるなというのが個人的な感想です。それらについても機会があればご紹介することにしましょう。
テンプレート文字列
テンプレート文字列(t文字列)とはf文字列と同様な構文をサポートした新たな文字列リテラルといえる。「t'hello {name}'」のようにクオート文字の前に「t」を記述することでテンプレート文字列が得られる。f文字列では波かっこ「{}」の中に指定した式の値が、文字列の評価時にそのまま補完されていたが、t文字列では文字列リテラル部分と補完部分を個別に管理し、特に補完部分に対して何らかの処理を事前に加えてから最終的な文字列を得られるような仕組みが導入されている。
なぜ、こうした仕組みが必要なのか。一つには最終的な文字列へセキュリティ的に問題があるものが埋め込まれることを防ぐことが挙げられる。例えば、「alert('evil')」という文字列があったとする。次のような素直なf文字列があった場合、この文字列は何のエスケープもなしに埋め込まれてしまう。
evil = "<script>alert('evil')</script>"
fstr = f"<p>{evil}</p>"
もちろん、エスケープ用のhtml関数があるとして、これを使って「f"{html(evil)}"」のように書けばよいかもしれない。しかし、1つのf文字列中にこうした置換フィールドが幾つもあった場合、フィールドごとに関数で補完要素を囲まなければならないだろう。
これに対して、(やはりエスケープ用のhtml関数があったとして)t文字列ではこの処理を次のように書ける(これはPEP 750から引用したコードだ)。
evil = "<script>alert('evil')</script>"
template = t"<p>{evil}</p>"
assert html(template) == "<p><script>alert('evil')</script></p>"
この例にある「template = t"<p>{evil}</p>"」がt文字列を定義している部分だ。既に述べたがt文字列は単純な文字列とは異なる形式で、その文字列リテラル部分と補完要素部分を内部的には個別に管理している。実際にどんなものかを見てみよう。
evil = "<script>alert('evil')</script>"
template = t"<p>{evil}</p>"
print(template)
これを実行すると次のようになる。
実際のところ、t文字列はPython 3.14で新たに導入されるstring.templatelibモジュールのTemplateクラスのインスタンスとなる。その属性としては上の画像から分かるようにリテラル文字列部分を格納するstrings属性、補完要素部分を格納するinterpolations属性などがある。また、補完要素部分の文字列そのものを取得するvalues属性もある。
もう少し簡単な例を見てみよう。
foo = 'FOO'
bar = 'BAR'
template = t'{foo} and {bar}'
print(template.strings)
print(template.interpolations)
print(template.values)
これを実行すると次のようになる。
strings属性には置換フィールド({}で囲まれた部分)を区切りとして、その前後の文字列が格納されている。「t'{foo} and {bar}'」では最初の置換フィールドとクオート文字の間に空文字列があるので、strings属性の先頭には空文字列があることに注目されたい。また、interpolations属性にはstring.templatelibモジュールで定義されるInterpolationクラスのインスタンスとして、補完要素部分の値が順番に格納されている。それらの値は文字列そのもの、置換フィールドに指定された式(の文字列表現)、文字列への変換(conversion)に使用する関数を指定する値(r、s、aのいずれか)、書式指定子となっている(詳細な説明は省略する)。values属性には補完要素部分の文字列そのものが格納されていることが分かる。
これらを使って何らかの処理を行うことも可能だが、リテラル文字列部分と補完要素部分を反復することも可能だ。例えば、上のコードで補完要素だけを小文字化したものを最終結果としたいのであれば、次のようなto_lower関数を書ける。
from string.templatelib import Interpolation
foo = 'FOO'
bar = 'BAR'
template = t'{foo} and {bar}'
def to_lower(t):
result = []
for item in t:
if isinstance(item, Interpolation):
result.append(item.value.lower())
else:
result.append(item)
return ''.join(result)
print(to_lower(template))
to_lower関数では受け取ったt文字列を反復して、その要素がInterpolationクラスのインスタンスであれば、そのvalue属性(補完要素の文字列そのもの)を小文字化して、resultに追加する。そうでなければ、要素(文字列リテラル部分)をそのままresultに追加する。最後にこれらを連結して戻り値としている。
これを実行すると次のようになる。
ここでは単純な小文字化を例としたが、危険な文字列要素を無害化するような処理をt文字列から最終的な結果を得るまでの間に介入させることが可能なことが分かるはずだ。
ここではt文字列についてホントに簡単にまとめました。取りあえずは、f文字列と同様な書式を持ちながら、「文字列」と言われて想像するのとは全く異なる構造のデータになっていること、それらがリテラル文字列部分と補完要素部分に分かれて管理されていること、個々の要素を反復して、独自の処理を加えられることを理解しておきましょう。
情報元
Copyright© Digital Advantage Corp. All Rights Reserved.