Python 言語仕様ガイド

他言語の経験者が 2〜3 時間でざっと読み通せるリファレンスです。Python 固有の注意点は「要注意」ボックスで強調しています。

Python 3.12経験者向けコード例付き
01

変数とデータ型

基礎
変数の宣言と基本型基礎

型宣言は不要。= で代入するだけで変数が作られ、型は代入値から自動決定される(動的型付け)。type() で型を確認できる。

PYTHON
name    = "Alice"    # str
age     = 30         # int
height  = 1.68       # float
is_admin = True      # bool(True / False は大文字始まり)
nothing  = None      # NoneType

print(type(age))     # 
print(type(nothing)) #
<class 'int'> <class 'NoneType'>
None の扱い基礎

他言語の null / nil に相当。None は言語全体で唯一のオブジェクトであるため、等値比較ではなく同一性比較 is None を使うのが慣習。

x == None は動作するが非推奨。x is None / x is not None を使う。__eq__ をオーバーライドしたオブジェクトでは == が意図しない結果を返す可能性があるため。
型変換(キャスト)基礎

組み込み関数で型を相互変換する。文字列→数値の変換が失敗すると ValueError が発生する。

PYTHON
int("42")        # → 42
float("3.14")    # → 3.14
str(100)         # → "100"
bool(0)          # → False  ← 0, "", None, [], {} は False
bool("hello")    # → True   ← 上記以外はほぼ True
f-string(文字列補間)基礎

Python 3.6+ の推奨フォーマット方法。f"...{式}..." の形式で変数や式をそのまま埋め込める。: で書式指定も可能。

PYTHON
name  = "Bob"
score = 87.456

print(f"名前: {name}")
print(f"スコア: {score:.2f}")    # 小数2桁
print(f"合否: {'合格' if score >= 60 else '不合格'}")
print(f"{score = }")             # Python 3.8+: デバッグ用(変数名も表示)
名前: Bob スコア: 87.46 合否: 合格 score = 87.456
主要な文字列メソッド基礎

文字列はイミュータブル(変更不可)。メソッドはすべて新しい文字列を返す。

PYTHON
s = "  Hello, World!  "

s.strip()               # "Hello, World!"(両端の空白除去)
s.lower()               # "  hello, world!  "
"hello".upper()         # "HELLO"
"a,b,c".split(",")      # ["a", "b", "c"]
"-".join(["a","b","c"]) # "a-b-c"
"hello".replace("l","L")# "heLLo"
"hello".startswith("he")# True
"hello".find("ll")      # 2(見つからない場合 -1)
数値リテラルと複素数基礎

整数は任意精度(桁あふれなし)。アンダースコアで桁区切りができる。複素数は j サフィックスで表現。

PYTHON
1_000_000     # 1000000(可読性のための区切り)
0b1010        # 10(2進数)
0o17          # 15(8進数)
0xFF          # 255(16進数)
3 + 4j        # 複素数  → (3+4j)
(3+4j).real   # 3.0
(3+4j).imag   # 4.0
定数の慣習基礎

Python に定数構文はない。慣習として大文字スネークケースで記述し、変更しないことを表す。

PYTHON
MAX_RETRY  = 3
BASE_URL   = "https://example.com"
PI         = 3.14159265358979
02

演算子

基礎
算術演算子基礎

/ は常に float を返す点に注意。整数除算は //、余りは %、累乗は **

PYTHON
10 / 3    # 3.3333...(float)
10 // 3   # 3        (切り捨て除算)
10 % 3    # 1        (余り)
2 ** 10   # 1024     (累乗)
-7 // 2   # -4       ← 負数は「小さい方向」に切り捨て(注意)
-7 % 2    # 1        ← 符号は除数に合わせる(C/Java と異なる)
負数の //% はゼロ方向でなく負の無限大方向に切り捨てる。-7 // 2-4(C/Java の -3 とは異なる)。
比較演算子・連鎖比較基礎

比較演算子は連鎖して書ける(多くの言語にない Python の特徴)。

PYTHON
x = 5
1 < x < 10      # True(連鎖比較)
1 < x and x < 10 # 上と同じ意味だが冗長

"abc" == "abc"  # True
"abc" != "ABC"  # True
論理演算子基礎

&& / || ではなく and / or / not キーワードを使う。短絡評価(ショートサーキット)が適用される。

PYTHON
True and False  # False
True or False   # True
not True        # False

# 短絡評価 → 値を返す(bool に変換しない)
0 or "default"   # "default"(0 は falsy なので右辺を返す)
"hi" or "default"# "hi"    ("hi" は truthy なので左辺を返す)
None and do_something()  # do_something() は呼ばれない
or / andTrue/False ではなく「短絡した側のオペランド自体」を返す。x = value or default というパターンはよく使われるが、value0"" のときも default に置き換わるため注意。
is vs ==(同一性と等値性)基礎

== は値が等しいか(__eq__)、is はメモリ上の同一オブジェクトかを比較する。

PYTHON
a = [1, 2, 3]
b = [1, 2, 3]
c = a

a == b   # True  (値が同じ)
a is b   # False (別オブジェクト)
a is c   # True  (同じオブジェクトを参照)

# 小さい整数(-5〜256)はキャッシュされるため is が True になることがある
# → 数値・文字列の比較に is を使うのは NG
is を使っていい場面は NoneTrueFalse との比較のみ。数値や文字列の比較に is を使うと、実装依存のキャッシュ挙動に頼ることになり信頼性がない。
in / not in(帰属演算子)基礎

リスト・文字列・辞書などに要素が含まれるかを確認する。辞書では「キー」を検索する。

PYTHON
"py" in "python"         # True
3 in [1, 2, 3, 4]        # True
"key" in {"key": 1}      # True(辞書はキーを検索)
"key" not in {"x": 1}    # True
条件式(三項演算子)基礎

他言語の 条件 ? 真 : 偽 に相当。Python では 真の値 if 条件 else 偽の値 の語順になる。

PYTHON
x = 10
label = "正" if x > 0 else "非正"
print(label)  # "正"
ウォルラス演算子:=(Python 3.8+)応用

式の中で変数へ代入しつつ値を返す「代入式」。while の条件や内包表記で中間値を再利用するときに便利。

PYTHON
import re

# 通常の書き方
m = re.search(r"\d+", "abc123")
if m:
    print(m.group())

# ウォルラス演算子で1行に
if m := re.search(r"\d+", "abc123"):
    print(m.group())   # "123"
ビット演算子応用

整数のビット操作。他言語と同様だが、Python の整数は任意精度なので符号付き固定幅ビットの概念はない。

PYTHON
0b1100 & 0b1010  # 0b1000 = 8  (AND)
0b1100 | 0b1010  # 0b1110 = 14 (OR)
0b1100 ^ 0b1010  # 0b0110 = 6  (XOR)
~0b1100          # -13         (NOT:-(n+1))
1 << 4           # 16          (左シフト)
16 >> 2          # 4           (右シフト)
03

制御フロー

基礎
if / elif / else基礎

ブロックは {} ではなくインデント(スペース4つが慣習)で表現する。else if ではなく elif

