1 of 47

PythonのUTF-8化

2 of 47

お前誰よ

稲田直哉

@methane (GitHub, X)

OSS developer @ KLab Inc.

MySQL driver (Go, Python)

エキスパートPythonプログラミング 翻訳

3 of 47

CPython Core Developer

  • Compact Ordered Dict (Python 3.6)
  • 起動高速化 (PYTHONPROFILEIMPORTTIME等)
  • GCヘッダー軽量化 (Python 3.8)
  • 文字列の型アノテーションの軽量化 (Python 3.10)
  • 文字列キーdictの軽量化 (Python 3.11)
  • 文字列の軽量化 (Python 3.12)

4 of 47

問題あるコード

  • encoding を指定してしない
    • ロケールエンコーディング
    • 日本語Windowsではcp932
  • JSONはUTF-8

5 of 47

修正版

encoding を指定する

バイナリモードを使う

(推奨)

6 of 47

デフォルトで

UTF-8を使って欲しい!!

7 of 47

デフォルトでUTF-8を使うべき理由

  • PythonのソースコードはUTF-8
  • JSON, TOML, YAML, MarkdownもUTF-8
  • VSCodeやメモ帳もデフォルトでUTF-8
  • node.jsもGoもRustもUTF-8
  • インターネット上のテキストもほぼUTF-8

8 of 47

なるよ!

Python 3.15 (2026)から�UTF-8 がデフォルトに!!

9 of 47

Pythonの主なエンコーディング

  • デフォルトエンコーディング
  • ファイルシステムエンコーディング
  • テキストエンコーディング

10 of 47

デフォルトエンコーディング

  • sys.getdefaultencoding() で取得
  • str.encode(), bytes.decode() �で使われる
  • すでにUTF-8

11 of 47

ファイルシステムエンコーディング

  • sys.getfilesystemencoding() で取得
  • ファイルパス, コマンドライン引数で使われる
  • Unixではロケールエンコーディング
  • WindowsではUTF-8

12 of 47

テキストエンコーディング

  • io.TextIOWrapper がデフォルトで使うエンコーディング
    • open(), stdout, subprocess.PIPE, etc…
  • ロケールエンコーディング

13 of 47

ロケールエンコーディング

  • locale.getencoding() (Python 3.11~)
    • locale.getpreferredencoding() (~Python 3.10)
  • Unixでは locale (LC_CTYPE)
  • Windowsでは ANSI code page (GetACP())
    • 日本語ではcp932

14 of 47

UTF-8化の歴史と未来

15 of 47

UTF-8化の歴史と未来

  • Python 3
  • Python 3.6
  • Python 3.7
  • Python 3.10
  • Python 3.15

16 of 47

Python 2 から Python 3 へ

17 of 47

Python 2

  • ソースコードはデフォルトでASCII
    • 先頭に # coding: utf-8
  • デフォルトエンコーディングは 'ascii'
  • バイト文字列ユニコード文字列

18 of 47

暗黙型変換

1 + 2.0 => 3.0

b"hello" + u"世界" => u"hello世界"

u"hello" + b"世界" => UnicodeDecodeError

19 of 47

Python 2 時代の日本語対応

  1. UnicodeDecodeError に出会う
  2. バイト文字列がどこから来たのか探す
  3. 適切なエンコーディングでデコードする

20 of 47

Python 3

  • バイト列が文字列ではなくなる
    • 暗黙型変換もなし
  • デフォルトエンコーディングはUTF-8
  • ソースコードのデフォルトもUTF-8
  • (Windows)ファイルパスやコマンドライン引数が�Unicode化

21 of 47

互換性問題

  • Python 3への移行はおよそ10年がかり
    • 文字列の変更が最大の原因
  • やるべきでなかった?
    • メリットも大きかった

22 of 47

教訓

たくさんの破壊的変更を詰め込まず、

1つ1つの破壊的変更を慎重に

23 of 47

Python 3.6

WindowsでのUTF-8化

24 of 47

PEP 528 コンソールI/OのUTF-8化

  • 標準入出力が WindowsConsoleIO
    • ファイルのようなオブジェクト
    • UTF-8で読み書きするとコンソールAPIを呼ぶ
    • cp932以外の文字も書けるようになった
  • コンソール以外への標準入出力は�ロケールエンコーディングのまま

25 of 47

PEP 529 パスのUTF-8化

  • 文字列パスはUnicode APIを呼んでいた
    • バイト列パスはロケールエンコーディングを使うが非推奨だった
  • バイト列パスをUTF-8として扱うように
    • デコードしUnicode APIを使うようになった
    • 非推奨を解除
  • Unix用のコードを扱いやすくなった
  • ファイルシステムエンコーディングがUTF-8に

