付録A — 旧 Python コードをモダンスタイルに移す
2017年スタイルの Python コード(% 文字列、has_key、type() 比較など)を現代の Python スタイルに段階的に移すガイド。旧 Python 基礎講座 21編の読者が本書へ自然に読み継げるように。
本付録は、このサイトの 旧 Python 基礎講座 21編(2017年) を読んだか、Python 2.x 〜 3.5 の時代に学んでから止まっていた人のためのマイグレーションガイドです。
本文 1 〜 33章が「最初からモダンで始める」本だとすれば、本付録は 旧コードを手に持ったまま一段階ずつモダンに移す ための案内です。一緒に把握しておけば、旧講座の資料も捨てずに更新して使えます。
旧 / 新の 1:1 対応 — 1 ページの要約 #
次の表が本付録の圧縮版です。各項目は下でペアのコードと一緒に再度見ます。
| 領域 | 旧コード(2017年前後) | 新コード(モダン) |
|---|---|---|
| 出力 | print "hello" (py2) | print("hello") |
| 文字列フォーマット | "hi %s" % name / .format() | f"hi {name}" |
| 整数除算 | 5 / 2 → 2 (py2) | 5 / 2 → 2.5 / 整数は 5 // 2 |
| dict キー確認 | d.has_key("k") | "k" in d |
| 例外捕捉 | except Exception, e: | except Exception as e: |
| 反復範囲 | xrange(10) | range(10) (すでに lazy) |
| Unicode | u"한글"、unicode 型 | そのまま "한글"、すべての str が unicode |
| 型検査 | type(x) == int | isinstance(x, int) |
| クラス boilerplate | def __init__(self, a, b): self.a=a; self.b=b | @dataclass |
| 型情報 | docstring に :type x: int | x: int アノテーション |
| コレクション型 | from typing import List + List[int] | list[int] (3.9+) |
| Option 型 | Optional[int] | `int |
| パス | os.path.join(a, b) | Path(a) / b |
| 外部コマンド | os.system("ls") / Popen | subprocess.run(["ls"]) |
| 非同期 | コールバック / generator コルーチン | async def + await |
| ツールチェーン | pip install + venv + pyenv | uv add + uv run + uv python install |
段階別の変換 #
1. print が関数になった
#
print "hello"
print "x =", xprint("hello")
print("x =", x)旧コードを Python 3 インタプリタで動かすと即座に SyntaxError。機械的変換なのでツールが一発で捕まえます(下の §「自動化」参照)。
2. 文字列フォーマット — % / .format() → f-string
#
3 世代が共存しています。新コードは無条件に f-string です。
"hi %s, you are %d" % (name, age)"hi {}, you are {}".format(name, age)
"hi {name}, you are {age}".format(name=name, age=age)f"hi {name}, you are {age}"
f"{name = }, {age = }" # デバッグ用詳しい使い方は 第2章 変数、基本型と型ヒント で。
3. 整数除算 #
5 / 2 # 2 (整数同士の除算が整数)
5.0 / 2 # 2.55 / 2 # 2.5 (常に float)
5 // 2 # 2 (整数除算は // で明示)旧コードで / だけ見て整数結果を期待していたら、Python 3 では壊れます。意図が整数なら // で明示します。
4. dict メソッド — has_key 廃止
#
if d.has_key("user"):
...if "user" in d:
...in のほうが短く、dict 以外のコレクションにも一貫して動作します。
5. 例外捕捉 — , e → as e
#
try:
do_work()
except Exception, e:
print "failed:", etry:
do_work()
except Exception as e:
print(f"failed: {e}"), 構文は Python 3 で SyntaxError。as に統一します。
加えて Python 3.11 から ExceptionGroup + except* が入りました。同時に発生した複数の例外をまとめて扱う新しい道具で、本文第6章で扱います。
6. xrange 廃止 — range がすでに lazy
#
for i in xrange(10):
...for i in range(10):
...Python 3 の range が旧 xrange の動作を吸収しました。一度にリストを作らず、lazy に値を出します。
7. Unicode — すべての str が Unicode
#
s = u"한글" # u プレフィックスが必要
isinstance(s, unicode)s = "한글" # str がそのまま Unicode
isinstance(s, str)unicode という型自体がありません。str = Unicode、bytes = バイトシーケンス。両者は自動変換されず、.encode() / .decode() で明示変換します。
8. 型検査 — type() 比較ではなく isinstance()
#
if type(x) == int:
...if isinstance(x, int):
...isinstance は継承関係まで認識します。type() 比較は正確に同じ型でしか真にならず、継承を扱えません。そしてモダン Python では、そもそも実行時の型検査より 型ヒント + 静的検証 に寄せる流れです(第2章 型チェッカを参照)。
9. クラスの boilerplate — __init__ 数十行を @dataclass 1 行に
#
class User(object):
def __init__(self, id, name, email):
self.id = id
self.name = name
self.email = email
def __repr__(self):
return "User(id=%r, name=%r, email=%r)" % (self.id, self.name, self.email)
def __eq__(self, other):
if not isinstance(other, User):
return False
return (self.id, self.name, self.email) == (other.id, other.name, other.email)from dataclasses import dataclass
@dataclass
class User:
id: int
name: str
email: str__init__、__repr__、__eq__ が自動で作られます。追加オプション(frozen、slots、kw_only)まですべて 1 行で。本文第8章 dataclass と __slots__ で詳しく扱います。
class User(object): の (object) も旧時代の名残です。Python 3 ではすべてのクラスが自動で object を継承するので省略します。
10. 型情報 — docstring からアノテーションへ #
def add(a, b):
"""
:param a: 一つ目の整数
:type a: int
:param b: 二つ目の整数
:type b: int
:rtype: int
"""
return a + bdef add(a: int, b: int) -> int:
"""一つ目と二つ目を足して返します。"""
return a + b型情報がシグネチャに上がります。IDE / 型チェッカ / ドキュメント生成器がすべて同じ情報を見ます。docstring は なぜ と どう使うか だけ書きます。
11. コレクション型 — typing import → ビルトインジェネリック
#
from typing import List, Dict, Tuple, Set
names: List[str] = []
ages: Dict[str, int] = {}
point: Tuple[float, float] = (0.0, 0.0)
unique: Set[str] = set()names: list[str] = []
ages: dict[str, int] = {}
point: tuple[float, float] = (0.0, 0.0)
unique: set[str] = set()from typing import List 自体が消えていく流れです。ビルトイン型にそのまま [] を使います。
12. Option 型 — Optional → T | None
#
from typing import Optional, Union
def find(id: int) -> Optional[str]:
...
def parse(s: str) -> Union[int, float, None]:
...def find(id: int) -> str | None:
...
def parse(s: str) -> int | float | None:
...短く、追加の import がありません。
13. パス — os.path → pathlib.Path
#
import os
config_path = os.path.join(os.path.dirname(__file__), "config", "app.toml")
with open(config_path, "r") as f:
data = f.read()from pathlib import Path
config_path = Path(__file__).parent / "config" / "app.toml"
data = config_path.read_text()Path オブジェクトは / 演算子で結合でき、.read_text() / .write_text() / .exists() / .glob() などのメソッドを持ちます。os.path の関数を覚える必要が減ります。
14. 外部コマンド — os.system / Popen → subprocess.run
#
import os
os.system("ls -la /tmp") # 結果を受け取れない、セキュリティリスクfrom subprocess import Popen, PIPE
proc = Popen(["ls", "-la", "/tmp"], stdout=PIPE, stderr=PIPE)
out, err = proc.communicate()import subprocess
result = subprocess.run(
["ls", "-la", "/tmp"],
capture_output=True,
text=True,
check=True,
)
print(result.stdout)リストで引数を渡すのが基本 — シェルインジェクションのリスクが減ります。check=True は異常終了時に例外を発生させ、capture_output=True は stdout / stderr を収集、text=True は bytes ではなく str で受け取ります。
15. 非同期 — コールバック / generator コルーチン → async def + await
#
import asyncio
@asyncio.coroutine
def fetch():
response = yield from get_url("https://example.com")
return responseasync def fetch():
response = await get_url("https://example.com")
return responseasync def / await キーワードが標準になり、@asyncio.coroutine デコレータは deprecated になりました。本文第14章 非同期入門、第18章 非同期の深さで扱います。
自動化 — すべて手で移す必要はありません #
上の変換のほとんどは 機械的 です。ツールが一度に捕まえてくれます。
pyupgrade #
uv tool install pyupgrade
pyupgrade --py312-plus path/to/file.py処理してくれる項目(一部):
% フォーマット→ f-string 変換(単純なケース)from typing import Listの削除 +List[int]→list[int]置換Optional[X]→X | NoneDict[K, V]→dict[K, V]super(ClassName, self)→super()の短縮- 旧
print文 → 関数形式 - f-string 内の
.format()整理
--py312-plus 引数は「自分のコードが Python 3.12 以上でだけ動くと仮定し、そのバージョンで使える新しい構文にすべて移せ」という意味です。本書基準であれば --py314-plus。
ruff の UP ルール — pyupgrade の ruff 統合 #
[tool.ruff.lint]
select = ["UP"] # pyupgrade ルール有効化uv run ruff check . --select UP --fixruff は pyupgrade のルールのほとんどを吸収しており、他の lint ルールと一緒に一度に回せます。pre-commit でまとめておけば、旧パターンが新たに入ってくるのを常時ブロックできます — 第30章 型チェッカ設定と CI 統合 で正式なセットアップを扱います。
自動化できないもの #
- クラス →
@dataclass変換(意図判断が必要) os.path→pathlib変換(どこでどう使われているか人の判断)- コールバック / generator コルーチン →
async def(構造変更) - docstring の型表記 → アノテーション(自動化ツールはあるが精度に限界)
上の 4 つは人が手で移すのが安全です。
旧講座 → 本書 — 推奨パス #
旧 Python 基礎講座 21編 の読者には、次の順序を勧めます。
- 本付録 A から — 旧講座時代のパターンが新しい本でどう違うかを一目で見る。
- 本書第1章 Python の始まりと uv セットアップ — ツールチェーンが完全に変わったので、環境から入れ直す。
- 本書第2章 変数、基本型と型ヒント — 最も大きな差である「型ヒント優先」を身につける。
- 本書第 3 〜 7章 — 旧講座ですでに馴染みのあるテーマ(制御フロー、コレクション、関数、例外、モジュール)をモダンな書き方で再度見る。馴染みがあるので速く読める。
- 本書 2部以降 — 旧講座になかった領域(dataclass、Protocol、非同期、FastAPI、運用)に踏み込む。
旧講座はサイトに永続的に残ります。本書に移ったあとも時々旧ページを開き直して「この表現は本書ではこう変わったんだな」と比べてみてください。
練習問題 #
旧コードのまとまり一つを丸ごとモダンスタイルに移してみます。
import os
class User(object):
def __init__(self, id, name, email):
self.id = id
self.name = name
self.email = email
def __repr__(self):
return "User(id=%r, name=%r)" % (self.id, self.name)
def load_users(path):
"""
:param path: users.txt のパス
:type path: str
:rtype: List[User]
"""
full = os.path.join(os.path.dirname(__file__), path)
users = []
f = open(full, "r")
try:
for line in f:
parts = line.strip().split(",")
if len(parts) != 3:
continue
id_str, name, email = parts
users.append(User(int(id_str), name, email))
finally:
f.close()
return users- 上のコードを本付録の変換表どおりにモダンスタイルに移してください。ヒント:
@dataclass、型アノテーション、pathlib.Path、with open(...) as f:、list[User]、f-string。 - 移したコードを
pyrightで検査し、エラーがないか確認してください。 pyupgrade --py314-plus old_user.pyを回してみて、ツールが自動で処理した部分と、人の手が必要だった部分を比べてみてください。
一行まとめ: 旧 Python コードのほとんどの変換は機械的なので
pyupgradeとruff --select UPが捕まえてくれます。人の手が必要なのは@dataclass、pathlib、async/await、docstring 型くらいです。旧講座 → 本書の推奨パスは付録 A → 第1章 → 第2章 → 第 3 〜 7章を速読 → 2部以降。