PYTHON
score = 72

if score >= 90:
    grade = "優"
elif score >= 70:
    grade = "良"
elif score >= 50:
    grade = "可"
else:
    grade = "不可"

print(grade)  # 良
インデントの深さがブロックを決定する。タブとスペースを混在させると TabError になる。プロジェクト全体でスペース4つに統一するのが標準(PEP 8)。
for ループ・range・enumerate基礎

任意のイテラブルを反復する。C 系のインデックスループより for x in collection が基本スタイル。インデックスが必要なら enumerate() を使う。

PYTHON
fruits = ["apple", "banana", "cherry"]

for fruit in fruits:
    print(fruit)

# インデックス付き(start で開始番号を変更可)
for i, fruit in enumerate(fruits, start=1):
    print(f"{i}: {fruit}")

# 数値ループ:range(start, stop, step)
for n in range(0, 10, 2):
    print(n, end=" ")  # 0 2 4 6 8
apple banana cherry 1: apple 2: banana 3: cherry 0 2 4 6 8
while / break / continue基礎

条件が True の間ループを続ける。break で即時終了、continue で次の反復へスキップ。

PYTHON
n = 0
while n < 10:
    n += 1
    if n % 2 == 0:
        continue    # 偶数はスキップ
    if n > 7:
        break       # 7 を超えたら終了
    print(n, end=" ")  # 1 3 5 7
1 3 5 7
for / while の else 節基礎

Python 固有の構文。ループが break されずに正常終了したときだけ else ブロックが実行される。「検索して見つからなかった場合」の処理に使える。

PYTHON
target = 7
for n in [1, 3, 5, 9]:
    if n == target:
        print("見つかった")
        break
else:
    print("見つからなかった")  # break されなかったので実行される
見つからなかった
pass 文基礎

何もしないプレースホルダ。Python ではブロックを空にできないため、スタブや一時的な空実装に使う。

PYTHON
def todo_later():
    pass   # あとで実装する

class EmptyClass:
    pass
match / case(Python 3.10+)応用

構造的パターンマッチング。値だけでなくオブジェクトの構造・型にも対応できる点が他言語の switch と異なる。

PYTHON
point = (0, 5)

match point:
    case (0, 0):
        print("原点")
    case (0, y):              # y に値をキャプチャ
        print(f"Y軸上: y={y}")
    case (x, 0):
        print(f"X軸上: x={x}")
    case (x, y):
        print(f"一般点: ({x}, {y})")
    case _:
        print("不明")
Y軸上: y=5
04

関数

基礎
def・引数・return基礎

def で関数を定義。デフォルト引数・キーワード引数が使える。return を省略すると None が返る。複数値は tuple で返す。

PYTHON
def greet(name, greeting="こんにちは"):
    return f"{greeting}{name}さん!"

print(greet("Alice"))
print(greet("Bob", greeting="おはよう"))

# 複数値を返す(実体は tuple)
def minmax(nums):
    return min(nums), max(nums)

lo, hi = minmax([3, 1, 4, 1, 5, 9])
print(lo, hi)  # 1 9
こんにちは、Aliceさん! おはよう、Bobさん! 1 9
デフォルト引数にリスト・辞書などのミュータブルな値を使うと、呼び出し間で共有されてしまう。 def f(lst=[]): # NG — 全呼び出しで同じリストを使い回す def f(lst=None): lst = lst if lst is not None else [] # OK
*args / **kwargs基礎

*args は任意個の位置引数を tuple で、**kwargs は任意個のキーワード引数を dict で受け取る。呼び出し側では * / ** でコレクションを展開できる。

PYTHON
def total(*nums):
    return sum(nums)

print(total(1, 2, 3, 4))   # 10

nums = [10, 20, 30]
print(total(*nums))         # 60(リストを展開して渡す)

def show(**info):
    for k, v in info.items():
        print(f"  {k}: {v}")

show(name="Carol", age=28)
d = {"city": "Tokyo", "lang": "Python"}
show(**d)                   # 辞書を展開して渡す
10 60 name: Carol age: 28 city: Tokyo lang: Python
位置専用 / キーワード専用引数基礎

/ より前は位置引数専用(キーワード指定不可)、* より後はキーワード引数専用(位置指定不可)。標準ライブラリでよく使われるパターン。

PYTHON
def process(x, y, /, *, verbose=False):
    # x, y は位置専用、verbose はキーワード専用
    if verbose:
        print(f"x={x}, y={y}")
    return x + y

process(1, 2, verbose=True)   # OK
# process(x=1, y=2)           # NG: x は位置専用
# process(1, 2, True)          # NG: verbose はキーワード専用
x=1, y=2
スコープ:global / nonlocal基礎

変数は LEGB 順(Local → Enclosing → Global → Built-in)で解決される。関数内から外側の変数を再代入したい場合は global / nonlocal が必要。

PYTHON
count = 0

def increment():
    global count   # グローバル変数を再代入する宣言
    count += 1

increment()
increment()
print(count)  # 2

# nonlocal: クロージャ内で外側(enclosing)の変数を再代入
def make_counter():
    n = 0
    def counter():
        nonlocal n
        n += 1
        return n
    return counter

c = make_counter()
print(c(), c(), c())  # 1 2 3
2 1 2 3
関数内で変数を参照するだけなら宣言不要。ただし同一スコープ内で代入が一度でもあると「ローカル変数」とみなされ、代入前に参照すると UnboundLocalError になる。
再帰関数基礎

Python のデフォルト再帰上限は 1000 回。sys.setrecursionlimit() で変更できるが、深い再帰はスタックオーバーフローになりやすいため、ループや functools.lru_cache との組み合わせを検討する。

PYTHON
def factorial(n: int) -> int:
    if n <= 1:
        return 1
    return n * factorial(n - 1)

print(factorial(10))  # 3628800
3628800
lambda(無名関数)応用

1式のみ書ける簡易関数。sorted() / map() / filter() のキー関数として使うことが多い。複雑な処理は通常の def で書くのが可読性上望ましい。

PYTHON
double = lambda x: x * 2
print(double(7))   # 14

words = ["banana", "fig", "apple", "date"]
print(sorted(words, key=lambda w: len(w)))
# ['fig', 'date', 'apple', 'banana']

nums = [1, -2, 3, -4, 5]
print(list(filter(lambda x: x > 0, nums)))  # [1, 3, 5]
print(list(map(lambda x: x ** 2, nums)))    # [1, 4, 9, 16, 25]
14 ['fig', 'date', 'apple', 'banana'] [1, 3, 5] [1, 4, 9, 16, 25]
クロージャ応用

関数が定義された時点の外側スコープの変数を「閉じ込めて」持ち続ける関数。状態を持つ軽量なオブジェクトの代替として使える。

PYTHON
def make_multiplier(factor):
    def multiply(x):
        return x * factor   # factor を閉じ込めている
    return multiply

double = make_multiplier(2)
triple = make_multiplier(3)

print(double(5))  # 10
print(triple(5))  # 15
10 15
型ヒント(Type Hints)応用

