できない.dev

Python を 3.14 から 3.15 に上げると動かなくなること

Python を 3.14 から 3.15 に上げると、open() の既定の文字コードが UTF-8 になり、Shift_JIS のファイルが UnicodeDecodeError で読めなくなる。
年の無い strptime や sre_constants を使うライブラリも落ちる。
3.14 と 3.15 で同じファイルを実行した 5 つの変更点と、それぞれの直し方をまとめる。

公開:

公式イメージの Python 3.15 と、uv で入れた Python 3.14 を同じコンテナに並べ、同じファイルを両方で実行した。
各節の出力は「v3.14 では」「v3.15 に上げると」「v3.15 で直すと」の 3 段で並ぶ。
5 つとも 3.15 では例外で止まるが、3.14 では最後まで動いていた。

日本語の環境で一番当たりやすいのは、最初の open() の文字コードである。
ほかの 4 つは 3.14 が DeprecationWarning を出していた書き方で、警告がライブラリの中で出ると既定では表示されない。
上げる前に 3.14 で python -W default::DeprecationWarning -X warn_default_encoding を付けてテストを走らせると、警告として先に見つけられる。

open() の既定の文字コードが UTF-8 になった

3.14 までの open() は、encoding を渡さないとロケールの文字コードで読み書きした。
日本語版 Windows では cp932(Shift_JIS 系)になる。
3.15 は UTF-8 モードが既定になり(PEP 686)、ロケールに関係なく UTF-8 で読む。
ここでは Linux のコンテナでロケールを ja_JP.SJIS にして、日本語版 Windows と同じ状況を作った。
読んだのは Shift_JIS で保存した 2 行の CSV である。

v3.14 では

$ cat read.py
with open("scores.csv") as f:
    print(f.read().splitlines())
$ env LANG=ja_JP.SJIS python3.14 read.py
['名前,点数', '佐藤,80']
$ echo $?
0
$ env LANG=ja_JP.SJIS python3.14 -X warn_default_encoding read.py
/app/read.py:1: EncodingWarning: 'encoding' argument not specified
  with open("scores.csv") as f:
['名前,点数', '佐藤,80']

v3.15 に上げると

$ env LANG=ja_JP.SJIS python3.15 read.py
Traceback (most recent call last):
  File "/app/read.py", line 2, in <module>
    print(f.read().splitlines())
          ~~~~~~^^
  File "<frozen codecs>", line 325, in decode
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x96 in position 0: invalid start byte
$ echo $?
1

v3.15 で直すと

$ env LANG=ja_JP.SJIS python3.15 -X utf8=0 read.py
['名前,点数', '佐藤,80']
$ echo $?
0
$ cat read.py
with open("scores.csv", encoding="cp932") as f:
    print(f.read().splitlines())
$ env LANG=ja_JP.SJIS python3.15 read.py
['名前,点数', '佐藤,80']
$ echo $?
0

3.14 でも -X warn_default_encoding を付けると、encoding を省いた行に EncodingWarning が出る。
直すときは、読むファイルの文字コードを encoding で書く。
Excel が書き出した CSV なら cp932 を渡す。
今までどおりロケールに従わせたいなら encoding="locale" を渡す。

コードに手を入れられないときは、-X utf8=0 か環境変数 PYTHONUTF8=0 で 3.14 までの既定に戻せる。
書き込みも同じように変わり、encoding を省いて書いたファイルは 3.15 から UTF-8 になる。
Excel でそのまま開くと文字化けするので、Excel に渡すファイルは encoding="utf-8-sig" か cp932 で書く。

年の無い日付を %d で読むと ValueError になった

datetime.strptime("10-31", "%m-%d") のように年を含まない書式で日付を読むと、3.14 までは 1900 年として読んでいた。
1900 年はうるう年ではないので、この書き方では 2 月 29 日が読めない。
3.14 は DeprecationWarning を出し、3.15 は書式を組み立てる段階で ValueError を出す。

v3.14 では

$ cat bday.py
from datetime import datetime
 
print(datetime.strptime("10-31", "%m-%d"))
$ python3.14 bday.py
/app/bday.py:3: DeprecationWarning: Parsing dates involving a day of month without a year specified is ambiguous
and fails to parse leap day. The default behavior will change in Python 3.15
to either always raise an exception or to use a different default year (TBD).
To avoid trouble, add a specific year to the input & format.
See https://github.com/python/cpython/issues/70647.
  print(datetime.strptime("10-31", "%m-%d"))
1900-10-31 00:00:00
$ echo $?
0

v3.15 に上げると

$ python3.15 bday.py
Traceback (most recent call last):
  File "/app/bday.py", line 3, in <module>
    print(datetime.strptime("10-31", "%m-%d"))
          ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.15/_strptime.py", line 844, in _strptime_datetime_datetime
    tt, fraction, gmtoff_fraction = _strptime(data_string, format)
                                    ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.15/_strptime.py", line 559, in _strptime
    format_regex = _TimeRE_cache.compile(format)
  File "/usr/local/lib/python3.15/_strptime.py", line 504, in compile
    return re_compile(self.pattern(format), IGNORECASE)
                      ~~~~~~~~~~~~^^^^^^^^
  File "/usr/local/lib/python3.15/_strptime.py", line 485, in pattern
    raise ValueError(
    ...<4 lines>...
        "See https://github.com/python/cpython/issues/70647.")