26 of 47

互換性問題

  • 標準入出力がファイルオブジェクトでなくなった
    • 例: os.dup2() を使ったリダイレクト
    • PYTHONLEGACYWINDOWSSTDIO=1 で回避
  • コードページでパスを扱っていたアプリケーション
    • 例: Mercurial
    • PYTHONLEGACYWINDOWSFSENCODING=1 で回避
  • どちらも大きな問題にはならなかった

27 of 47

Python 3.7

CロケールのUTF-8化

28 of 47

コンテナ時代の到来

  • ミニマムなLinux環境の利用拡大
  • ロケールがCになる
  • ロケールエンコーディングがASCIIになる
  • UTF-8ロケールで開発して問題なかったアプリが�動かなくなる
    • 例: ログ出力がASCIIになりエラー

29 of 47

PEP 540 UTF-8モード

  • ロケールエンコーディングをUTF-8にする
    • locale.getpreferredencoding() がUTF-8を返す
  • ロケールがCだった場合に自動で有効に

30 of 47

PEP 538 UTF-8ロケールへの矯正

  • ロケールがCだった場合 LC_CTYPE=C.UTF-8 する
  • readline等のライブラリもUTF-8を使うようになる
    • Pythonシェルで日本語が使える

31 of 47

互換性

  • PEP 540 は PYTHONUTF8=0 で無効化できる
  • PEP 538 は PYTHONCOERCECLOCALE=0 で無効化
    • 環境変数を表示するだけのツールが書けなくなった
    • Pythonビルド時に無効化も可能
  • 大きな混乱はなく、コンテナでPythonが使いやすくなった

32 of 47

教訓

  • DeprecationWarning を使いにくい変更は
    • フィードバック期間の確保する
    • 後方互換性のためのオプションを提供する

33 of 47

完全UTF-8化への挑戦 (2019~2021)

34 of 47

Windows May 2019 Update

  • メモ帳のデフォルトがUTF-8に
  • もはやテキストファイルはほぼUTF-8

35 of 47

UTF-8 Mode 再発見

  • Windows 上の Python で UTF-8 をデフォルトにする
  • ロケールエンコーディングがUTF-8になる
  • 「デフォルトでUTF-8を使う」は達成できる
  • しかし、標準ではない
    • 環境依存コードが増える

36 of 47

PEP 597 UTF-8 as default text encoding

  • UTF-8 Modeをデフォルトで有効にしない?
  • テキストファイルだけでもUTF-8をデフォルトにしない?

37 of 47

保守的な意見

  • 企業内ではまだレガシーエンコーディング利用多そう
    • ネットでどう調査しても影響を過小評価する
      • 後方互換性オプションあってもダメ?
  • Pythonのバージョンアップしたらファイル読めなくなる
    • エンコーディング指定しないでファイルを開くのが悪い
    • DeprecationWarningを出そう
      • さすがにウザすぎる

38 of 47

PEP 597 Opt-in EncodingWarning

  • opt-in で EncodingWarning を追加
    • PYTHONWARNDEFAULTENCODING=1 で有効に
  • encoding を指定していないところを調べる
  • encoding="locale" で明示的にロケールエンコーディングを使用
  • Python 3.10 で Accept

39 of 47

結果

  • 標準ライブラリを含め、多くのWarningを修正
  • ほとんどは無害(ASCIIファイルの読み書き)
  • 一部はUTF-8化すれば解消する問題
  • UTF-8化すると壊れるケースはほぼ無い

40 of 47

例: pipのログ

41 of 47

リベンジ (2022)

42 of 47

JEP 400 UTF-8 by Default

Java 18 で UTF-8 がデフォルト化!

43 of 47

PEP 686 Make UTF-8 Mode Default

  • UTF-8 Modeをデフォルト有効に
  • PYTHONUTF8=0 で無効に
  • locale.getencoding() を追加 (Python 3.11)
    • locale.getpreferredencoding() の代わり
    • UTF-8 Modeでもロケールエンコーディングを使える

44 of 47

理論武装

  • Javaがdefault charsetをUTF-8にした
  • Ruby 3.0もWindowsで外部エンコーディングをUTF-8にした
  • どっちもWarning出してない
  • PythonはEncodingWarningがある

45 of 47

結果

  • Targetを3.15 (2026)に変更
  • Accept!!

46 of 47

お願い

  • UTF-8 Mode を使ってください

PYTHONUTF8=1

  • 影響を調べる場合は

PYTHONWARNDEFAULTENCODING=1

47 of 47

Thanks