Python 3.5+ で利用可能。実行時には無視されるが、IDE 補完・mypy などの静的解析に活用される。Python 3.9+ では list[str] など組み込み型を直接使える。

PYTHON
def add(a: int, b: int) -> int:
    return a + b

def greet_all(names: list[str]) -> None:
    for name in names:
        print(f"Hello, {name}")

# None を返す可能性がある場合
def find(items: list[int], val: int) -> int | None:
    for i, x in enumerate(items):
        if x == val:
            return i
    return None
functools.lru_cache(メモ化)応用

同じ引数での計算結果をキャッシュし、再計算をスキップする。再帰アルゴリズムの高速化に効果的。Python 3.9+ の @cache は無制限キャッシュの簡略版。

PYTHON
from functools import lru_cache

@lru_cache(maxsize=None)
def fib(n: int) -> int:
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

print(fib(50))   # キャッシュなしでは指数時間かかる
print(fib.cache_info())  # キャッシュの状態を確認
12586269025 CacheInfo(hits=48, misses=51, maxsize=None, currsize=51)
05

コレクション

基礎
list(リスト)基礎

順序付き・変更可能なコレクション。任意の型を混在できる。インデックスは 0 始まりで、負のインデックスは末尾から数える。

PYTHON
nums = [3, 1, 4, 1, 5, 9]

nums.append(2)          # 末尾に追加
nums.insert(0, 0)       # 先頭に挿入
nums.pop()              # 末尾を削除して返す
nums.pop(0)             # インデックス0を削除して返す
nums.remove(1)          # 値が 1 の最初の要素を削除
nums.sort()             # 破壊的ソート(元のリストを変更)
sorted(nums)            # 非破壊的ソート(新しいリストを返す)
len(nums)               # 要素数
nums.count(1)           # 値 1 の個数

# スライス [start:stop:step](stop は含まない)
a = [0, 1, 2, 3, 4, 5]
a[1:4]    # [1, 2, 3]
a[::2]    # [0, 2, 4](1つおき)
a[::-1]   # [5, 4, 3, 2, 1, 0](逆順)
スライス a[:] はシャローコピーを返す(新しいリストだが、要素はオリジナルと同じオブジェクトを参照)。ネストしたリストを完全にコピーするには copy.deepcopy() を使う。 また、リストの代入 b = a はコピーではなく同じオブジェクトへの参照の共有。b.append(x) すると a も変わる。
dict(辞書)基礎

キーと値のペア。Python 3.7+ では挿入順が保証される。キーはイミュータブルな型(str・int・tuple など)に限定される。

PYTHON
person = {"name": "Dave", "age": 25}

person["city"] = "Osaka"         # 追加・更新
del person["age"]                # 削除

person.get("email")              # None(存在しないキーでも安全)
person.get("email", "未設定")    # "未設定"(デフォルト値)
person.setdefault("lang", "Python")  # なければ追加、あれば何もしない

for key, val in person.items():  # キーと値を同時取得
    print(f"{key}: {val}")

# dict のマージ(Python 3.9+)
d1 = {"a": 1}
d2 = {"b": 2}
merged = d1 | d2                 # {"a": 1, "b": 2}
d1 |= d2                         # d1 を上書き更新
tuple(タプル)基礎

順序付き・変更不可(イミュータブル)なコレクション。辞書のキーや関数の複数戻り値に使われる。要素が1つの場合はカンマが必須。

PYTHON
point = (3, 5)
x, y = point              # アンパック
print(x, y)               # 3 5

single = (42,)            # 要素1つの tuple はカンマ必須
not_tuple = (42)          # これは int

# ネストしたアンパック
(a, b), c = (1, 2), 3
print(a, b, c)            # 1 2 3

# *rest でスプラット
first, *rest = (1, 2, 3, 4, 5)
print(first, rest)        # 1 [2, 3, 4, 5]
3 5 1 2 3 1 [2, 3, 4, 5]
set / frozenset(集合)基礎

重複なし・順序なしのコレクション。in による検索が O(1) と高速。frozenset はイミュータブルなセット(辞書キーにも使える)。

PYTHON
tags_a = {"python", "web", "ai"}
tags_b = {"python", "data", "ai"}

tags_a | tags_b    # 和集合:{"python","web","ai","data"}
tags_a & tags_b    # 積集合:{"python","ai"}
tags_a - tags_b    # 差集合:{"web"}(a にだけある)
tags_a ^ tags_b    # 対称差:{"web","data"}(どちらか一方にだけある)

# 重複除去
nums = [1, 2, 2, 3, 3, 3]
unique = list(set(nums))   # [1, 2, 3](順序は不定)
zip / zip_longest基礎

複数のイテラブルを並列に反復する。zip は短い方に合わせて終了。長い方に合わせたい場合は itertools.zip_longest を使う。

PYTHON
names  = ["Alice", "Bob", "Carol"]
scores = [85, 92, 78]

for name, score in zip(names, scores):
    print(f"{name}: {score}")

# dict に変換
mapping = dict(zip(names, scores))
# {"Alice": 85, "Bob": 92, "Carol": 78}

# アンパックで転置
pairs = [(1, "a"), (2, "b"), (3, "c")]
nums, letters = zip(*pairs)
print(nums)     # (1, 2, 3)
print(letters)  # ('a', 'b', 'c')
Alice: 85 Bob: 92 Carol: 78
collections モジュール応用

標準ライブラリの特殊コレクション。よく使うパターンを簡潔に書ける。

PYTHON
from collections import Counter, defaultdict, deque, namedtuple

# Counter: 頻度カウント
c = Counter("mississippi")
print(c.most_common(3))   # [('s', 4), ('i', 4), ('p', 2)]

# defaultdict: キーがなくてもデフォルト値を返す
dd = defaultdict(list)
dd["fruits"].append("apple")   # KeyError が起きない

# deque: 両端キュー(appendleft / popleft が O(1))
dq = deque([1, 2, 3])
dq.appendleft(0)   # [0, 1, 2, 3]
dq.popleft()       # 0

# namedtuple: フィールド名付き tuple
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 5)
print(p.x, p.y)    # 3 5
[('s', 4), ('i', 4), ('p', 2)] 3 5
06

内包表記

基礎
リスト内包表記基礎

[式 for 変数 in イテラブル if 条件] の形でリストを生成する。map() + filter() の組み合わせより可読性が高い。

PYTHON
# 基本
squares = [x**2 for x in range(1, 6)]
# [1, 4, 9, 16, 25]

# フィルタ付き
evens = [x for x in range(10) if x % 2 == 0]
# [0, 2, 4, 6, 8]

# ネストループ(行列の平坦化)
matrix = [[1, 2], [3, 4], [5, 6]]
flat = [x for row in matrix for x in row]
# [1, 2, 3, 4, 5, 6]

# 条件分岐を含む式(三項演算子)
labels = ["偶数" if x % 2 == 0 else "奇数" for x in range(5)]
# ['偶数', '奇数', '偶数', '奇数', '偶数']
辞書・集合の内包表記基礎

辞書は {キー: 値 for ...}、集合は {式 for ...}。どちらも [] ではなく {} を使う点に注意。