ValueError: Day of month directive '%d' may not be used without a year directive. Parsing dates involving a day of month without a year is ambiguous and fails to parse leap day. Add a year to the input and format. See https://github.com/python/cpython/issues/70647.
$ echo $?
1

v3.15 で直すと

$ cat bday.py
from datetime import datetime
 
d = datetime.strptime("2026-10-31", "%Y-%m-%d")
print(d.month, d.day)
$ python3.15 bday.py
10 31
$ echo $?
0

入力と書式の両方に年を入れる。
誕生日のように月日だけの文字列なら、f"{year}-{s}" のように年を足してから %Y-%m-%d で読む。
月と日の数字だけが要るなら、strptime を使わずに s.split("-") で分けてもよい。

sre_constants などの古いモジュールが消えた

sre_compile・sre_constants・sre_parse は re モジュールの中身で、3.11 から非推奨だった。
3.15 で消えたので、これを import するライブラリは読み込んだ時点で ModuleNotFoundError になる。
ここでは、正規表現に当てはまる文字列を列挙する sre_yield を使った。

v3.14 では

$ python3.14 -c 'import sre_yield; print(list(sre_yield.AllStrings("[ab]c")))'
['ac', 'bc']
$ echo $?
0

v3.15 に上げると

$ python3.15 -c 'import sre_yield; print(list(sre_yield.AllStrings("[ab]c")))'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
    import sre_yield; print(list(sre_yield.AllStrings("[ab]c")))
    ^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.15/site-packages/sre_yield/__init__.py", line 33, in <module>
    import sre_constants
ModuleNotFoundError: No module named 'sre_constants'
$ echo $?
1

v3.15 で直すと

$ python3.15 -c 'import exrex; print(list(exrex.generate("[ab]c")))'
['ac', 'bc']
$ echo $?
0

トレースバックの最後に出るパス(site-packages/sre_yield/__init__.py)で、どのライブラリが原因かが分かる。
新しい版で直っていれば、その版に上げる。
検証した時点で pip が入れた sre_yield は 3.15 で読み込めなかったので、ここでは同じことができる exrex に替えた。

NamedTuple をキーワード引数で定義できなくなった

NamedTuple("Point", x=int, y=int) のように、フィールドをキーワード引数で渡す書き方は 3.13 から非推奨だった。
3.15 ではキーワード引数そのものを受け付けず、TypeError になる。

v3.14 では

$ cat point.py
from typing import NamedTuple
 
Point = NamedTuple("Point", x=int, y=int)
print(Point(1, 2))
$ python3.14 point.py
/app/point.py:3: DeprecationWarning: Creating NamedTuple classes using keyword arguments is deprecated and will be disallowed in Python 3.15. Use the class-based or functional syntax instead.
  Point = NamedTuple("Point", x=int, y=int)
Point(x=1, y=2)
$ echo $?
0

v3.15 に上げると

$ python3.15 point.py
Traceback (most recent call last):
  File "/app/point.py", line 3, in <module>
    Point = NamedTuple("Point", x=int, y=int)
TypeError: NamedTuple() got an unexpected keyword argument 'x'
$ echo $?
1

v3.15 で直すと

$ cat point.py
from typing import NamedTuple
 
Point = NamedTuple("Point", [("x", int), ("y", int)])
print(Point(1, 2))
$ python3.15 point.py
Point(x=1, y=2)
$ echo $?
0

フィールドを (名前, 型) のリストで渡す。
クラスで書く class Point(NamedTuple): の形に直してもよく、型チェッカーにはこちらのほうが読みやすい。

フィールドを渡さない TypedDict が書けなくなった

TypedDict("Options") のようにフィールドを渡さずに作る書き方も、3.13 から非推奨だった。
3.15 では fields が必須の引数になり、省くと TypeError になる。

v3.14 では

$ cat empty.py
from typing import TypedDict
 
Options = TypedDict("Options")
print(Options())
$ python3.14 empty.py
/app/empty.py:3: DeprecationWarning: Failing to pass a value for the 'fields' parameter is deprecated and will be disallowed in Python 3.15. To create a TypedDict class with 0 fields using the functional syntax, pass an empty dictionary, e.g. `Options = TypedDict('Options', {})`.
  Options = TypedDict("Options")
{}
$ echo $?
0

v3.15 に上げると

$ python3.15 empty.py
Traceback (most recent call last):
  File "/app/empty.py", line 3, in <module>
    Options = TypedDict("Options")
TypeError: TypedDict() missing 1 required positional argument: 'fields'
$ echo $?
1

v3.15 で直すと

$ cat empty.py
from typing import TypedDict
 
Options = TypedDict("Options", {})
print(Options())
$ python3.15 empty.py
{}
$ echo $?
0

フィールドが無い TypedDict は、空の辞書 {} を渡して作る。
3.14 の警告文にも同じ直し方が書いてある。

検証環境Python 3.15.2 / Git 2.47.3 / Python(旧) 3.14.10 / 2026-10-31 検証(ほか 3 件)
検証日
実行環境
python:3.15 Debian GNU/Linux 13 (trixie)
バージョン
  • Python 3.15.2
  • Git 2.47.3
  • Python(旧) 3.14.10
  • Python(新) 3.15.2
  • sre_yield 1.2
  • exrex 0.12.0

この記事の「実行例」は、上記の環境で実際にコマンドを実行して得られた出力をそのまま掲載しています。 再現手順はリポジトリの検証スクリプトとして管理し、定期的に再実行して出力を更新しています。