PYTHON
# 辞書内包表記
words = ["apple", "fig", "mango"]
word_len = {w: len(w) for w in words}
# {"apple": 5, "fig": 3, "mango": 5}

# 辞書の反転(値→キー)
original = {"a": 1, "b": 2, "c": 3}
inverted = {v: k for k, v in original.items()}
# {1: "a", 2: "b", 3: "c"}

# 集合内包表記
unique_lens = {len(w) for w in words}
# {3, 5}(重複なし)
ジェネレータ式応用

(式 for ...) の形で、値を一度にすべてメモリに展開せず必要なときに1つずつ生成する。sum() / max() など集約関数に直接渡せるため、大きなデータで特に有効。

PYTHON
# リスト内包表記:全件をメモリに展開
total_list = sum([x**2 for x in range(1_000_000)])

# ジェネレータ式:メモリ使用量が大幅に少ない
total_gen  = sum(x**2 for x in range(1_000_000))

print(total_gen)  # 333332833333500000
333332833333500000
ジェネレータは一度しか消費できない。2回目にイテレートしても空になる。複数回使いたい場合はリストに変換するか、毎回ジェネレータを作り直す。
ネストした内包表記の読み方応用

複数の for を並べると多重ループになる。左から右の順で外側→内側のループに対応する。

PYTHON
# デカルト積(全組み合わせ)
colors = ["red", "blue"]
sizes  = ["S", "M", "L"]
items  = [(c, s) for c in colors for s in sizes]
# [('red','S'),('red','M'),('red','L'),('blue','S'),...]

# 等価な for ループ
items2 = []
for c in colors:
    for s in sizes:        # ← 右の for が内側のループ
        items2.append((c, s))
07

クラスと継承

応用
class / __init__ / self応用

class でクラスを定義し、__init__ がコンストラクタ。インスタンスメソッドの第1引数には慣習的に self を使う(言語仕様ではなく慣習)。

PYTHON
class Animal:
    # クラス変数(全インスタンスで共有)
    kingdom = "Animalia"

    def __init__(self, name: str, sound: str):
        # インスタンス変数(インスタンスごとに独立)
        self.name  = name
        self.sound = sound

    def speak(self) -> str:
        return f"{self.name} が「{self.sound}」と鳴く"

    def __repr__(self) -> str:          # print / repr() で使われる
        return f"Animal({self.name!r})"

    def __str__(self) -> str:           # str() / f-string で使われる
        return self.name

dog = Animal("Pochi", "ワン")
print(dog.speak())
print(repr(dog))
print(Animal.kingdom)    # クラス変数にはクラス名でアクセス
Pochi が「ワン」と鳴く Animal('Pochi') Animalia
クラス変数はすべてのインスタンスで共有される。インスタンス側で同名変数に代入すると「インスタンス変数」として隠蔽されるが、リスト・辞書などのミュータブルなクラス変数に .append() 等を呼ぶと全インスタンスに影響する。 class Foo:    items = [] # ← NG: 全インスタンスで共有されてしまう    def __init__(self):        self.items = [] # ← OK: インスタンス変数として定義
継承と super()応用

クラス定義の () に親クラスを指定して継承する。super() で親クラスのメソッドを呼び出せる。isinstance() で継承関係を確認できる。

PYTHON
class Dog(Animal):
    def __init__(self, name: str, breed: str):
        super().__init__(name, "ワン")   # 親の __init__ を呼ぶ
        self.breed = breed

    def speak(self) -> str:             # オーバーライド
        base = super().speak()          # 親メソッドを呼びつつ拡張
        return f"{base}{self.breed})"

shiba = Dog("Hachi", "柴犬")
print(shiba.speak())
print(isinstance(shiba, Dog))     # True
print(isinstance(shiba, Animal))  # True(継承関係も True)
print(type(shiba) is Animal)      # False(厳密な型比較)
Hachi が「ワン」と鳴く(柴犬) True True False
@property / setter応用

属性アクセスの形式を保ちながら、取得・設定時にロジックを挟める。Java の getter/setter より簡潔に書ける。

PYTHON
class Temperature:
    def __init__(self, celsius: float):
        self._celsius = celsius

    @property
    def celsius(self) -> float:
        return self._celsius

    @celsius.setter
    def celsius(self, value: float):
        if value < -273.15:
            raise ValueError("絶対零度を下回れません")
        self._celsius = value

    @property
    def fahrenheit(self) -> float:       # 読み取り専用プロパティ
        return self._celsius * 9/5 + 32

t = Temperature(100)
print(t.fahrenheit)   # 212.0
t.celsius = 0
print(t.fahrenheit)   # 32.0
212.0 32.0
@staticmethod / @classmethod応用

@staticmethodself/cls を受け取らない純粋な関数。@classmethod はクラス自体(cls)を受け取り、代替コンストラクタとして使われることが多い。

PYTHON
class Circle:
    PI = 3.14159

    def __init__(self, radius: float):
        self.radius = radius

    @staticmethod
    def is_valid_radius(r) -> bool:     # クラス・インスタンス不要な処理
        return r > 0

    @classmethod
    def unit(cls) -> "Circle":          # 代替コンストラクタ
        return cls(1.0)

    def area(self) -> float:
        return self.PI * self.radius ** 2

print(Circle.is_valid_radius(-1))  # False
c = Circle.unit()
print(c.area())                    # 3.14159
False 3.14159
@dataclass(Python 3.7+)応用

__init__ / __repr__ / __eq__ を自動生成する。frozen=True で変更不可(イミュータブル)にもできる。

PYTHON
from dataclasses import dataclass, field

@dataclass
class Point:
    x: float
    y: float
    label: str = "point"   # デフォルト値

@dataclass(frozen=True)    # イミュータブル・hashable になる
class Color:
    r: int
    g: int
    b: int

p = Point(1.0, 2.5)
print(p)                    # Point(x=1.0, y=2.5, label='point')
print(p == Point(1.0, 2.5)) # True(__eq__ 自動生成)

# ミュータブルなデフォルト値は field(default_factory=...) を使う
@dataclass
class Bag:
    items: list = field(default_factory=list)
Point(x=1.0, y=2.5, label='point') True
抽象クラス(ABC)応用

他言語の interface / abstract class に相当。@abstractmethod を持つクラスは直接インスタンス化できず、サブクラスでの実装を強制する。

PYTHON
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self) -> float: ...

    @abstractmethod
    def perimeter(self) -> float: ...

class Rectangle(Shape):
    def __init__(self, w: float, h: float):
        self.w, self.h = w, h

    def area(self)      -> float: return self.w * self.h
    def perimeter(self) -> float: return 2 * (self.w + self.h)

# Shape()  # → TypeError: 抽象クラスはインスタンス化できない
r = Rectangle(3, 4)
print(r.area(), r.perimeter())   # 12  14
12 14.0
主要なダンダーメソッド応用

__xxx__ という名前の特殊メソッド。演算子や組み込み関数の振る舞いをカスタマイズできる。

PYTHON
class Vector:
    def __init__(self, x, y):
        self.x, self.y = x, y

    def __repr__(self):
        return f"Vector({self.x}, {self.y})"

    def __add__(self, other):           # v1 + v2
        return Vector(self.x + other.x, self.y + other.y)

    def __mul__(self, scalar):          # v * 3
        return Vector(self.x * scalar, self.y * scalar)

    def __len__(self):                  # len(v)
        return 2

    def __eq__(self, other):            # v1 == v2
        return self.x == other.x and self.y == other.y

v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2)    # Vector(4, 6)
print(v1 * 3)     # Vector(3, 6)
print(len(v1))    # 2
Vector(4, 6) Vector(3, 6) 2
08

例外処理

応用
try / except / else / finally応用

else 節は例外が発生しなかった場合のみ実行される(Python 固有)。finally は例外の有無に関わらず必ず実行される。

PYTHON
def safe_divide(a, b):
    try:
        result = a / b
    except ZeroDivisionError:
        print("ゼロ除算エラー")
        return None
    except TypeError as e:
        print(f"型エラー: {e}")
        return None
    else:
        print("計算成功")   # 例外がなかった場合のみ実行
        return result
    finally:
        print("--- 終了 ---")  # 常に実行

safe_divide(10, 2)
print("---")
safe_divide(10, 0)
計算成功 --- 終了 --- --- ゼロ除算エラー --- 終了 ---
except Exception で広く捕捉するのは一般的に避けるべき。KeyboardInterruptSystemExit など、プログラム終了に関わる例外まで飲み込んでしまうことがある。捕捉する例外は具体的な型を指定する。
主要な組み込み例外応用

よく遭遇する組み込み例外の一覧。継承関係は BaseException → Exception → より具体的な例外 の順。

PYTHON
int("abc")        # ValueError:    値が不正
1 + "a"           # TypeError:     型が不適切
{}["x"]           # KeyError:      辞書キーが存在しない
[][0]             # IndexError:    リストの範囲外
None.upper()      # AttributeError: 属性が存在しない
open("no.txt")    # FileNotFoundError
1 / 0             # ZeroDivisionError
int("1" * 1000)   # OverflowError(float のみ。int は無制限)
raise・カスタム例外・raise from応用

Exception を継承して独自の例外クラスを作れる。raise ... from で元の例外を連鎖させると、デバッグ時にコンテキストが保たれる。

PYTHON
class ValidationError(ValueError):
    def __init__(self, field: str, message: str):
        super().__init__(f"{field}: {message}")
        self.field = field

def set_age(age: int) -> None:
    if not isinstance(age, int):
        raise TypeError(f"int が必要です: {type(age)}")
    if age < 0 or age > 150:
        raise ValidationError("age", f"範囲外の値: {age}")

try:
    set_age(-1)
except ValidationError as e:
    print(f"バリデーションエラー: {e}")
    print(f"フィールド: {e.field}")

# raise from: 例外を変換しつつ元の例外を保持
try:
    int("abc")
except ValueError as e:
    raise RuntimeError("設定ファイルの読み込みに失敗") from e
バリデーションエラー: age: 範囲外の値: -1 フィールド: age
with 文・コンテキストマネージャ応用

ブロックの開始・終了に処理を自動実行する仕組み。ファイルのクローズ・ロックの解放など、リソースの後処理を確実に行うために使う。finally を書かなくて済む。

PYTHON
# ファイル操作(with を抜けると自動でクローズ)
with open("data.txt", "w", encoding="utf-8") as f:
    f.write("Hello\n")
    f.write("World\n")
# ここで f は閉じられている

# 複数リソースを同時に管理
with open("src.txt") as src, open("dst.txt", "w") as dst:
    dst.write(src.read())

# contextlib で独自コンテキストマネージャを作る
from contextlib import contextmanager

@contextmanager
def timer(label: str):
    import time
    start = time.perf_counter()
    yield                              # with ブロックの本体がここで実行
    elapsed = time.perf_counter() - start
    print(f"{label}: {elapsed:.4f}s")

with timer("処理"):
    sum(range(1_000_000))
09

ファイル・入出力

基礎
open() でファイルを読み書きする基礎

open(path, mode, encoding) でファイルを開く。必ず with 文を使うとクローズ忘れを防げる。テキストモードのデフォルトエンコーディングはOSに依存するため、明示的に encoding="utf-8" を指定するのが安全。

PYTHON
# 書き込み(上書き)
with open("sample.txt", "w", encoding="utf-8") as f:
    f.write("1行目\n")
    f.writelines(["2行目\n", "3行目\n"])

# 読み込み(全体)
with open("sample.txt", "r", encoding="utf-8") as f:
    content = f.read()          # 全テキストを文字列で返す

# 行ごとに読む
with open("sample.txt", encoding="utf-8") as f:
    for line in f:              # イテレータで1行ずつ(メモリ効率が良い)
        print(line.rstrip())

# 追記モード
with open("sample.txt", "a", encoding="utf-8") as f:
    f.write("4行目\n")

# バイナリ読み込み
with open("image.png", "rb") as f:
    data = f.read()             # bytes オブジェクト
Windowsのデフォルトエンコーディングは cp932(Shift-JIS系)。encoding を省略すると環境によってファイルが文字化けする。クロスプラットフォームのコードでは常に encoding="utf-8" を明示する。
pathlib.Path(Python 3.4+)応用

ファイルパスをオブジェクトとして扱う。OS差を吸収し、文字列の手動結合(os.path.join)より直感的に書ける。

PYTHON
from pathlib import Path

p = Path("data") / "subdir" / "file.txt"   # / 演算子でパス結合
print(p)              # data/subdir/file.txt
print(p.stem)         # "file"(拡張子なし名前)
print(p.suffix)       # ".txt"
print(p.parent)       # data/subdir

p.parent.mkdir(parents=True, exist_ok=True)  # ディレクトリ作成
p.write_text("hello", encoding="utf-8")      # テキスト書き込み
print(p.read_text(encoding="utf-8"))         # テキスト読み込み
p.exists()            # True / False
p.is_file()           # True / False

# glob でファイル一覧
for py_file in Path(".").glob("**/*.py"):    # 再帰的に .py を列挙
    print(py_file)
json モジュール基礎

Python オブジェクト ↔ JSON 文字列の変換。json.dumps / json.loads は文字列、json.dump / json.load はファイルオブジェクトを扱う。

PYTHON
import json

data = {"name": "Alice", "scores": [85, 92, 78], "active": True}

# Python → JSON 文字列
s = json.dumps(data, ensure_ascii=False, indent=2)
print(s)

# JSON 文字列 → Python
obj = json.loads(s)
print(obj["name"])    # "Alice"

# ファイルに保存 / ファイルから読む
with open("data.json", "w", encoding="utf-8") as f:
    json.dump(data, f, ensure_ascii=False, indent=2)

with open("data.json", encoding="utf-8") as f:
    loaded = json.load(f)
csv モジュール基礎

csv.DictReader / DictWriter を使うとヘッダー行を辞書のキーとして扱えるため可読性が高い。

PYTHON
import csv

# CSV 書き込み
rows = [{"name": "Alice", "score": 85}, {"name": "Bob", "score": 92}]
with open("scores.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.DictWriter(f, fieldnames=["name", "score"])
    writer.writeheader()
    writer.writerows(rows)

# CSV 読み込み
with open("scores.csv", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    for row in reader:
        print(row["name"], row["score"])
Alice 85 Bob 92
10

モジュール・パッケージ

基礎
import / from ... import / as基礎

モジュール全体を読み込む import、特定の名前だけ取り込む from ... import、エイリアスを付ける as の3パターン。

PYTHON
import math                       # モジュール全体
print(math.sqrt(16))              # 4.0

from math import sqrt, pi         # 特定の名前だけ
print(sqrt(16), pi)               # 4.0  3.141...

import numpy as np                # エイリアス(慣習的な短縮名)
import pandas as pd

from os.path import join, exists  # サブモジュールから取り込み

# ワイルドカードインポートは避ける(名前衝突の原因になる)
# from math import *  ← NG
__name__ == "__main__"基礎

ファイルが直接実行された場合のみ __name__"__main__" になる。モジュールとしてインポートされた際はモジュール名になる。スクリプトのエントリポイントとして使う定番パターン。

PYTHON
# mymodule.py
def greet(name: str) -> str:
    return f"Hello, {name}!"

if __name__ == "__main__":
    # python mymodule.py で直接実行した場合のみ動く
    print(greet("World"))
    # import mymodule した場合はここは実行されない
パッケージと __init__.py応用

複数のモジュールをディレクトリでまとめたものがパッケージ。Python 3.3+ では __init__.py がなくても(namespace package として)動くが、明示的に置くのが一般的。

PYTHON
# ディレクトリ構造
# myapp/
# ├── __init__.py      ← パッケージであることを示す
# ├── utils.py
# └── models/
#     ├── __init__.py
#     └── user.py

# myapp/__init__.py でよく使う名前を再エクスポート
from .utils import helper        # 相対インポート(.= 同じパッケージ)
from .models.user import User

# 使う側
from myapp import User           # __init__.py で公開した名前を直接使える
from myapp.utils import helper   # 完全パスでのインポートも常に OK
よく使う標準ライブラリ基礎

インストール不要で使えるバッテリー同梱ライブラリの主要どころ。

PYTHON
import os, sys, re, random, datetime, itertools, functools

# os / sys
os.getcwd()                     # カレントディレクトリ
os.environ.get("HOME")          # 環境変数
sys.argv                        # コマンドライン引数リスト
sys.exit(0)                     # プロセス終了

# datetime
from datetime import datetime, timedelta
now = datetime.now()
print(now.strftime("%Y-%m-%d %H:%M"))
tomorrow = now + timedelta(days=1)

# re(正規表現)
m = re.search(r"\d+", "order-42-A")
print(m.group())                # "42"
words = re.findall(r"\w+", "hello world")

# itertools
from itertools import chain, islice, product
list(chain([1,2], [3,4]))       # [1,2,3,4]
list(product("AB", "12"))       # [('A','1'),('A','2'),('B','1'),('B','2')]

# functools
from functools import reduce
reduce(lambda a, b: a + b, [1,2,3,4])  # 10
11

デコレータ

応用
デコレータの基本 / functools.wraps応用

関数を引数として受け取り、新しい関数を返す「関数を加工する関数」。@functools.wraps(func) を使うと元の関数名・docstring が保たれる。

PYTHON
import functools, time

def timer(func):
    @functools.wraps(func)              # __name__ 等を元の関数から引き継ぐ
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print(f"{func.__name__}: {elapsed:.4f}s")
        return result
    return wrapper

@timer
def slow_sum(n: int) -> int:
    return sum(range(n))

slow_sum(1_000_000)    # slow_sum: 0.0xxx s
print(slow_sum.__name__)  # "slow_sum"(wraps がなければ "wrapper" になる)
@functools.wraps(func) を省略すると、ラップされた関数の __name____doc__ が失われ、デバッグやドキュメント生成ツールで元の関数名が表示されなくなる。デコレータを書く際は常に付ける習慣を持つ。
引数付きデコレータ(デコレータファクトリ)応用

デコレータ自体に引数を渡したい場合は、「デコレータを返す関数」として1段ネストさせる(3層構造になる)。

PYTHON
import functools

def retry(times: int = 3):
    """times 回まで再試行するデコレータ"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(1, times + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    print(f"試行 {attempt}/{times} 失敗: {e}")
            raise RuntimeError(f"{func.__name__}{times} 回失敗しました")
        return wrapper
    return decorator          # ← デコレータを返す

@retry(times=3)
def flaky_request(url: str) -> str:
    import random
    if random.random() < 0.7:
        raise ConnectionError("接続失敗")
    return "OK"
@cache / @lru_cache(functools)応用

関数の戻り値を引数をキーとしてキャッシュするメモ化デコレータ。再帰関数の高速化に特に効果的。@cache(Python 3.9+)は上限なし、@lru_cache(maxsize=N) はサイズ制限付き。

PYTHON
from functools import cache, lru_cache

# @cache なしでは指数オーダーの計算量になる
@cache
def fib(n: int) -> int:
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)

print(fib(50))          # 一瞬で計算できる
print(fib.cache_info()) # hits / misses / size を確認できる

# lru_cache: maxsize でキャッシュ上限を設定
@lru_cache(maxsize=128)
def expensive(x: int) -> int:
    return x ** 3
12586269025 CacheInfo(hits=48, misses=51, maxsize=None, currsize=51)
複数デコレータの適用順応用

デコレータを複数重ねると下から上に向かって適用される(最も関数に近いものが最初に実行される)。

PYTHON
def bold(func):
    @functools.wraps(func)
    def wrapper(*a, **kw): return f"{func(*a, **kw)}"
    return wrapper

def italic(func):
    @functools.wraps(func)
    def wrapper(*a, **kw): return f"{func(*a, **kw)}"
    return wrapper

@bold
@italic        # italic が先に適用され、その結果に bold が適用される
def greet(name):
    return f"Hello, {name}"

# 等価な式: bold(italic(greet))("World")
print(greet("World"))   # Hello, World
<b><i>Hello, World</i></b>
12

型システム

応用
型ヒントの基本 / Optional / Union応用

Python の型ヒントは実行時に強制されない(静的解析ツール mypy / pyright が検証する)。Optional[X]X | None の短縮。Python 3.10+ では X | Y 構文が使える。

PYTHON
from typing import Optional, Union

# Python 3.9 以前のスタイル
def greet(name: str, times: Optional[int] = None) -> str:
    n = times or 1
    return ("Hello, " + name + "! ") * n

# Python 3.10+ のスタイル(| 構文)
def parse(value: str | int | None) -> float | None:
    if value is None:
        return None
    return float(value)

# コレクションの型ヒント(Python 3.9+ は組み込み型をそのまま使える)
def process(items: list[int]) -> dict[str, int]:
    return {"total": sum(items), "count": len(items)}

# Python 3.8 以前は typing.List / typing.Dict を使う
from typing import List, Dict
def old_style(items: List[int]) -> Dict[str, int]: ...
TypeVar・ジェネリクス応用

TypeVar で型変数を定義し、入力と出力の型を関連付けられる。Python 3.12+ では type T = ... 構文が使える。

PYTHON
from typing import TypeVar

T = TypeVar("T")

def first(items: list[T]) -> T:    # 入力と出力が同じ型であることを示す
    return items[0]

x: int = first([1, 2, 3])          # int と推論される
s: str = first(["a", "b"])         # str と推論される

# 境界付き TypeVar(bound)
from typing import TypeVar
Numeric = TypeVar("Numeric", int, float)  # int か float に限定

def double(x: Numeric) -> Numeric:
    return x * 2
Literal / TypedDict応用

Literal は特定の値のみを許可する型。TypedDict は辞書のキーと値の型を定義する(dataclass より軽量で JSON との親和性が高い)。

PYTHON
from typing import Literal, TypedDict

# Literal: 受け入れる値を列挙
Direction = Literal["north", "south", "east", "west"]

def move(direction: Direction) -> None:
    print(f"{direction} へ移動")

move("north")   # OK
# move("up")   # mypy エラー

# TypedDict: 辞書の構造を型で表現
class User(TypedDict):
    id: int
    name: str
    email: str

class PartialUser(TypedDict, total=False):   # total=False で全キーを省略可能に
    id: int
    name: str

user: User = {"id": 1, "name": "Alice", "email": "a@example.com"}
Protocol(構造的部分型)応用

明示的に継承しなくても、必要なメソッドを持っていれば型チェックが通る(ダックタイピングを型安全に表現できる)。Go の interface に近い概念。

PYTHON
from typing import Protocol

class Drawable(Protocol):
    def draw(self) -> None: ...   # このメソッドを持っていれば OK

class Circle:
    def draw(self) -> None:       # Drawable を継承していなくてもOK
        print("○")

class Square:
    def draw(self) -> None:
        print("□")

def render(shape: Drawable) -> None:
    shape.draw()

render(Circle())  # OK
render(Square())  # OK
13

非同期プログラミング

応用
async / await / コルーチン応用

async def で定義した関数はコルーチン。await で他の非同期処理の完了を待つ間、イベントループは他のタスクを実行できる。I/Oバウンドな処理の並行実行に適する。

PYTHON
import asyncio

async def fetch(url: str) -> str:
    print(f"取得開始: {url}")
    await asyncio.sleep(1)          # I/O 待機をシミュレート(この間に他のタスクが動く)
    return f"data from {url}"

async def main():
    result = await fetch("https://example.com")
    print(result)

# Python 3.7+
asyncio.run(main())   # イベントループを起動してコルーチンを実行
取得開始: https://example.com data from https://example.com
asyncio.gather / create_task応用

複数のコルーチンを並行実行するには asyncio.gather()asyncio.create_task() を使う。gather は全完了を待ち結果をリストで返す。

PYTHON
import asyncio, time

async def fetch(name: str, delay: float) -> str:
    await asyncio.sleep(delay)
    return f"{name} 完了"

async def main():
    start = time.perf_counter()

    # gather: 3つを並行実行 → 最も遅い 1.5s で全部終わる
    results = await asyncio.gather(
        fetch("A", 1.0),
        fetch("B", 1.5),
        fetch("C", 0.5),
    )
    print(results)
    print(f"経過: {time.perf_counter() - start:.2f}s")   # ≈ 1.5s

asyncio.run(main())
['A 完了', 'B 完了', 'C 完了'] 経過: 1.51s
async with / async for応用

非同期コンテキストマネージャ(async with)と非同期イテレータ(async for)は、I/O を伴うリソース管理やストリーミングに使う。

PYTHON
import asyncio

# async with: HTTP クライアント (aiohttp) の典型的な使い方
# import aiohttp
# async with aiohttp.ClientSession() as session:
#     async with session.get(url) as resp:
#         data = await resp.json()

# async for: 非同期ジェネレータからストリーム処理
async def stream_lines(n: int):
    for i in range(n):
        await asyncio.sleep(0.01)
        yield f"行 {i}"

async def main():
    async for line in stream_lines(3):
        print(line)

asyncio.run(main())
行 0 行 1 行 2
14

並列処理

応用
GIL(グローバルインタープリタロック)応用

CPython には GIL があり、同時に実行できる Python バイトコードは1スレッドのみ。スレッドは I/O 待機中に解放されるが、CPU 演算は並列化されない。

PYTHON
# 処理の種類と適切な並列化手段
#
# I/O バウンド(ファイル・ネットワーク待機が主)
#   → threading または asyncio が有効(GIL は I/O 待機中に解放される)
#
# CPU バウンド(計算が主)
#   → multiprocessing が有効(別プロセス = 別GIL)
#   → threading では並列化されない(GIL がボトルネック)
#
# ※ Python 3.13 で "free-threaded" モード(--disable-gil)が試験的に導入。
#   通常ビルドでは依然として GIL が存在する。
スレッドで CPU バウンドの処理を並列化しようとしても GIL のせいで高速化されず、むしろコンテキストスイッチのオーバーヘッドで遅くなることがある。CPU バウンドな並列化には multiprocessing または concurrent.futures.ProcessPoolExecutor を使う。
threading / Lock応用

I/O バウンドなタスクの並行実行に適する。共有リソースへの競合アクセスは threading.Lock で防ぐ。

PYTHON
import threading, time

counter = 0
lock = threading.Lock()

def increment(n: int):
    global counter
    for _ in range(n):
        with lock:              # ロックで排他制御
            counter += 1

threads = [threading.Thread(target=increment, args=(100_000,)) for _ in range(5)]
for t in threads: t.start()
for t in threads: t.join()     # 全スレッドの完了を待つ

print(counter)   # 500000
500000
multiprocessing応用

各プロセスが独立した Python インタープリタを持つため GIL の制約を受けない。CPU バウンドな処理の真の並列化に使う。プロセス間のデータ共有はコストがかかる点に注意。

PYTHON
from multiprocessing import Pool

def square(n: int) -> int:
    return n ** 2

if __name__ == "__main__":        # Windows では必須(フォーク時の再実行を防ぐ)
    with Pool(processes=4) as pool:
        results = pool.map(square, range(10))
    print(results)
    # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
concurrent.futures(高レベルAPI)応用

ThreadPoolExecutor(I/Oバウンド)と ProcessPoolExecutor(CPUバウンド)を統一インターフェースで使える。submit() で非同期にタスクを投入し、Future で結果を取得する。

PYTHON
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor, as_completed
import time

def fetch_fake(url: str) -> str:
    time.sleep(0.5)
    return f"OK: {url}"

urls = ["https://example.com/1", "https://example.com/2", "https://example.com/3"]

# I/O バウンド → ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=3) as executor:
    futures = {executor.submit(fetch_fake, url): url for url in urls}
    for future in as_completed(futures):
        print(future.result())

# CPU バウンド → ProcessPoolExecutor(__main__ ガード必要)
# with ProcessPoolExecutor() as executor:
#     results = list(executor.map(heavy_calc, data))
15

イテレータ・ジェネレータ

応用
イテレータプロトコル応用

__iter__()__next__() を実装したオブジェクトがイテレータ。for ループは内部でこのプロトコルを呼び出している。

PYTHON
class Countdown:
    def __init__(self, start: int):
        self.current = start

    def __iter__(self):
        return self             # イテレータ自身を返す

    def __next__(self):
        if self.current <= 0:
            raise StopIteration # イテレーション終了
        val = self.current
        self.current -= 1
        return val

for n in Countdown(3):
    print(n)   # 3, 2, 1

# 組み込み関数との連携
it = iter([10, 20, 30])
print(next(it))   # 10
print(next(it))   # 20
3 2 1 10 20
yield・ジェネレータ関数応用

yield を含む関数はジェネレータ関数。呼び出すとジェネレータオブジェクトを返し、next() のたびに yield まで実行して値を返す。大量データをメモリに展開せずに処理できる。

PYTHON
def integers_from(start: int):
    """無限の整数列を生成するジェネレータ"""
    n = start
    while True:
        yield n
        n += 1

gen = integers_from(1)
print(next(gen))   # 1
print(next(gen))   # 2

# islice で先頭 N 件だけ取得
from itertools import islice
first5 = list(islice(integers_from(0), 5))
print(first5)      # [0, 1, 2, 3, 4]

# ファイルの遅延読み込み(巨大ファイルでもメモリを節約)
def read_chunks(path: str, size: int = 4096):
    with open(path, "rb") as f:
        while chunk := f.read(size):
            yield chunk
1 2 [0, 1, 2, 3, 4]
yield from応用

別のイテラブルやジェネレータに処理を委譲する。ネストしたループを書かずに済み、サブジェネレータの return 値も受け取れる。

PYTHON
def flatten(nested):
    """ネストしたリストを再帰的に平坦化"""
    for item in nested:
        if isinstance(item, list):
            yield from flatten(item)   # 再帰的に委譲
        else:
            yield item

data = [1, [2, [3, 4]], [5, 6]]
print(list(flatten(data)))   # [1, 2, 3, 4, 5, 6]

# yield from でチェーン
def chain_gen(*iterables):
    for it in iterables:
        yield from it

print(list(chain_gen([1,2], [3,4], [5])))  # [1, 2, 3, 4, 5]
[1, 2, 3, 4, 5, 6] [1, 2, 3, 4, 5]
itertools 主要関数応用

標準ライブラリ itertools は遅延評価のイテレータを返す関数群。リストを事前に全展開せず処理できる。

PYTHON
from itertools import (
    chain, islice, product, combinations,
    permutations, groupby, accumulate, cycle, repeat
)

list(chain([1,2], [3,4], [5]))       # [1, 2, 3, 4, 5]
list(islice(range(100), 5))          # [0, 1, 2, 3, 4]
list(product("AB", "12"))            # [('A','1'),('A','2'),('B','1'),('B','2')]
list(combinations("ABCD", 2))        # [('A','B'),('A','C'),...]
list(permutations("ABC", 2))         # [('A','B'),('A','C'),...]
list(accumulate([1,2,3,4]))          # [1, 3, 6, 10](累積和)

# groupby: ソート済みデータをキーでグループ化
data = [("fruit","apple"),("fruit","mango"),("veggie","carrot")]
for key, group in groupby(data, key=lambda x: x[0]):
    print(key, [g[1] for g in group])
fruit ['apple', 'mango'] veggie ['carrot']
16

メモリとスコープ

応用
LEGB スコープ規則応用

変数名の解決順序: Local(関数内)→ Enclosing(外側の関数)→ Global(モジュールトップ)→ Builtin(組み込み)。

PYTHON
x = "global"

def outer():
    x = "enclosing"

    def inner():
        x = "local"
        print(x)    # "local"(L で解決)
    inner()
    print(x)        # "enclosing"(E で解決)

outer()
print(x)            # "global"(G で解決)

# global / nonlocal で上位スコープに書き込む
count = 0
def increment():
    global count
    count += 1

def make_counter():
    n = 0
    def inc():
        nonlocal n   # 外側の関数変数に書き込む
        n += 1
        return n
    return inc
local enclosing global
参照の共有(ミュータブルとイミュータブル)応用

Python の変数はすべてオブジェクトへの参照。代入は参照のコピーであり、ミュータブルなオブジェクト(list・dict等)は複数の変数から同一オブジェクトを参照することになる。

PYTHON
# イミュータブル(int・str・tuple):実質的に「値渡し」のように振る舞う
a = 10
b = a
b = 20
print(a)  # 10(a は変わらない)

# ミュータブル(list・dict):同じオブジェクトへの参照を共有
x = [1, 2, 3]
y = x               # 同じリストを参照
y.append(4)
print(x)            # [1, 2, 3, 4](x も変わる!)

# 関数に渡した場合も同様
def append_zero(lst: list) -> None:
    lst.append(0)   # 呼び出し元のリストを変更する

nums = [1, 2, 3]
append_zero(nums)
print(nums)         # [1, 2, 3, 0]
10 [1, 2, 3, 4] [1, 2, 3, 0]
ミュータブルなオブジェクトを関数内で変更すると呼び出し元に影響する(意図しない副作用の原因になる)。変更したくない場合は関数内でコピーを作る: lst = lst[:] または lst = lst.copy()
シャローコピーとディープコピー応用

シャローコピーは新しいコンテナを作るが内部要素は同じオブジェクトを参照する。ディープコピーはオブジェクトグラフ全体を再帰的に複製する。

PYTHON
import copy

original = [[1, 2], [3, 4]]

# シャローコピー(新しいリストだが要素は同じオブジェクト)
shallow = copy.copy(original)     # または original[:]
shallow[0].append(99)             # 内側のリストを変更
print(original)   # [[1, 2, 99], [3, 4]] ← original も変わる!

original2 = [[1, 2], [3, 4]]

# ディープコピー(全体を再帰的に複製)
deep = copy.deepcopy(original2)
deep[0].append(99)
print(original2)  # [[1, 2], [3, 4]] ← original2 は変わらない
[[1, 2, 99], [3, 4]] [[1, 2], [3, 4]]
__slots__ でメモリを節約応用

通常のクラスはインスタンスごとに __dict__(辞書)を持つ。__slots__ で属性名を宣言すると辞書の代わりに固定配列が使われ、メモリ消費を削減できる。大量インスタンスを生成する場合に効果的。

PYTHON
class PointNormal:
    def __init__(self, x, y):
        self.x, self.y = x, y

class PointSlotted:
    __slots__ = ("x", "y")        # 許可する属性名を宣言
    def __init__(self, x, y):
        self.x, self.y = x, y

import sys
p1 = PointNormal(1, 2)
p2 = PointSlotted(1, 2)

print(sys.getsizeof(p1.__dict__)) # 通常: 辞書のサイズ分余分にかかる
# PointSlotted は __dict__ を持たない
# hasattr(p2, "__dict__")  → False

# 動的属性の追加は不可
# p2.z = 3   # AttributeError: __slots__ にない属性は追加できない
🏠