本ツールで使用しているオープンソースライブラリ

本ツールのコードには 1 個のオープンソースライブラリが含まれています。

Python チートシート — クイックリファレンス

モダン Python 3(3.11+)の構文、組み込み型、頻繁に使う標準ライブラリをまとめたクイックリファレンス。日常の約 80% の用途をカバーします。

Py

Python Python 3.11(LTS 相当)

CPython · マルチパラダイム(OOP・関数型・手続き型) · 動的・強い型付け・ダックタイピング

おすすめの学習パス

まずは python3 の実行と REPL に慣れましょう → 続いて変数、組み込み型、制御フローをマスター → 関数、クラス、例外を深く学習 → 標準ライブラリでファイル・ネットワーク I/O → 参照と可変性(シャロー/ディープコピー)を理解 → 必要に応じて asyncio、プロセス、正規表現を → 最後に venv/pip/pyproject によるプロジェクト運用を学びます。落とし穴を避けたいときは FAQ 章を振り返ると効果的です。

1.Hello World とビルド環境

Python の実行、対話型 REPL、仮想環境、パッケージ管理について説明します。

最小プログラム

print でコンソールに出力します。Python はインデントでブロックを構成し、波括弧やセミコロンは使いません。

1
2
3
4
5
6
7
8
# hello.py
print('Hello, world!')
# 実行:python3 hello.py
# または対話式:python3 で REPL に入り直接入力
print(f'1 + 1 = {1 + 1}')
# stderr に出力:
import sys
print('error', file=sys.stderr)

実行方法

python3 でスクリプト実行、-c で文字列実行、-m でモジュール実行。対話型 REPL ではすぐに評価されます。

1
2
3
4
5
6
7
8
9
10
11
# スクリプトを実行:
# python3 hello.py
# 文字列を実行:
# python3 -c "print('hi')"
# モジュールを実行(__main__ を含む):
# python3 -m http.server 8000
# バージョンを確認:
# python3 --version
# REPL に入る:
# python3
# 対話プロンプト >>> でコードを入力し Enter で実行

仮想環境

venv はプロジェクト依存を隔離します。activate 後の pip は現在の環境だけに作用します。プロジェクトごとに必ず仮想環境を使いましょう。

1
2
3
4
5
6
7
8
9
10
11
12
# 仮想環境を作成:
python3 -m venv .venv
# 有効化(Windows):
# .venv\Scripts\activate
# 有効化(macOS/Linux):
# source .venv/bin/activate
# 無効化:deactivate
# 有効化後:
# python -m pip install requests
# インタープリタを確認:
# which python # Linux/macOS
# 仮想環境名がプロンプトの接頭辞に表示される

pip パッケージ管理

pip で依存をインストール/アンインストール/凍結します。バージョンは ==、範囲は >=,<y で指定します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# インストール:
pip install requests
# バージョン指定:
pip install 'requests==2.31.0'
# バージョン範囲:
pip install 'requests>=2,<3'
# アップグレード:pip install -U requests
# アンインストール:pip uninstall requests
# 依存をエクスポート:
pip freeze > requirements.txt
# ファイルからインストール:
pip install -r requirements.txt
# 一覧:pip list
# pip をアップグレード:python3 -m pip install -U pip

pyproject.toml

現代のプロジェクト設定ファイル: 依存関係、ビルドバックエンド、ツール設定を pyproject.toml に集約します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# pyproject.toml(PEP 621):
# [project]
# name = "my-app"
# version = "0.1.0"
# requires-python = ">=3.11"
# dependencies = [
# "requests>=2.31",
# "httpx>=0.24",
# ]
#
# [build-system]
# requires = ["setuptools>=68"]
# build-backend = "setuptools.build_meta"
#
# [tool.ruff]
# line-length = 88
# 依存をインストール:pip install -e .

Shebang スクリプト

#!/usr/bin/env python3 でスクリプトを実行可能にします。POSIX では chmod +x の上 ./script.py で直接実行できます。

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env python3
# script.py
# 実行権限を与える:
# chmod +x script.py
# 直接実行:
# ./script.py
print('可执行脚本')
# 特定の python パスに依存しない:
# env が python3 を見つける
# Windows では .py をダブルクリックすると関連付けられたインタープリタで実行
# shebang がない場合は python3 script.py で実行

REPL での対話

python3 で REPL に入り対話的にデバッグします。_ が直前の結果を保持し、exit() で抜けます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> 1 + 2
3
>>> _
3
>>> def f(x): return x * 2
>>> f(21)
42
# REPL を終了:
# exit() または Ctrl+D
# 組み込みの help:
>>> help(str)
>>> help('str'.upper)
# オブジェクトの属性を確認:
>>> dir('hello')
# 履歴:上下矢印キーで参照

__main__ とモジュール

if __name__ == '__main__' でスクリプト直接実行とモジュールのインポートを区別します。スクリプト実行/モジュール利用の慣用的書き方です。

1
2
3
4
5
6
7
8
9
10
11
12
# greetings.py
def hello(name='world'):
return f'Hello, {name}!'
if __name__ == '__main__':
# 直接実行時のみ実行
print(hello())
# 直接実行:python3 greetings.py
# インポートして使用:
# from greetings import hello
# hello('Nick')
# __name__ は直接実行時 '__main__',インポート時はモジュール名

2.変数と型注釈

動的型付け、型注釈、多重代入、スコープ、そしてお walrus 演算子(:=)を取り上げます。

代入と型推論

変数の宣言は不要で、代入で生成されます。型は動的に推論され、いつでも別の型に変更できます。

1
2
3
4
5
6
7
8
9
10
11
x = 42 # int と推定
y = 3.14 # float
name = 'Rex' # str
ok = True # bool
empty = None # NoneType
# 型は変更可能:
x = '现在是字符串'
# 複数の変数を同時に:
a = b = c = 0
# 変数は名前であり,箱ではない:
# オブジェクトに束縛し,再代入で束縛を替える

型注釈

PEP 484 スタイルの型注釈で、コロンに続けて型を書きます。注釈は実行時には影響せず、ヒントと静的検査に使われます。

1
2
3
4
5
6
7
8
9
10
11
12
count: int = 0
items: list[str] = []
mapping: dict[str, int] = {}
# PEP 604 ユニオン構文(3.10+):
optional: int | None = None
# 関数アノテーション:
def add(a: int, b: int) -> int:
return a + b
# アノテーションは強制されない:
add('a', 'b') # 実行時はエラーにならない
# 検査ツール:mypy / pyright
# ジェネリック:list[int] であり list[int, ...] ではない

多重代入

a, b = b, a で 1 行の値交換。拡張アンパックは *rest を使います。個数は一致している必要があります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
a, b, c = 1, 2, 3
# 交換:
a, b = b, a
# 拡張アンパック:
first, *rest = [1, 2, 3, 4]
# first=1, rest=[2,3,4]
*head, last = [1, 2, 3, 4]
# head=[1,2,3], last=4
# タプルのアンパック:
x, y = (10, 20)
# 辞書キーのアンパック:
keys = 'ab'
d = dict(zip(keys, [1, 2]))
# 数が合わないと ValueError

定数の慣習

Python には定数キーワードがありません。モジュールレベルの定数は ALL_CAPS で命名するのが慣習です。

1
2
3
4
5
6
7
8
9
10
11
PI = 3.14159
MAX_RETRIES = 3
DATABASE_URL = 'postgres://...'
# あくまで慣習で,変更は可能:
# PI = 99 できるがやめるべき
# 役割:
# 一目で変わらない値と分かる
# 設定を一元管理
# Enum でマジックナンバーを置き換える:
# from enum import Enum
# class Color(Enum): RED = 1

スコープ LEGB

名前探索の順序は Local → Enclosing → Global → Built-in。関数内での代入には global か nonlocal の宣言が必要です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
counter = 0
def inc():
global counter # モジュールレベルを読み書き
counter += 1
def outer():
x = 1
def inner():
nonlocal x # 外側関数の変数を読み書き
x += 1
inner()
print(x) # 2
# 外側の変数の読み取りだけなら nonlocal は不要
# モジュールの先頭でグローバルを定義
# 組み込み関数(len/print)は Built-in 層にある

ウォルラス演算子

:= は式の中で代入する代入式(Python 3.8+)。同じ計算の繰り返しを避けられます。

1
2
3
4
5
6
7
8
9
10
11
12
# 従来の書き方:
line = input('> ')
while line != 'quit':
print(line)
line = input('> ')
# セイウチ演算子:
while (line := input('> ')) != 'quit':
print(line)
# リスト内包表記で再利用:
# [y for x in data if (y := f(x)) > 0]
# 括弧に注意:代入式は括弧で囲む必要がある
# 用途:条件内で初期化して判定

None と bool

None は値なしを表します。真偽は if x で判定し、if x == True は避けましょう。空のコンテナは偽と評価されます。

1
2
3
4
5
6
7
8
9
10
11
12
13
value = None
if value is None: # None の判定は is
print('无值')
# 真偽値の判定:
if [1, 2]: pass # 空でないリストは真
if []: pass # 空リストは偽
# 偽値:False/0/''/[]/{}/(,)/None
if not value: # value が偽のとき
pass
# 三項演算子:
status = 'ok' if value else 'empty'
# 空値マージの代用:
name = value or '默认值'

del とガベージコレクション

del は変数名やコンテナ要素を削除します。名前は未定義になり、オブジェクトの参照カウントが 1 減ります。

1
2
3
4
5
6
7
8
9
10
11
x = 42
del x # 名前を削除
# x を再参照すると NameError
items = [1, 2, 3]
del items[0] # 要素を削除 -> [2, 3]
d = {'a': 1}
del d['a'] # キーを削除
# 辞書のキー削除は pop にデフォルトを渡せる:
val = d.pop('a', None)
# try と組み合わせて KeyError を捕捉
# del は大きなオブジェクトを早期に解放するのに使う

3.データ型

組み込み型: 数値、真偽値、シーケンス、マッピング、集合、そして応用的な型注釈。

数値型

int は任意精度、float は浮動小数、complex は複素数。bool は int の派生で、除算には 2 種類のセマンティクスがあります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
n = 10 # int
g = 3.14 # float
c = 1 + 2j # complex
big = 2 ** 100 # int 任意精度
# 除算:
5 / 2 # 2.5(真の除算)
5 // 2 # 2(整数除算)
5 % 2 # 1(剰余)
# 進数:
0b1010 # 2 進数 10
0o12 # 8 進数 10
0xff # 16 進数 255
# 型変換:
int('42'); float(1); bool(0)

bool と None

True/False は頭文字が大文字で、None はシングルトンです。and/or は短絡評価で、決定した側のオペランドを返します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
t, f = True, False
# ショートサーキット:
print(0 and 'x') # 0(ショートサーキットで左の値を返す)
print('' or '默认') # '默认'
print(None or []) # []
# 三項演算子:
age = 17
msg = '成年' if age >= 18 else '未成年'
# 比較の連鎖:
if 0 < x < 10: pass
# None の判定:
if x is None: pass
# bool() 変換:
bool([]) # False
bool([1]) # True

シーケンス型

str/list/tuple はすべてシーケンスで、インデックス・スライス・反復が使えます。str と tuple は不変です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
s = 'abc'
# インデックス:
s[0] # 'a'
s[-1] # 'c'
# スライス:
s[1:] # 'bc'
s[::-1] # 'cba' 反転
# シーケンス共通:
len(s), min([3,1]), max([3,1])
sum([1,2,3])
list('abc') # ['a','b','c']
# 存在:
'a' in s # True
# 不変シーケンスは変更不可:
# s[0] = 'x' エラー
# インデックス範囲外 IndexError

マッピング dict

dict はキーと値の組を持ち、挿入順を保持します。キーはハッシュ可能でなければならず、.get で安全に取り出せます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
d = {'a': 1, 'b': 2}
# 取得:
d['a'] # 1,キー欠落で KeyError
d.get('c', 0) # 0,キー欠落でデフォルト
# 追加・変更:
d['c'] = 3
d.setdefault('x', 0)
# 削除:
d.pop('a', None)
# マージ:
d.update({'y': 9})
# ビュー:
d.keys(); d.values(); d.items()
# 辞書内包表記:
{n: n**2 for n in range(3)}
# キーはハッシュ可能である必要:数値/文字列/タプルは可

集合型

set は順序なしの重複なし集合、frozenset はその不変版です。和集合・積集合・差集合の演算が使えます。

1
2
3
4
5
6
7
8
9
10
11
12
13
s = {1, 2, 3}
s.add(4)
s.remove(2) # キー欠落で KeyError
s.discard(99) # キー欠落でもエラーなし
# 重複除去:
list(set([1, 1, 2])) # [1, 2]
# 演算:
{1, 2} | {2, 3} # 和集合 {1,2,3}
{1, 2} & {2, 3} # 積集合 {2}
{1, 2} - {2} # 差集合 {1}
# 不変集合:
frozenset([1, 2])
# 空集合は set(),{} は空辞書

バイト型

bytes は不変、bytearray は可変です。テキストとバイト列は encode/decode で相互変換します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
b = b'hello' # bytes
ba = bytearray(b'abc')
ba[0] = 120 # 可変
# エンコード:
text = '你好'.encode('utf-8')
# b'\xe4\xbd\xa0\xe5\xa5\xbd'
text.decode('utf-8') # '你好'
# エンコードエラー:
text.decode('utf-8', errors='ignore')
# 文字列とバイトの変換には必ずエンコーディングを指定
# バイナリデータ:
import struct
struct.pack('>i', 42)
# バイトオーダー:> ビッグエンディアン < リトルエンディアン

型注釈の応用

typing モジュール: Optional、Union、TypeVar、ジェネリックなコンテナ。Python 3.10+ では | 構文も使えます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from typing import Optional, Union, TypeVar, Callable
# オプショナル:
x: Optional[int] = None # int | None と同じ
# ユニオン:
y: Union[int, str] # int | str と同じ
# ジェネリック変数:
T = TypeVar('T')
def first(items: list[T]) -> T:
return items[0]
# 呼び出し可能:
def apply(f: Callable[[int], int], n: int) -> int:
return f(n)
# 型エイリアス(3.12+):
type Vector = list[float]
# チェック:mypy / pyright

ダックタイピング

「アヒルのように鳴くならアヒル」: 型ではなく振る舞いを重視します。typing.Protocol で構造的サブタイピングを定義できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# ダックタイピング:len を持つオブジェクトなら使える
def size(obj):
return len(obj)
size([1, 2]) # 2
size('abc') # 3
# プロトコル(typing.Protocol):
from typing import Protocol
class Sizeable(Protocol):
def __len__(self) -> int: ...
def total_len(objs: list[Sizeable]) -> int:
return sum(len(o) for o in objs)
# 構造的サブタイピング:形が合えばよい
# 明示的な継承は不要

4.参照と可変性

Python のオブジェクト参照セマンティクス: 可変/不変、シャローコピーとディープコピー、共有参照の落とし穴。

参照のセマンティクス

変数はオブジェクトに束縛され、コピーのための箱ではありません。複数の名前が同じオブジェクトを指せます。代入は束縛を作り直す操作です。

1
2
3
4
5
6
7
8
9
10
11
12
13
a = [1, 2, 3]
b = a # b と a は同じリストを指す
b.append(4)
print(a) # [1, 2, 3, 4]
# 再バインドしても元のオブジェクトは影響を受けない:
a = [9]
print(b) # 依然 [1,2,3,4]
# 整数などの不変オブジェクト:
x = 10
y = x
x = 20
print(y) # 10,値は変わらない
# 要点:「オブジェクトを変更」と「束縛を変更」を区別する

可変と不変

list/dict/set は可変で、その場で変更できます。int/str/tuple/frozenset は不変です。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 可変:その場で変更
lst = [1, 2]
lst.append(3) # 同じオブジェクトが変化
# 不変:操作で新しいオブジェクトが生成
s = 'ab'
s2 = s.upper() # 新しい文字列
# s は変わらない
# 不変の値は辞書キーにできる:
d = {('a', 1): 'x'}
# リストはキーにできない:
# {[1]: 'x'} エラー
# 可変要素を含むタプルもハッシュ不可
# 不変オブジェクトは安全に共有できる

シャローコピー

copy.copy はシャローコピーで、外側のコンテナは新しくなりますが、内側の要素は共有されたままです。リストのスライスもシャローコピーです。

1
2
3
4
5
6
7
8
9
10
11
12
13
import copy
original = [[1, 2], [3, 4]]
shallow = copy.copy(original)
# 外側は別:
shallow is original # False
# 内側は共有:
shallow[0] is original[0] # True
# リストのスライスも同じ:
sub = original[:]
# 内側を変更すると相互に影響:
shallow[0].append(99)
print(original) # [[1,2,99], [3,4]]
# 浅いコピーは 1 層構造に適する

ディープコピー

copy.deepcopy は全層を再帰的に複製します。ネストした可変構造の変更はコピー側に漏れません。コストに注意してください。

1
2
3
4
5
6
7
8
9
10
11
12
import copy
original = [[1, 2], [3, 4]]
deep = copy.deepcopy(original)
deep[0].append(99)
print(original) # [[1, 2], [3, 4]]
print(deep) # [[1, 2, 99], [3, 4]]
# 深いコピーは再帰的に複製:
# すべてのネストしたオブジェクトが新しい
# コストは深さに比例
# 循環参照も処理できる
# 自己参照オブジェクト:copy.deepcopy(x)
# 大規模データでは注意,構造設計を考慮

共有参照の落とし穴

同じ可変オブジェクトを共有していると、どこで変更してもすべてに見えます。引数渡しも参照渡しのセマンティクスです。

1
2
3
4
5
6
7
8
9
10
11
# 関数内で渡されたリストを変更:
def add_item(items):
items.append('x') # 呼び出し側に影響!
my_list = []
add_item(my_list)
print(my_list) # ['x']
# その場での変更を避けたいならコピーを渡す:
add_item(my_list.copy())
# または関数内で新しいリストを返す
# デフォルト引数の罠は FAQ を参照
# 判断:関数が読み取り専用を約束しているか

is と == の違い

== は値を比較し、is は同一性(同じオブジェクトか)を比較します。None との比較には is None を使います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
a = [1, 2]
b = [1, 2]
a == b # True,値が等しい
a is b # False,別のオブジェクト
# 小整数のインターン:
x = 256
y = 256
x is y # True(-5..256 はキャッシュ)
# 大きな整数:
x, y = 10**6, 10**6
x is y # 不定,依存しない
# 正しい使い方:
if x is None: pass
# 値の比較には ==:
if x == 42: pass

デフォルト引数の落とし穴

可変なデフォルト引数は関数定義時に 1 度だけ作られ、呼び出し間で共有されます。None をセンチネルにして関数内で生成しましょう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# BAD:共有されるデフォルトリスト
def add(x, acc=[]):
acc.append(x)
return acc
add(1) # [1]
add(2) # [1, 2] !
# GOOD:None + 関数内で作成
def add(x, acc=None):
if acc is None:
acc = []
acc.append(x)
return acc
# デフォルト値は 1 回だけ評価:
# 定義時に作成され,以後再利用される

インターン化とキャッシュ

小さな整数と短い文字列はインターン化(interning)でメモリが節約されます。is で値を比較するのはやめましょう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 小整数のキャッシュ範囲:
# -5 から 256 はシングルトン
x, y = 256, 256
x is y # True
# 範囲外:
a, b = 257, 257
a is b # 不定(実装依存)
# 文字列のインターン:
'hello' is 'hello' # True(コンパイル時定数)
# 実行時生成は別:
'he' + 'llo' is 'hello' # 場合により False
# 結論:
# 内容比較は一律 == を使う
# is は None とシングルトンにのみ使う

5.制御フロー

条件分岐、ループ、パターンマッチング、内包表記。

if / elif / else

インデントでブロックを分けます。elif で複数の条件を扱い、任意の真理値式を条件にできます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
score = 85
if score >= 90:
grade = 'A'
elif score >= 80:
grade = 'B'
elif score >= 70:
grade = 'C'
else:
grade = 'D'
# 1 行書き(複雑な場合は非推奨):
if x > 0: print('正数')
# 複数条件:
if a and not b: pass
if x in (1, 2, 3): pass

パターンマッチング

match-case は構造的パターンマッチング(Python 3.10+)。形で照合し分解できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def handle(command):
match command.split():
case ['quit']:
print('退出')
case ['open', path]:
print(f'打开 {path}')
case ['open', path, *rest]:
print(f'打开 {path} 附加 {rest}')
case _:
print('未知命令')
# ガード:
def f(point):
match point:
case (0, 0): print('原点')
case (x, y) if x == y: print('对角')
case (x, y): print(f'({x}, {y})')
# デフォルト分岐 _ で補完

for ループ

for は反復可能オブジェクトを走査します。enumerate で添字、zip で並行、dict.items() でキーと値を取得できます。

1
2
3
4
5
6
7
8
9
10
11
12
for i in range(5):
print(i) # 0..4
for idx, item in enumerate(['a', 'b']):
print(idx, item)
for k, v in {'a': 1}.items():
print(k, v)
for a, b in zip([1, 2], ['x', 'y']):
print(a, b) # (1,'x') (2,'y')
# range のステップ:
range(0, 10, 2) # 0 2 4 6 8
# 逆順:reversed(lst)
# 逆順でインデックス付き:range(len(x)-1, -1, -1)

while ループ

while は条件が真の間ループします。break で早期終了し、無限ループに注意します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
n = 0
while n < 5:
print(n)
n += 1
# 無限ループ + break:
while True:
line = input('> ')
if line == 'quit':
break
print(line)
# while-else:
# break せずに終了した場合 else を実行
attempts = 0
while attempts < 3:
if try_connect():
break
attempts += 1
else:
print('连接失败')

break と continue

break はループ全体を抜け、continue は現在の反復をスキップします。else 節は break しなかった場合に実行されます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for i in range(10):
if i % 2 == 0:
continue # 偶数をスキップ
if i > 7:
break # 途中で終了
print(i) # 1 3 5 7
# for-else:
for n in range(2, 10):
for d in range(2, n):
if n % d == 0:
break
else:
print(n, '是质数')
# else はループを最後まで走ったときに実行
# 注意:else は for に属し,if ではない

内包表記

リスト/辞書/集合内包表記は 1 行でコレクションを生成し、条件でフィルタ、式で変換します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# リスト内包表記:
squares = [n ** 2 for n in range(5)]
# [0, 1, 4, 9, 16]
# 条件付き:
evens = [n for n in range(10) if n % 2 == 0]
# 辞書内包表記:
{n: n * 2 for n in range(3)}
# {0:0, 1:2, 2:4}
# 集合内包表記:
{n % 3 for n in range(10)}
# ネスト:
flat = [y for xs in matrix for y in xs]
# ジェネレータ式(遅延):
total = sum(n * n for n in range(100))

真偽値判定

if x で真偽を判定します。0、空文字、空リスト、None は偽。True/False との明示比較は避けましょう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 空コンテナ判定:
items = []
if not items: # GOOD:空なら偽
print('空列表')
# BAD:こう書かない
if len(items) == 0: pass
# None 判定:
if value is not None:
print(value)
# 数値判定:
if count: pass # 0 は偽
# 文字列:
if name: pass # '' は偽
# 明示的な真偽:
if bool(x): pass
# 優先:可読性を優先し,条件を長くしない

反復のイディオム

itertools は効率的な反復の合成を提供します。groupby でグループ化、chain で連結、product で直積。

1
2
3
4
5
6
7
8
9
10
11
12
from itertools import chain, groupby, product
# 連結:
list(chain([1, 2], [3, 4])) # [1,2,3,4]
# 直積:
list(product('ab', [1, 2]))
# [('a',1),('a',2),('b',1),('b',2)]
# グループ化(ソート必要):
rows = sorted(rows, key=lambda r: r['type'])
for key, group in groupby(rows, key=lambda r: r['type']):
print(key, list(group))
# 無限イテレーション:itertools.count()
# 順列・組合せ:itertools.permutations / combinations

6.関数

関数定義、引数の渡し方、デコレータ、ジェネレータ、クロージャ。

関数定義

def で関数を定義し、本体はインデントします。return で値を返し、return がない関数は None を返します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def greet(name):
return f'Hello, {name}!'
print(greet('Nick'))
# 戻り値なし:
def log(msg):
print(msg) # None を返す
# 複数の戻り値(タプル):
def min_max(nums):
return min(nums), max(nums)
lo, hi = min_max([3, 1, 4])
# 型アノテーション:
def add(a: int, b: int) -> int:
return a + b
# ドキュメント文字列:
def f():
"""描述功能"""
pass

引数の種類

位置引数、キーワード引数、デフォルト値。呼び出し時は位置とキーワードを混在させられます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def show(a, b, c):
print(a, b, c)
# 位置呼び出し:
show(1, 2, 3)
# キーワード呼び出し:
show(c=3, a=1, b=2)
# 混在(位置が先):
show(1, c=3, b=2)
# デフォルト引数:
def greet(name, greeting='Hi'):
return f'{greeting}, {name}!'
# デフォルト引数は最後に:
# def f(a=1, b) エラー
# キーワード専用引数:
def f(*, strict=False): pass
# 位置専用引数(3.8+):
def f(a, /): pass

*args と **kwargs

*args は余った位置引数をタプルに、**kwargs は余ったキーワード引数を辞書に集めます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def log_all(*args, **kwargs):
print('位置:', args)
print('关键字:', kwargs)
log_all(1, 2, x=3, y=4)
# 転送呼び出し:
def wrapper(*args, **kwargs):
return original(*args, **kwargs)
# アンパック呼び出し:
items = [1, 2, 3]
print(*items) # 1 2 3
config = {'a': 1}
f(**config)
# 順序:位置 -> *args -> キーワード -> **kwargs
# 乱用しない,引数が多い場合はオブジェクトを推奨

無名関数

lambda は 1 行式の無名関数で、sort のキーや map/filter など高階関数に渡す用途で使われます。

1
2
3
4
5
6
7
8
9
10
11
square = lambda x: x * x
square(5) # 25
# ソートの key:
users.sort(key=lambda u: u['age'])
# 高階関数:
list(map(lambda x: x * 2, [1, 2]))
list(filter(lambda x: x > 1, [1, 2]))
# 即時呼び出し:
(lambda x: x + 1)(41) # 42
# 複雑なロジックは def,lambda は簡単な式のみ
# lambda に文(if/return)は書けない

クロージャ

内部関数が外側の変数を捕捉し、返された関数がそのスコープを覚えます。ファクトリ関数で定番のパターンです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def make_multiplier(n):
def multiply(x):
return x * n # n をキャプチャ
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
double(5) # 10
triple(5) # 15
# クロージャで外側の変数を変更:
def counter():
count = 0
def inc():
nonlocal count
count += 1
return count
return inc
# 各クロージャは自分の n/count を独立して保持

デコレータ

@decorator は関数をラップして振る舞いを拡張します。コードを変えずに計測やロギングを追加できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import functools, time
def timing(func):
@functools.wraps(func) # メタ情報を保持
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
print(f'{func.__name__}: {time.perf_counter()-start:.4f}s')
return result
return wrapper
@timing
def slow():
time.sleep(0.1)
# 引数付きデコレータ:
def repeat(times):
def deco(func):
@functools.wraps(func)
def wrapper(*a, **kw):
for _ in range(times): func(*a, **kw)
return wrapper
return deco
# 組み込みデコレータ:@staticmethod/@classmethod/@property

ジェネレータ

yield は値を遅延生成します。ジェネレータは 1 つずつ値を流すので、大規模な系列を省メモリで扱えます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def count_up(n):
i = 0
while i < n:
yield i # 一時停止して返す
i += 1
for x in count_up(3):
print(x) # 0 1 2
# ジェネレータ式:
squares = (n * n for n in range(3))
# 無限シーケンス:
def fib():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
# 遅延:すべてをメモリに読み込まない
# 使い捨て:使い切ったら作り直す
# パイプライン:ジェネレータを連結してストリーム処理

関数型ツール

map/filter/reduce/sorted が関数スタイルの要で、functools は partial などのヘルパーを提供します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from functools import partial, reduce
# map 変換:
doubled = list(map(lambda x: x * 2, [1, 2, 3]))
# filter フィルタ:
evens = list(filter(lambda x: x % 2 == 0, range(6)))
# reduce 集約:
total = reduce(lambda a, b: a + b, [1, 2, 3])
# 部分適用:引数を固定
def power(base, exp): return base ** exp
square = partial(power, exp=2)
square(5) # 25
# ジェネレータは map/filter に渡せる:
list(map(str, range(3))) # ['0','1','2']
# モダンなスタイルでは内包表記を推奨,可読性が高い

再帰

関数が自分自身を呼び出します。必ず基底ケースを置きましょう。再帰深度の上限は約 1000(sys.setrecursionlimit で変更可)です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def factorial(n):
if n <= 1:
return 1
return n * factorial(n - 1)
factorial(5) # 120
# 再帰の深さ制限:
import sys
sys.getrecursionlimit() # デフォルト 1000
# 深い再帰は反復かスタックを使う:
def fib_iter(n):
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
return a
# 木走査/分割統治は再帰が自然
# メモ化:@functools.lru_cache

7.文字列

不変文字列、f-string フォーマット、よく使うメソッド、エンコーディング。

文字列の基礎

シングルクォート/ダブルクォート/トリプルクォートで作成し、エスケープシーケンスが効きます。str は不変で、操作は新しい文字列を返します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
s1 = '单引号'
s2 = "双引号"
s3 = '''多行
字符串'''
# 生文字列(パス/正規表現):
r'C:\\path'
# エスケープ:
'\n' # 改行
'\t' # タブ
'\\' # バックスラッシュ
# 文字列はシーケンス:
s = 'hello'
s[0] # 'h'
s[1:4] # 'ell'
# 不変:
# s[0] = 'H' エラー
# 連結:
first + last
' '.join(['a', 'b'])

よく使うメソッド

upper/lower は大文字小文字、strip は空白除去、split/join は分割と連結、startswith は接頭辞判定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
s = ' Hello World '
s.upper() # ' HELLO WORLD '
s.lower()
s.strip() # 前後の空白を除去
s.startswith('He') # True
s.endswith('ld') # True
s.replace('World', 'Python')
# 検索:
s.find('World') # インデックスまたは -1
s.index('World') # なければ ValueError
s.count('l')
# 分割・結合:
'1,2,3'.split(',') # ['1','2','3']
'-'.join(['a', 'b']) # 'a-b'
# 判定:
'abc'.isalpha(); '123'.isdigit()

f-string フォーマット

f"{var}" で式を埋め込みます。書式で配置・精度・桁区切りを制御でき、3.12+ では引用符の再利用も可能です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
name = 'Nick'
age = 30
f'{name} 今年 {age} 岁'
# 式:
f'{age + 1}'
# 幅揃え:
f'{name:>10}' # 右揃え 10
f'{name:<10}' # 左揃え
# 数値フォーマット:
f'{3.14159:.2f}' # '3.14'
f'{1000000:,}' # '1,000,000'
f'{0.5:.0%}' # '50%'
# 進数:
f'{255:x}' # 'ff'
# 日付:
import datetime
f'{datetime.date.today():%Y-%m-%d}'
# 辞書の取得:f'{d["key"]}'

format と % フォーマット

str.format は位置/名前プレースホルダ、% 演算子は古いスタイル。新しいコードでは f-string を推奨します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# format メソッド:
'{0} {1}'.format('a', 'b')
'{name} 今年 {age}'.format(name='Nick', age=30)
# 整列:
'{:>10}'.format('hi') # 右揃え
'{:.2f}'.format(3.14159)
# 旧式 % フォーマット:
'%s 今年 %d' % ('Nick', 30)
'%.2f' % 3.14159
'%x' % 255
# 3 つのスタイル対比:
# f-string:f'{x:.2f}'(3.6+ 推奨)
# format:'{:.2f}'.format(x)
# 旧式:'%.2f' % x
# 新しいコードは f-string を使う

分割と連結

split はセパレータで分解、rsplit は右から、partition は 3 要素に分割、join は高速に連結します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
'a,b,c'.split(',') # ['a','b','c']
'a,b,c'.split(',', 1) # ['a', 'b,c']
'a b c'.split() # 空白で自動分割
'a-b-c'.rsplit('-', 1) # ['a-b', 'c']
'key=value'.partition('=') # ('key','=','value')
# 結合(性能):
parts = ['a', 'b', 'c']
', '.join(parts)
# 1 文字ずつ分割:
list('abc')
# 複数デリミタ:
import re
re.split(r'[;,|]', 'a;b,c|d')
# ループ内の + 連結は避ける(O(n^2))

トリムとパディング

strip/lstrip/rstrip で空白除去、zfill でゼロ詰め、center で中央寄せ、removeprefix/removesuffix は 3.9+ で使えます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
s = ' hello '
s.strip() # 'hello'
s.lstrip() # 'hello '
s.rstrip() # ' hello'
' x \n'.strip() # 'x'
# 指定文字:
'xxhelloxx'.strip('x') # 'hello'
# パディング:
'42'.zfill(5) # '00042'
'hi'.center(7, '-') # '--hi---'
# 接頭辞除去(3.9+):
'/usr/bin'.removeprefix('/usr/') # 'bin'
'/usr/bin'.removesuffix('/bin') # '/usr'
# 改行の除去:
text.rstrip('\n')

Unicode とエンコーディング

Python の str は Unicode コードポイントの列です。encode でバイト列、decode で文字列に戻し、len は文字数を数えます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
s = '你好👋'
len(s) # 3(emoji は 1 コードポイント)
# エンコード:
b = s.encode('utf-8')
# デコード:
b.decode('utf-8')
# コードポイント:
ord('中') # 20013
chr(20013) # '中'
# 正規化:
import unicodedata
unicodedata.normalize('NFKC', s)
# 大小文字:
s.casefold() # より厳密な小文字化
# エンコードエラー処理:
b.decode('utf-8', errors='replace')
# コードポイント走査:for ch in s は既にコードポイント単位

検索と置換

find/index で検索、replace で置換、translate でテーブル置換、count で出現回数を数えます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
s = 'the quick brown fox'
s.find('quick') # 4
s.find('z') # -1
s.index('quick') # 4(なければ例外)
s.rfind('o') # 右から探す
s.count('o') # 4
# 置換:
s.replace('fox', 'dog')
s.replace('o', '0', 1) # 1 回だけ置換
# 複数置換(translate):
table = str.maketrans({'a': 'A', 'e': 'E'})
'apple'.translate(table) # 'ApplE'
# 大文字小文字を無視して置換:
import re
re.sub('(?i)the', 'THE', s)
# 部分文字列の抽出:s[4:9]

テキスト折り返し

textwrap はテキストの折り返し: wrap で幅指定、dedent で共通インデント除去、fill で段落化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import textwrap
long_text = '这是一个比较长的文本,需要按照指定宽度断行显示。'
# 幅で改行:
lines = textwrap.wrap(long_text, width=20)
# 段落に整形:
textwrap.fill(long_text, width=20)
# 共通インデントを除去:
block = '''\
line1
line2'''
textwrap.dedent(block)
# インデント:
textwrap.indent('a\nb', ' ')
# 用途:CLI ヘルプテキストの整形
# コンソール出力の整列

8.コレクションとコンテナ

list/dict/set/tuple の操作、collections モジュール、ソート。

リストの操作

append で末尾追加、insert で挿入、remove で値削除、pop で末尾/指定位置を取り出し、index で位置、count で数を取得。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
nums = [3, 1, 2]
nums.append(4) # [3,1,2,4]
nums.insert(0, 0) # [0,3,1,2,4]
nums.remove(1) # 最初の 1 を削除
last = nums.pop() # 末尾を pop
first = nums.pop(0) # 指定位置を pop
nums.index(2) # インデックス
nums.count(1)
nums.reverse() # その場で反転
nums.clear() # クリア
# コピー:
nums[:] 或 nums.copy()
# 拡張:
nums.extend([5, 6])
nums += [7, 8]

辞書の操作

.get で取得、update や | でマージ、items で反復、値で並べ替え、ネストした辞書へのアクセス。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
d = {'a': 1, 'b': 2}
d['c'] = 3 # 追加/更新
d.get('x', 0) # デフォルト 0
val = d.setdefault('y', 0)
# マージ(3.9+):
merged = d | {'z': 9}
d.update({'z': 9})
# 走査:
for k, v in d.items(): pass
# キーでソート:
sorted(d.items())
# 値でソート:
sorted(d.items(), key=lambda kv: kv[1])
# ネスト:
d['user']['name']
# 安全なアクセスチェーン:
d.get('user', {}).get('name')
# デフォルト辞書:collections.defaultdict

集合演算

和集合 |、積集合 &、差集合 -、対称差 ^。メンバ判定は O(1)。重複除去に向きます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
a = {1, 2, 3}
b = {2, 3, 4}
a | b # {1,2,3,4} 和集合
a & b # {2,3} 積集合
a - b # {1} 差集合
b - a # {4}
a ^ b # {1,4} 対称差
# 判定:
2 in a # True O(1)
# 部分集合:
a <= b; a < b
# 重複除去で順序保持:
list(dict.fromkeys([3, 1, 3, 2]))
# 重複要素を探す:
[x for x in lst if lst.count(x) > 1]
# 不変:frozenset

タプルの使い方

tuple は不変シーケンスで、複数の戻り値、レコード、辞書のキーに向きます。namedtuple はフィールド付きの派生版です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
t = (1, 2, 3)
# アンパック:
a, b, c = t
# 1 要素にはカンマが必要:
single = (1,)
# レコードとして使用:
point = (10, 20)
x, y = point
# 名前付きタプル:
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(1, 2)
p.x; p[0] # 2 通りのアクセス
# 名前付きタプルには型アノテーション版も:
# from typing import NamedTuple
# 不変:t[0] = 9 エラー
# 辞書のキーに使える(ハッシュ可能)

collections のツール

Counter は集計、deque は両端キュー、defaultdict はキー既定値、OrderedDict は順序付き辞書です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from collections import Counter, deque, defaultdict
# カウント:
Counter('abracadabra').most_common(2)
# [('a', 5), ('r', 2)]
# 両端キュー:
q = deque([1, 2])
q.appendleft(0); q.pop(); q.popleft()
# 固定長(古い要素を破棄):
last5 = deque(maxlen=5)
# デフォルト辞書:
dd = defaultdict(list)
dd['k'].append(1) # 空の list を自動生成
# 順序付き辞書:
# Python 3.7+ の dict は元々順序保持
# カウンタの演算:
Counter('aab') + Counter('abb')
# Counter('aab') - Counter('b')

ソート

sorted は新しいリストを返し、list.sort はその場でソートします。key で比較対象を指定し、reverse=True で逆順。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
nums = [3, 1, 2]
sorted(nums) # [1,2,3] 新しいリスト
nums.sort() # その場でソート
# 逆順:
sorted(nums, reverse=True)
# key でソート:
words = ['banana', 'apple', 'cherry']
sorted(words, key=len)
# 辞書のリスト:
users.sort(key=lambda u: u['age'])
# 多段ソート:
sorted(users, key=lambda u: (u['age'], u['name']))
# 安定ソート:
sorted(users, key=lambda u: u['age'])
# 降順キー:
sorted(users, key=lambda u: -u['age'])

スライス

seq[start:stop:step] でスライス。負のインデックスは末尾から数えます。反転、ステップ、コピーに使います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
s = list(range(10))
s[2:5] # [2, 3, 4]
s[:3] # 先頭から
s[7:] # 末尾まで
s[-3:] # 末尾 3 個
s[::2] # 1 つおき
s[::-1] # 反転
s[1:8:2] # ステップ
# 代入:
s[1:3] = [9] # 部分を置換
# 削除:
del s[1:3]
# コピー:
copy = s[:]
# 文字列も同様:
'hello'[::-1] # 'olleh'
# スライスは範囲外でもエラーにならない

ヒープとキュー

heapq は優先度付き処理、queue はスレッドセーフなキュー(優先度キューを含む)を提供します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import heapq
nums = [3, 1, 4, 1, 5]
heapq.heapify(nums) # 最小ヒープを作成
heapq.heappop(nums) # 最小値を取り出す
heapq.heappush(nums, 0)
# 最大/最小 n 個を取り出す:
heapq.nlargest(2, [3, 1, 4])
heapq.nsmallest(2, [3, 1, 4])
# 優先度キュー:
import queue
pq = queue.PriorityQueue()
pq.put((1, '低'))
pq.put((0, '高'))
pq.get() # (0, '高')
# スレッドセーフ:queue.Queue()
# タスクキュー:queue.Queue(maxsize)

9.メモリとパフォーマンス

ガベージコレクション、参照カウント、メモリ計測ツール、性能最適化。

ガベージコレクション

参照カウントを中心に、世代別 GC が循環参照を回収します。手動で free を呼ぶ必要はありません。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# オブジェクトの参照カウントが 0 になると回収
x = [1, 2]
y = x
del x # カウント減,まだ生存
del y # カウント 0,回収
# 循環参照:
import gc
gc.collect() # 手動でトリガ
# オブジェクト破棄フック:
class Temp:
def __del__(self):
print('回收')
# 通常は手動 gc 不要
# 参照カウントは循環参照を処理できない,世代別 GC が補完

memoryview とゼロコピー

memoryview はバッファをコピーせずにビューで扱います。大規模なバイナリ処理を効率化できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
data = bytearray(b'hello world')
view = memoryview(data)
view[0] # 104
view[6:11] # b'world'
# スライスはビューでありコピーではない:
sub = view[6:11]
sub[0] = 87 # 変更は元データに影響
# フォーマット:
mem = memoryview(b'\x00\x01')
mem.cast('H') # 16 ビット符号なしで読む
# 用途:
# 1. 大きなファイルの分割処理
# 2. プロトコル解析でコピー回避
# 3. struct でビューを組み合わせる
# 性能が重要な場面ではスライスコピーより速い

弱参照

weakref.ref は参照先の回収を妨げません。大きなオブジェクトのキャッシュや循環参照リークの防止に有用です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import weakref
class Node:
pass
node = Node()
ref = weakref.ref(node)
print(ref()) # オブジェクト
node = None # 強参照を解放
gc.collect()
print(ref()) # None(回収済み)
# WeakKeyDictionary/WeakValueDictionary:
# キー/値を弱参照,回収で自動的に除去
cache = weakref.WeakValueDictionary()
# 循環参照の予防:
# いずれかの辺を弱参照に置き換える
# 用途:キャッシュ、オブザーバー、シングルトン

__slots__ でメモリ節約

__slots__ で属性を固定し、インスタンスごとの __dict__ を省きます。数百万個の小オブジェクトでメモリと速度を改善。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# デフォルトでは各インスタンスに __dict__ がある:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
# __slots__ を使う:
class Point:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
# p.z = 3 エラー:未宣言の属性
# 節約:インスタンスごとに dict が 1 つ減る
# 数百万の小オブジェクトで効果顕著
# 欠点:動的に属性を追加できない,__dict__ がない

大規模データ処理

大きなデータはチャンクで処理し、反復は遅延評価にしましょう。全量を一度に読み込まず、ファイルを 1 行ずつストリームします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 大きなファイルを行ごとに読む:
with open('big.log') as f:
for line in f: # 遅延で 1 行ずつ
process(line)
# 大きなリストはジェネレータ:
def read_all_lines(path):
with open(path) as f:
for line in f:
yield line
# 分割処理:
def chunks(iterable, size):
from itertools import islice
it = iter(iterable)
while batch := list(islice(it, size)):
yield batch
for batch in chunks(range(100), 10):
process_batch(batch)
# list(map(...)) で全部読み込むのは避ける
# 大規模な数値は numpy の方が効率的

プロファイリング

cProfile で時間計測、timeit でマイクロベンチマーク。ボトルネックがわかってから最適化します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import timeit, cProfile
# マイクロベンチマーク:
timeit.timeit("'x' * 100", number=10000)
# またはコマンドライン:
# python3 -m timeit "'-'.join(str(n) for n in range(100))"
# 全体プロファイリング:
# python3 -m cProfile -s cumulative script.py
# コード内プロファイリング:
cProfile.run('my_func()', sort='cumulative')
# 計時デコレータ:
import time
def timed(f):
def w(*a, **k):
t = time.perf_counter()
r = f(*a, **k)
print(f.__name__, time.perf_counter() - t)
return r
return w
# 原則:測ってから最適化

性能最適化のコツ

ローカル変数はグローバルより速く、内包表記はループより速く、集合の検索は O(1)。ループ内の重い計算は外に出しましょう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# ローカル変数のキャッシュ:
import math
def f(n):
m = math.sqrt # ローカル参照
return m(n)
# 集合のメンバー O(1):
allowed = set(['a', 'b'])
if x in allowed: pass
# 内包表記 vs ループ:
# 通常内包表記の方が速く簡潔
squares = [n * n for n in range(1000)]
# 重複計算を避ける:
# ループ外に共通因子を出す
# 文字列連結は join を使う
# 大規模な数値は numpy
# 複雑なホットパスは C 拡張 / numba を検討

よくあるメモリエラー

MemoryError(メモリ不足)、RecursionError(スタックあふれ)、循環参照。診断ツールも紹介します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# スタックオーバーフロー:
def infinite():
return infinite()
# RecursionError: maximum recursion depth
# メモリ不足:
# MemoryError(大量データを一度に読み込む)
# 診断:
import tracemalloc
tracemalloc.start()
# コードを実行...
snapshot = tracemalloc.take_snapshot()
top = snapshot.statistics('lineno')
print(top[:5])
# オブジェクト参照グラフ:
import objgraph # サードパーティ
# objgraph.show_refs([obj])
# 対策:
# 分割処理、参照の解放、ジェネレータを使う

10.クラスとオブジェクト

class の定義、継承、特殊(ダンダー)メソッド、dataclass、enum。

クラスの基礎

class で型を定義します。__init__ で初期化し、self がインスタンスを指します。インスタンスメソッドはインスタンスに束縛されます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def greet(self):
return f'Hi, {self.name}'
p = Person('Nick', 30)
p.greet()
# クラス属性(共有):
class Counter:
total = 0 # クラスレベル
def __init__(self):
Counter.total += 1
# インスタンス属性(個別):
p.name
# 動的属性:
p.email = '[email protected]'
# 型チェック:isinstance(p, Person)

属性アクセス

インスタンス属性はクラス属性より優先されます。setattr/getattr/hasattr で動的に扱い、property で算出属性を定義します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class User:
default_role = 'user' # クラス属性
def __init__(self, name):
self.name = name
# 属性の探索:インスタンス -> クラス -> 基底クラス
u = User('a')
u.default_role # 'user'
# 動的アクセス:
hasattr(u, 'name') # True
getattr(u, 'name', None)
setattr(u, 'role', 'admin')
# property 属性:
class Circle:
def __init__(self, r):
self._r = r
@property
def area(self):
return 3.14 * self._r ** 2
c = Circle(2)
c.area # メソッドを属性としてアクセス
# property の setter で検証

3 種類のメソッド

インスタンスメソッドは self、クラスメソッドは cls、staticmethod はどちらも取りません。@staticmethod/@classmethod で修飾します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Tool:
count = 0
def __init__(self, name):
self.name = name
Tool.count += 1
def instance_method(self):
return self.name # インスタンスにアクセス
@classmethod
def class_method(cls):
return cls.count # クラスにアクセス
@staticmethod
def helper(x):
return x * 2 # self/cls なし
# 呼び出し:
t = Tool('锤子')
t.instance_method()
Tool.class_method()
Tool.helper(4)
# クラスメソッドはファクトリ/カウンタに
# 静的メソッドはユーティリティ関数に

継承

サブクラスは親クラスのメソッドを継承します。super() で親を呼び、オーバーライドで上書き。多継承は MRO に従います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f'{self.name} 发出声音'
class Dog(Animal):
def speak(self):
return f'{self.name} 汪汪'
def fetch(self):
return f'{self.name} 捡球'
d = Dog('Rex')
d.speak() # オーバーライド
# super() で親クラスを呼ぶ:
class Cat(Animal):
def __init__(self, name, color):
super().__init__(name)
self.color = color
# isinstance と issubclass
# 多重継承は MRO の順に探索
# ダックタイピング:継承よりプロトコルを多用

ダンダーメソッド

ダブルアンダースコアのメソッドは組み込みの振る舞いをカスタマイズします: __repr__ 表示、__eq__ 等価、__len__ 長さ、__add__ 加算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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):
return Vector(self.x + other.x, self.y + other.y)
def __eq__(self, other):
return (self.x, self.y) == (other.x, other.y)
def __len__(self):
return 2
v = Vector(1, 2)
str(v); v + v; v == Vector(1, 2)
# よく使うもの:
# __str__ ユーザー向け
# __getitem__ インデックスアクセス
# __call__ オブジェクトを呼び出し可能に
# __bool__ 真偽判定
# __enter__/__exit__ コンテキストマネージャ

dataclass

@dataclass は __init__、__repr__、__eq__ などを自動生成する宣言的な書き方です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from dataclasses import dataclass, field
@dataclass
class Point:
x: int
y: int
label: str = 'origin' # デフォルト値
tags: list = field(default_factory=list)
p = Point(1, 2)
p2 = Point(1, 2)
p == p2 # True(自動 __eq__)
repr(p) # Point(x=1, y=2, label='origin')
# 凍結(不変):
@dataclass(frozen=True)
class Config:
debug: bool = False
# ソート:
@dataclass(order=True)
class Item:
price: int
# 辞書への変換:
from dataclasses import asdict, astuple
asdict(p)
# 継承フィールドも処理される

Enum 列挙

Enum は名前付きの定数集合を定義します。str 、自動採番、反復をサポートし、マジックナンバーの置き換えに使えます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from enum import Enum, auto
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
class Status(Enum):
ACTIVE = auto() # 自動採番
INACTIVE = auto()
# アクセス:
Color.RED
Color(1) # 値で検索
Color.RED.value # 1
Color.RED.name # 'RED'
# 走査:
for c in Color: pass
# 文字列 Enum:
class Mode(str, Enum):
FAST = 'fast'
SLOW = 'slow'
# 比較:
Color.RED is Color.RED # True シングルトン
# 説明:
class Kind(Enum):
A = ('a', '描述')
def __init__(self, code, desc):
self.code = code
self.desc = desc

コンテキストマネージャ

with がリソース管理を担います。__enter__/__exit__ を実装するか、@contextlib.contextmanager でジェネレータとして書きます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Timer:
def __enter__(self):
import time
self.start = time.perf_counter()
return self
def __exit__(self, exc_type, exc, tb):
import time
print(f'耗时 {time.perf_counter()-self.start:.4f}s')
return False # False は例外を飲み込まない
with Timer():
pass
# 簡易版:
from contextlib import contextmanager
@contextmanager
def timer():
import time
t = time.perf_counter()
yield
print(f'耗时 {time.perf_counter()-t:.4f}s')
with timer():
pass
# with open(...) は組み込みコンテキストマネージャ
# contextlib.suppress / ExitStack

11.例外処理

try-except、例外階層、自作例外、with によるリソース管理。

try / except

例外を捕捉してエラーを処理します。except は型で絞り込み、as e で例外オブジェクトを取得できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
try:
num = int('abc')
except ValueError:
print('无法转换')
# 捕捉して例外にアクセス:
except ValueError as e:
print(e)
# 複数型:
except (ValueError, TypeError):
pass
# 順序:より具体的な型を先に
try:
result = risky()
except (ValueError, KeyError) as e:
print(f'可预期错误: {e}')
except Exception as e:
print(f'未知错误: {e}')
# 捕捉しないと上に伝播しクラッシュする

else と finally

else は例外なしのときに実行され、finally は成功失敗に関わらず実行されるため、クリーンアップに適します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
try:
data = parse(input_data)
except ValueError as e:
print(f'解析失败: {e}')
else:
print('解析成功') # 例外がないときだけ実行
finally:
cleanup() # 常に実行
# 組み合わせ:
# try にはエラーになり得るコード
# else には成功時だけのロジック
# finally には後始末(接続解除/解放)
# finally は例外の伝播を止めない
# else か finally は省略可能

例外階層

Exception が多くの例外の基底クラスで、代表例として ValueError、TypeError、KeyError、IndexError があります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
try:
d['missing'] # KeyError
except KeyError:
pass
try:
[1][5] # IndexError
except IndexError:
pass
try:
int('x') # ValueError
except ValueError:
pass
try:
1 / 0 # ZeroDivisionError
except ZeroDivisionError:
pass
# 階層:
# BaseException > Exception > 具体的なエラー
# KeyboardInterrupt/SystemExit は Exception ではない
# except Exception で業務エラーを捕捉
# 裸の except: は避ける(終了まで飲み込む)

自作例外

Exception を継承して業務例外を定義し、フィールドやメッセージを持たせます。呼び出し側は型で捕捉します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ValidationError(Exception):
pass
class NotFoundError(Exception):
def __init__(self, entity, key):
self.entity = entity
self.key = key
super().__init__(f'{entity} 不存在: {key}')
# スロー:
def get_user(user_id):
if user_id == 0:
raise NotFoundError('用户', user_id)
return {'id': user_id}
# 捕捉:
try:
get_user(0)
except NotFoundError as e:
print(e.entity, e.key)
# 基底クラスでまとめて処理:
class AppError(Exception): pass
# 派生 SpecificError(AppError)

raise と連鎖

raise で能動的に投げ、raise ... from ... で原因を連鎖させます。except 内の素の raise は現在の例外を再送出します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def validate(age):
if age < 0:
raise ValueError('年龄不能为负')
# 再スロー:
try:
risky()
except SomeError:
raise # そのまま投げ直す
# 例外チェーン(原因を保持):
try:
parse_file(path)
except FileNotFoundError as e:
raise RuntimeError(f'读取失败: {path}') from e
# チェーンを抑制:
raise NewError('x') from None
# チェーン表示:Traceback (most recent call last):
# 原因が先,現在のが後
# デバッグ時は __cause__ を保持

assert

assert は開発時の不変条件チェックです。python -O で無効化されるため、ユーザー入力の検証には絶対に使わないでください。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def process(data):
assert data is not None, 'data 不能为空'
assert isinstance(data, list), '期望列表'
return len(data)
# アサーション失敗で AssertionError
# アサーションを無効にして実行:
# python3 -O script.py
# 用途:
# 1. 内部不変条件
# 2. デバッグ期の引数チェック
# 3. 型の前提条件
# assert に使わないこと:
# ユーザー入力の検証
# セキュリティ境界の検査
# 重要な業務ロジック

例外とロギング

logging を使うとスタック付きで例外を記録できます。except ブロック内では logger.exception が定番です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import logging
logging.basicConfig(level=logging.INFO)
try:
x = 1 / 0
except ZeroDivisionError:
logging.exception('除法出错') # スタック付き
# または:
logger.error('出错', exc_info=True)
# レベル:
logging.debug/info/warning/error
# 設定:
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s %(levelname)s %(message)s',
)
# 構造化:
logger.info('请求', extra={'id': req_id})
# ログに print は使わない(レベル/タイムスタンプがない)

エラー処理のパターン

EAFP(許可を求めるより許しを請う)と LBYL(よく見てから飛ぶ)。ガード節で早期 return を使うとネストが減ります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# EAFP:試してから捕捉
def parse_eafp(s):
try:
return int(s)
except ValueError:
return None
# LBYL:確認してから操作
def parse_lbyl(s):
if not s.isdigit():
return None
return int(s)
# 汎用捕捉 vs 精密捕捉:
# 精密:except ValueError
# フォールバック:except Exception
# ガード節で早期リターン:
def handle(user):
if user is None:
return
if not user.active:
return
process(user)
# ネストを減らし,ロジックをフラットに

12.入出力

print/input、ファイル入出力、pathlib、JSON/CSV、標準ストリーム。

出力と入力

print は複数引数、sep/end で区切りと末尾を制御できます。input は 1 行を str で読み取ります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
print('a', 'b') # a b
print('a', 'b', sep=', ') # a, b
print('end', end='!\n')
# ファイルに出力:
with open('out.txt', 'w') as f:
print('text', file=f)
# 入力:
name = input('你的名字? ') # 常に str
# 数値入力:
age = int(input('年龄? '))
# 一度に複数入力:
a, b = input().split()
# 変数を出力:
print(f'Hi {name}')
# コンソールの進捗:
print('\r进度 50%', end='')
# バッファをフラッシュ:print(..., flush=True)

ファイルの読み書き

open のモード: r 読み込み、w 書き込み、a 追記、b バイナリ。読み込みは read/readline/readlines。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 読む:
f = open('data.txt', encoding='utf-8')
text = f.read() # 全部
line = f.readline() # 一行
lines = f.readlines() # 行のリスト
f.close()
# 書く:
f = open('out.txt', 'w', encoding='utf-8')
f.write('内容\n')
f.writelines(['a', 'b'])
f.close()
# モード:
# 'r' 読む 'w' 上書き 'a' 追記
# 'rb'/'wb' バイナリ
# 'r+' 読み書き
# 必ず with で自動クローズ

with とファイル

with は例外発生時もファイルをクローズします。ファイルを扱う際のイディオムです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 推奨の書き方:
with open('data.txt', encoding='utf-8') as f:
text = f.read() # 使い終わったら自動クローズ
# 行ごと:
with open('data.txt', encoding='utf-8') as f:
for line in f:
print(line.rstrip())
# 書く:
with open('out.txt', 'w', encoding='utf-8') as f:
f.write('hello')
# 追記:
with open('log.txt', 'a') as f:
f.write('更多')
# エラー処理:
try:
with open('missing.txt') as f:
pass
except FileNotFoundError:
print('文件不存在')
# エンコーディング指定は必須,クロスプラットフォームで一致

pathlib のパス

Path はプラットフォーム非依存のパス操作(/、 exists、mkdir、read_text など)を提供し、os.path の現代版です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pathlib import Path
p = Path('data') / 'sub' / 'file.txt'
p.exists() # 存在するか
p.is_file(); p.is_dir()
p.mkdir(parents=True, exist_ok=True)
# 読み書き:
text = Path('data.txt').read_text(encoding='utf-8')
Path('out.txt').write_text('hi', encoding='utf-8')
# 走査:
for f in Path('.').glob('*.py'):
print(f.name)
# rglob 再帰:
Path('.').rglob('*.json')
# 属性:
p.name; p.suffix; p.stem
p.parent; p.absolute()
# よく使うディレクトリ:
Path.home()
Path.cwd()
# 記述が簡単で,クロスプラットフォーム統一

JSON の読み書き

json.loads/dumps でシリアライズし、dict/list と JSON を相互変換します。ensure_ascii=False で日本語などをそのまま保てます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import json
data = {'name': 'Nick', 'tags': ['a', 'b']}
# シリアライズ:
text = json.dumps(data, ensure_ascii=False)
# 整形:
json.dumps(data, indent=2, ensure_ascii=False)
# デシリアライズ:
obj = json.loads(text)
# ファイル読み書き:
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:
obj = json.load(f)
# エラー:
try:
json.loads('invalid')
except json.JSONDecodeError as e:
print(e)
# 型:JSON オブジェクト <-> dict

CSV の読み書き

csv モジュールでカンマ区切りを扱います。writer で書き込み、reader / DictReader で読み込みます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import csv
rows = [['name', 'age'], ['Nick', 30], ['Anna', 25]]
# 書く:
with open('data.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerows(rows)
# 読む:
with open('data.csv', newline='', encoding='utf-8') as f:
reader = csv.reader(f)
for row in reader:
print(row)
# 辞書で読み書き:
with open('data.csv', encoding='utf-8') as f:
for row in csv.DictReader(f):
print(row['name'])
# 辞書を書く:
with open('o.csv', 'w', newline='') as f:
w = csv.DictWriter(f, fieldnames=['name'])
w.writeheader()
w.writerow({'name': 'x'})
# 引用符/改行の扱いに注意

標準ストリーム

sys.stdin/stdout/stderr でパイプデータを扱い、stdin を 1 行ずつ処理します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import sys
# 一行読む:
line = sys.stdin.readline()
# 全部を行ごとに(パイプ):
for line in sys.stdin:
sys.stdout.write(line.upper())
# エラーストリーム:
print('error', file=sys.stderr)
# パイプの使い方:
# cat data.txt | python3 filter.py
# 出力:
# python3 filter.py > out.txt
# エンコーディング:
# PYTHONIOENCODING=utf-8 でストリームのエンコーディングを制御
# input() は sys.stdin.readline() と同等
# 進捗は stderr に出力し stdout を汚さない

バイナリ I/O

バイナリモードでバイト列を読み書きします。struct でパック/アンパックし、seek/tell でランダムアクセス。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# バイトの読み書き:
with open('img.bin', 'rb') as f:
data = f.read()
with open('out.bin', 'wb') as f:
f.write(b'\x00\x01\x02')
# 固定サイズで分割:
with open('file.bin', 'rb') as f:
while chunk := f.read(1024):
process(chunk)
# struct でパック:
import struct
packed = struct.pack('>I', 1024) # 4 バイト
value = struct.unpack('>I', packed)[0]
# ランダムアクセス:
with open('db.bin', 'rb') as f:
f.seek(100) # オフセットへ移動
data = f.read(10)
# 末尾追記:'ab' モード

13.よくある落とし穴

Python の日常開発で踏みやすい落とし穴と正しい書き方。

可変なデフォルト引数

可変なデフォルト引数は関数定義時に 1 度だけ作られ、呼び出し間で共有されます。None をセンチネルにしましょう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# BAD:共有されるデフォルトリスト
def add(item, basket=[]):
basket.append(item)
return basket
add('a') # ['a']
add('b') # ['a', 'b'] !
# GOOD:None + 関数内で作成
def add(item, basket=None):
if basket is None:
basket = []
basket.append(item)
return basket
# 原因:デフォルト値は定義時に一度だけ評価
# 影響:list/dict/set すべて影響を受ける

is と == の混同

is は同一性、== は値を比較します。小さな整数/短い文字列はインターン化されることがありますが、内容比較に is を使わないでください。

1
2
3
4
5
6
7
8
9
10
11
12
13
# BAD:is で内容を比較
a = [1, 2]
b = [1, 2]
a is b # False,だが値は同じ
# GOOD:== で値を比較
a == b # True
# None の判定は is:
x = None
x is None # True
# インターンに注意:
# CPython では 257 is 257 が True になり得る
# ただし実装詳細なので依存しない
# ルール:内容は ==,シングルトン/None は is

ループ内クロージャの落とし穴

ループ内の lambda は変数を実行時に評価するため遅れて束縛されます。今の値を必要とするなら既定引数で固定します。

1
2
3
4
5
6
7
8
9
10
11
# BAD:全部 9 を出力
funcs = [lambda: i for i in range(10)]
funcs[0]() # 9
# GOOD:現在の値を束縛
funcs = [lambda i=i: i for i in range(10)]
funcs[0]() # 0
# またはデフォルト引数で捕捉:
funcs = [(lambda x: lambda: x)(i) for i in range(10)]
# 原因:クロージャは値ではなく変数名を捕捉
# 修正:デフォルト引数は定義時に評価
# 注意:for ループ変数は終了後も残る

文字列連結

ループで += 連結すると O(n^2) になります。リストに集めてから join するか、io.StringIO を使いましょう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# BAD:ループ内で連結
s = ''
for i in range(10000):
s += str(i) # 毎回新しい文字列を作成
# GOOD:集めてから join
parts = []
for i in range(10000):
parts.append(str(i))
s = ''.join(parts)
# またはジェネレータ:
s = ''.join(str(i) for i in range(10000))
# 少しだけなら + で問題なし:
name = first + ' ' + last
# ルール:多数の連結は join を使う
# f-string は既知の断片に適する

反復中のリスト変更

反復中にリストを変更すると要素の飛ばし/重複が生じます。コピーに対して反復するか、新しくリストを作り直しましょう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# BAD:要素を削除してスキップ
items = [1, 2, 3, 4]
for x in items:
if x % 2 == 0:
items.remove(x) # インデックスがずれる
# GOOD:新しいリストを作り直す
items = [x for x in items if x % 2 == 1]
# またはコピーを走査:
for x in items[:]:
if x % 2 == 0:
items.remove(x)
# dict を走査中にキーを変更するのも危険
# 原則:イテレータが無効 -> コピーを走査
# または新しいコンテナを作る

組み込み名のシャドーイング

組み込み名を変数に使うと元の関数が隠されます。list、str、input、sum などの上書きは避けましょう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# BAD:組み込みを遮蔽
def total(data):
sum = 0 # 組み込み sum を上書き
for x in data:
sum += x
return sum
# 以後 sum() を使うとエラーになる
# GOOD:別の名前を使う
total = 0
def total(data):
return sum(data)
# よくある罠:
# list = [] で list() を遮蔽
# input = ... で input() を遮蔽
# type/str/dict も同様
# リスト内包表記の変数はリークしない(3+ 独立スコープ)

例外の飲み込み

素の except は KeyboardInterrupt まで含めて全部飲み込みます。最低限ログするか、具体的な型で絞り込みましょう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# BAD:裸の except で黙って飲み込む
import os
try:
os.remove('f.txt')
except:
pass # 何も分からない
try:
value = int(x)
except Exception:
value = 0 # 型エラーを隠す
try:
risky()
except Exception:
print('出错了') # 何のエラーかも不明
# GOOD:精密に捕捉 + 記録
import logging
try:
value = int(x)
except (ValueError, TypeError) as e:
logging.warning('转换失败 %s', e)
value = 0
# KeyboardInterrupt/SystemExit は飲み込まない

グローバル変数の誤用

関数内で代入すると新しいローカルになり、グローバルは変わりません。global 宣言が必要です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# BAD:グローバルを変更したつもりがエラー
count = 0
def inc():
count += 1 # UnboundLocalError
# 原因:+= は代入であり,ローカル count を作る
# GOOD:global を宣言
def inc():
global count
count += 1
# 読み取りだけなら宣言不要:
def show():
print(count)
# グローバルの乱用を避ける:
# 引数で渡す + 戻り値で返す
# またはクラスで状態をカプセル化
# 設定オブジェクトはモジュールレベルで読み取り専用

コピーとソート

list.sort はその場でソートし、sorted は新しいリストを返します。共有参照による想定外の変更に注意。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# BAD:sort はその場で変更
original = [3, 1, 2]
sorted_list = original.sort() # None を返す
# 元のリストもソートされる
# GOOD:sorted は新しいリストを返す
original = [3, 1, 2]
sorted_list = sorted(original)
# または copy してから sort:
c = original[:]; c.sort()
# 参照の共有:
b = original
b.append(9) # original も変わる
# 独立が必要ならコピー:
new_list = original[:]
# 関数内で引数を変更すると呼び出し側に影響:
# copy を渡す

14.並行と非同期

threading、asyncio、GIL、そして並行処理モデルの選び方。

スレッドの基礎

threading.Thread でスレッドを作り、start で起動、join で完了を待ち、daemon=True でデーモン化します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import threading
def work(n):
print(f'线程 {n} 工作')
threads = []
for i in range(3):
t = threading.Thread(target=work, args=(i,))
threads.append(t)
t.start()
for t in threads:
t.join() # 完了を待つ
# スレッドオブジェクトに引数:
threading.Thread(target=work, args=(1,), daemon=True)
# daemon スレッドはメインスレッドと共に終了
# スレッド数は増やしすぎない,コンテキストスイッチのオーバーヘッド回避
# 単純な並行シーンなら十分

スレッドクラス

Thread を継承して run をオーバーライドします。データ共有にはロックを使い、ワーカースレッドをきれいにまとめられます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import threading
class Worker(threading.Thread):
def __init__(self, name):
super().__init__(name=name)
self.result = None
def run(self):
self.result = heavy_compute()
w = Worker('job-1')
w.start()
w.join()
print(w.result)
# start は上書きせず run を上書きする
# データはインスタンス属性で返す
# 例外はサブスレッドで黙る:
# run 内で try-except 記録
# 戻り値が必要なら ThreadPoolExecutor 優先

ロックと同期

Lock が共有状態を守り、with lock で取得/解放を自動化します。RLock は同じスレッドから再入できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import threading
lock = threading.Lock()
counter = 0
def increment():
global counter
for _ in range(1000):
with lock: # 自動で取得/解放
counter += 1
threads = [threading.Thread(target=increment) for _ in range(10)]
for t in threads: t.start()
for t in threads: t.join()
print(counter) # 10000(ロックで保護)
# RLock:同一スレッドで再取得可能
# 条件変数:Condition
# イベント:Event
# セマフォ:Semaphore で並行数を制御
# デッドロック:ロック順の不一致、ロック保持のまま待機
# Queue でデータを渡せばロック不要,優先

GIL と並列処理

GIL のため CPython のスレッドは CPU バウンドを並列化できません。I/O バウンドでは依然として有効です。

1
2
3
4
5
6
7
8
9
10
11
12
13
# GIL:グローバルインタプリタロック
# 同一時刻に 1 スレッドだけがバイトコードを実行
# CPU 集約タスクではマルチスレッドは加速しない:
# 計算タスクはスレッドだと逆に遅い
# I/O 集約タスクではスレッドが有効:
# ネットワーク/ディスク待ちで GIL を譲る
# 本当の並列 CPU 集約:
# 1. multiprocessing でマルチプロセス
# 2. C 拡張で GIL を解放(numpy など)
# 3. asyncio でシングルスレッド非同期
# 判断:
# ボトルネックが I/O -> スレッド/asyncio
# ボトルネックが CPU -> プロセス

マルチプロセス

multiprocessing は CPU バウンドをプロセスプールで並列化し、ProcessPoolExecutor が高水準のラッパーを提供します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from multiprocessing import Pool
def square(n):
return n * n
with Pool(4) as pool:
results = pool.map(square, range(10))
# ProcessPoolExecutor:
from concurrent.futures import ProcessPoolExecutor
with ProcessPoolExecutor(max_workers=4) as ex:
results = list(ex.map(square, range(10)))
# 独立メモリ:プロセス間で共有しない
# 通信:Queue/Pipe
# 起動オーバーヘッドはスレッドより大きい
# 大量データはシリアライズのオーバーヘッドに注意
# if __name__ == '__main__' と組み合わせる

asyncio の基礎

async def でコルーチン、await で中断、asyncio.run がイベントループを駆動します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import asyncio
async def say_hi():
await asyncio.sleep(1)
print('hi')
asyncio.run(say_hi())
# await はコルーチンを一時停止し制御を譲る
# 並行実行:
async def main():
task = asyncio.create_task(say_hi())
await task
asyncio.run(main())
# イベントループ:
# 同一スレッド内でコルーチンをスケジューリング
# ブロッキングコードはループを止める:
# time.sleep -> await asyncio.sleep
# ブロッキングライブラリは非同期版へ:httpx/aiohttp
# 3.11+ TaskGroup でタスク管理

async / await

async でコルーチンを宣言し、await で結果を待ちます。gather で並列実行、asyncio.timeout でタイムアウトを指定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import asyncio
async def fetch(url):
await asyncio.sleep(0.1)
return f'data from {url}'
# 直列:
async def main():
a = await fetch('a')
b = await fetch('b')
# 並列:
results = await asyncio.gather(
fetch('a'), fetch('b'), fetch('c'))
# タイムアウト:
try:
result = await asyncio.wait_for(fetch('a'), timeout=1)
except asyncio.TimeoutError:
print('超时')
# タスクをまとめて作成:
tasks = [fetch(u) for u in urls]
await asyncio.gather(*tasks)
# 戻り値は渡した順,例外は伝播

非同期 I/O

aiohttp / httpx で非同期 HTTP、aiofiles で非同期ファイル I/O。I/O バウンドでは飛躍的にスループットが向上します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# httpx 非同期クライアント:
# pip install httpx
# import httpx
async def main():
async with httpx.AsyncClient() as client:
r = await client.get('https://example.com')
return r.status_code
# 同期コードと混在:
async def worker():
loop = asyncio.get_event_loop()
result = await loop.run_in_executor(None, blocking_fn)
# 非同期ファイル:
import aiofiles
# async with aiofiles.open('f', 'r') as f:
# text = await f.read()
# 大量の並行 I/O でスループットが大きく向上
# データベース:asyncpg/aiomysql

エグゼキュータ

ThreadPoolExecutor がスレッドプール、ProcessPoolExecutor がプロセスプール。どちらも submit / map を共通 API で提供します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from concurrent.futures import ThreadPoolExecutor, as_completed
def work(n):
return n * 2
with ThreadPoolExecutor(max_workers=4) as ex:
futures = [ex.submit(work, i) for i in range(10)]
for f in as_completed(futures):
print(f.result()) # 完了順
# map は順序どおり:
with ThreadPoolExecutor(4) as ex:
results = list(ex.map(work, range(10)))
# 例外:
# f.result() が元の例外を投げる
# shutdown(wait=True) で待機
# スレッドプールは I/O 集約に適する
# プロセスプールは CPU 集約に適する
# 3.9+ キャンセル:f.cancel()

15.ネットワークとモジュール

モジュールシステム、HTTP リクエスト、URL 処理、サーバー。

モジュールとインポート

import でモジュールを読み込み、from x import y で特定の名前を取り込みます。パッケージは __init__.py を持つディレクトリです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# インポート:
import os
import os.path
import json as js # エイリアス
from datetime import datetime, timedelta
from collections import * # 非推奨
# 相対インポート(パッケージ内):
# from . import utils
# from ..other import x
# インポートの仕組み:
# sys.path の検索パス
# キャッシュ:同じモジュールは 1 回だけ読み込む
# __main__ エントリ
# カスタムモジュール:
# utils.py が同じディレクトリに
# import utils
# 遅延インポート:関数内 import で起動時間を短縮

HTTP リクエスト

requests ライブラリ: GET/POST、クエリパラメータ、ヘッダー、JSON ボディ、ステータスコードの判定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import requests
# GET:
r = requests.get('https://api.example.com/users',
params={'page': 2}, timeout=5)
# ステータス:
r.status_code == 200
r.ok
# データ:
r.json() # dict
r.text # テキスト
r.content # バイト
# POST:
resp = requests.post('https://api.example.com/login',
json={'user': 'nick'},
headers={'Authorization': 'Bearer x'},
timeout=10)
# エラー:
try:
r.raise_for_status()
except requests.HTTPError:
print('请求失败')
# セッション(cookie 保持):
s = requests.Session()

urllib 標準ライブラリ

urllib.request はサードパーティ不要の HTTP クライアント、urllib.parse は URL パーサです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import urllib.request
with urllib.request.urlopen('https://example.com') as resp:
data = resp.read()
print(resp.status)
# エンコーディング引数:
req = urllib.request.Request('https://api.com',
headers={'User-Agent': 'my-app'},
data=b'body')
with urllib.request.urlopen(req) as resp:
print(resp.read().decode('utf-8'))
# URL 解析:
from urllib.parse import urlparse, urlencode, quote
urlparse('https://a.com/p?q=1#f')
# ParseResult(scheme='https', netloc='a.com', path='/p', query='q=1')
urlencode({'a': 1, 'b': 'x y'}) # 'a=1&b=x+y'
quote('中文') # パーセントエンコーディング
# 簡潔なリクエストは requests,標準ライブラリの urllib がフォールバック

簡易 HTTP サーバー

http.server は静的ファイルや簡易サーバーを提供し、BaseHTTPRequestHandler をサブクラス化して拡張できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 静的ファイルサービス:
# python3 -m http.server 8000
# 閲覧 http://localhost:8000
# バインドアドレス:
# python3 -m http.server 8000 --bind 0.0.0.0
# カスタム処理:
from http.server import HTTPServer, BaseHTTPRequestHandler
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
body = b'{"ok": true}'
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.send_header('Content-Length', str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, fmt, *args):
pass # サイレント
HTTPServer(('', 8000), Handler).serve_forever()
# 本番はフレームワーク:FastAPI/Flask

ソケット通信

socket は低水準の TCP/UDP インターフェースで、ソケット接続、送受信、パケット解析に使えます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import socket
# TCP クライアント:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(('example.com', 80))
s.sendall(b'GET / HTTP/1.1\r\nHost: example.com\r\n\r\n')
data = s.recv(4096)
print(data[:200])
# TCP サーバー:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
server.bind(('0.0.0.0', 9000))
server.listen(5)
conn, addr = server.accept()
with conn:
data = conn.recv(1024)
conn.sendall(b'pong')
# UDP:SOCK_DGRAM,sendto/recvfrom
# タイムアウト:s.settimeout(3)
# 複雑なプロトコルはフレームワーク優先

URL とパラメータ

urlparse で URL を分解、urlencode でクエリ文字列を構築、quote でパーセントエンコード、urljoin で相対パスを解決します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from urllib.parse import urlparse, urlencode, quote, urljoin
# 解析:
p = urlparse('https://[email protected]:8080/path?q=1#sec')
p.scheme; p.netloc; p.path
p.query; p.fragment
# クエリ文字列を組み立てる:
params = urlencode({'q': 'python 教程', 'page': 2})
# 'q=python+%E6%95%99%E7%A8%8B&page=2'
url = f'https://api.com/search?{params}'
# 連結:
urljoin('https://a.com/docs/', '../about')
# デコード:
from urllib.parse import unquote
unquote('%E4%B8%AD') # '中'
# パスの安全化:
quote('a/b', safe='') # スラッシュをエスケープ
# パラメータを解析:
from urllib.parse import parse_qs
parse_qs('a=1&a=2&b=3') # {'a': ['1','2'], 'b': ['3']}

設定と環境

os.environ で環境変数にアクセスし、python-dotenv で .env を読み込みます。設定は階層化して管理します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import os
# 環境変数を読む:
port = int(os.environ.get('PORT', '3000'))
secret = os.environ['SECRET_KEY'] # 欠落で KeyError
# 設定:
os.environ['DEBUG'] = '1'
# 判定:
if os.environ.get('DEBUG') == '1':
print('调试模式')
# dotenv:
# pip install python-dotenv
from dotenv import load_dotenv
load_dotenv() # .env を読み込む
# .env ファイル:
# PORT=3000
# SECRET_KEY=xxx
# 用途:
# デプロイ設定とコードを分離
# キーをバージョン管理に入れない
# 設定検証:欠落は即エラー
# レイヤー:default/dev/prod

API 呼び出しパターン

リトライ、レート制限、エラー処理をラップします。pydantic でレスポンスを型付きバリデーションするのが定番です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import requests
from time import sleep
def api_get(url, retries=3):
for attempt in range(retries):
try:
r = requests.get(url, timeout=10)
if r.status_code == 429: # レート制限
sleep(2 ** attempt) # バックオフ
continue
r.raise_for_status()
return r.json()
except requests.RequestException:
if attempt == retries - 1:
raise
sleep(2 ** attempt)
# 共通リクエストヘッダ:
HEADERS = {'User-Agent': 'my-app/1.0'}
# レスポンス検証(pydantic):
# pip install pydantic
# from pydantic import BaseModel
# class User(BaseModel):
# name: str
# age: int
# u = User.model_validate(r.json())
# キャッシュ:lru_cache / diskcache

16.日時

datetime、フォーマット、timedelta、タイムゾーン。

datetime の基礎

datetime のコンストラクタ、date / time サブクラス、属性アクセス、比較演算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from datetime import datetime, date, time
# 現在時刻:
now = datetime.now() # ローカル
now_utc = datetime.utcnow() # 非推奨,timezone を使う
# 構築:
d = datetime(2026, 8, 2, 12, 30, 0)
# アクセス:
d.year; d.month; d.day
d.hour; d.minute; d.second
# 曜日:
d.weekday() # 0=月曜
d.isoweekday() # 1=月曜
# 日付/時刻のみ:
date(2026, 8, 2)
time(12, 30)
# 比較:
d > datetime(2026, 1, 1)
# 不正な月を指定すると ValueError

フォーマットと解析

strftime でフォーマット、strptime で解析。指定子: %Y 年、%m 月、%d 日、%H 時。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from datetime import datetime
now = datetime.now()
# フォーマット:
now.strftime('%Y-%m-%d') # '2026-08-02'
now.strftime('%Y-%m-%d %H:%M:%S')
now.strftime('%H:%M') # 時分
now.strftime('%A') # 曜日の英語
# 文字列を解析:
d = datetime.strptime('2026-08-02', '%Y-%m-%d')
d = datetime.fromisoformat('2026-08-02T12:00:00')
# よく使う指令:
# %Y 4 桁年 %y 2 桁年
# %m 月 %d 日 %H 24 時間 %I 12 時間
# %M 分 %S 秒 %f マイクロ秒
# 日本語の曜日:カスタムマッピング
# タイムゾーン安全:先に tz を付けてからフォーマット

timedelta

timedelta で日/時間などの差を表現し、加減算や 2 つの日付の差を求めます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from datetime import datetime, timedelta
now = datetime.now()
# 7 日加える:
now + timedelta(days=7)
# 2 時間加える:
now + timedelta(hours=2)
# 30 分引く:
now - timedelta(minutes=30)
# 差を計算:
d1 = datetime(2026, 8, 2)
d2 = datetime(2026, 1, 1)
delta = d1 - d2
delta.days # 213
delta.total_seconds() # 秒
# 相対パラメータ:
# weeks/days/hours/minutes/seconds
# 時間差は比較可能:
if delta > timedelta(days=100):
print('超过百天')
# 月差は自分で計算:
# timedelta に月単位はない

タイムゾーン

timezone は固定オフセット、zoneinfo(3.9+)は IANA 名でタイムゾーンを表します。保存は UTC、表示は現地の運用が堅実です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from datetime import datetime, timezone, timedelta
# 固定オフセット:
utc8 = timezone(timedelta(hours=8))
now_utc8 = datetime.now(utc8)
# 名前付きタイムゾーン(3.9+):
from zoneinfo import ZoneInfo
shanghai = ZoneInfo('Asia/Shanghai')
now_sh = datetime.now(shanghai)
# 変換:
utc_time = datetime.now(timezone.utc)
local_time = utc_time.astimezone(shanghai)
# タイムゾーン認識 vs naive:
# tzinfo を持つのが認識型
# 比較には同じタイムゾーンか両方認識が必要
# 実践:
# データベースには UTC を保存
# 表示時にユーザーのタイムゾーンへ変換
# tzdata パッケージがタイムゾーンライブラリを提供

タイムスタンプ

Unix 秒のタイムスタンプを .timestamp() で取得、fromtimestamp() で復元。比較と保存に便利です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import time
from datetime import datetime, timezone
# unix タイムスタンプ(秒):
now_ts = time.time()
datetime.now().timestamp()
# datetime をタイムスタンプへ:
dt = datetime(2026, 8, 2, tzinfo=timezone.utc)
dt.timestamp()
# タイムスタンプを datetime へ:
datetime.fromtimestamp(now_ts) # ローカル
datetime.fromtimestamp(now_ts, timezone.utc) # UTC
# ミリ秒:
int(time.time() * 1000)
# タイムスタンプ比較:
if now_ts > last_ts: pass
# 保存:
# unix 秒の整数が共通標準
# 表示:タイムスタンプからローカルフォーマットへ
# 精度:float 秒に小数を含む

タイミングと待機

time.sleep で待機、time.perf_counter で高精度計測、定期ジョブには schedule ライブラリが便利です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import time
# 待機:
time.sleep(1.5) # 秒
# 計測:
start = time.perf_counter()
# コードを実行...
elapsed = time.perf_counter() - start
print(f'{elapsed:.4f}s')
# 精度:
time.time() # 壁時計,調整されることがある
time.perf_counter() # 高精度モノトニック
# 定期ループ:
def run_every(interval, fn):
while True:
fn()
time.sleep(interval)
# 定期タスクライブラリ:
# pip install schedule
# import schedule
# schedule.every(10).minutes.do(job)
# while True: schedule.run_pending()
# ブロッキング vs 非同期待機:
# イベントループ内では asyncio.sleep を使う

日付ユーティリティ

曜日の判定、月初・月末、相対日付、日付範囲を扱います。calendar モジュールが頼りになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from datetime import datetime, timedelta
import calendar
d = datetime.now()
# 曜日判定:
d.weekday() < 5 # 平日
# 月初:
d.replace(day=1)
# 月末:
last_day = calendar.monthrange(d.year, d.month)[1]
d.replace(day=last_day)
# 日付シーケンス:
start = datetime(2026, 8, 1)
for i in range(7):
day = start + timedelta(days=i)
print(day.strftime('%m-%d %a'))
# 自然言語解析:
# pip install python-dateutil
# from dateutil.parser import parse
# parse('2026-08-02 10:30')
# 月差の計算:
# dateutil.relativedelta
# from dateutil.relativedelta import relativedelta
# d + relativedelta(months=1)

時間区間の判定

時刻が区間内か、区間の重複か、残り時間かを判定します。スケジュールや制限時間タスクで頻出です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from datetime import datetime, timedelta
# 区間の判定:
now = datetime.now()
start = datetime(2026, 1, 1)
end = datetime(2027, 1, 1)
if start <= now < end:
print('在窗口内')
# 区間の重なり:
def overlaps(a1, a2, b1, b2):
return a1 < b2 and b1 < a2
# 残り時間:
dealine = now + timedelta(hours=2)
remaining = deadline - now
if remaining > timedelta(minutes=30):
print('时间充足')
# 定期ウィンドウ:
def in_working_hours(dt):
return 9 <= dt.hour < 18
# 境界の扱い:
# 半開区間 [start, end) で重複を避ける
# 日付をまたぐ場合は正規化に注意
# レート制限:前回タイムスタンプと比較

17.プロセスとシステム

subprocess、sys/os モジュール、コマンドライン引数、シグナル。

subprocess

subprocess.run で外部コマンドを実行し、出力を取得、リターンコードを確認、タイムアウトを設定できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import subprocess
# 実行して出力を捕捉:
result = subprocess.run(
['ls', '-l'],
capture_output=True, text=True)
print(result.returncode) # 0 は成功
print(result.stdout)
print(result.stderr)
# 失敗の検査:
result.check_returncode() # 0 以外で CalledProcessError
# タイムアウト:
subprocess.run(['sleep', '10'], timeout=2)
# エラー:TimeoutExpired
# 安全:
# 引数リストを渡し,shell 文字列を組み立てない
# shell=True はインジェクションのリスク
# リアルタイム出力:subprocess.Popen で行ごと読む

sys モジュール

sys.argv は CLI 引数、sys.exit は終了コード、sys.path はモジュールの検索パスです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import sys
# コマンドライン引数:
sys.argv # ['script.py', 'arg1', 'arg2']
# 終了:
sys.exit(0) # 成功
sys.exit(1) # 失敗
# モジュール検索パス:
sys.path # リスト
sys.path.append('/custom') # 追加
# バージョン:
sys.version_info # (3, 11, ...)
sys.version
# ストリーム:
sys.stdin; sys.stdout; sys.stderr
# プラットフォーム:
sys.platform # 'win32' / 'darwin' / 'linux'
# エンコーディング:
sys.getdefaultencoding() # 'utf-8'
# 再帰制限:
sys.getrecursionlimit()
sys.setrecursionlimit(5000)

os モジュール

os は環境、パス、ディレクトリ操作を提供します。os.getcwd で現在ディレクトリ、os.mkdir で作成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import os
# 環境変数:
os.environ['HOME']
# ディレクトリ:
os.getcwd() # 現在のディレクトリ
os.chdir('/tmp') # 切替
os.mkdir('dir') # ディレクトリ作成
os.makedirs('a/b/c', exist_ok=True)
# ファイル:
os.remove('file')
os.rename('a', 'b')
# パス(os.path):
os.path.join('a', 'b') # クロスプラットフォーム連結
os.path.exists('f')
os.path.isdir('d')
os.path.dirname('/a/b/c') # '/a/b'
os.path.basename('/a/b/c') # 'c'
os.path.splitext('f.txt') # ('f', '.txt')
# 走査:
for root, dirs, files in os.walk('.'):
print(root, files)
# os.path の代わりに pathlib を推奨

argparse

argparse は引数の解析、ヘルプ生成、デフォルト値を提供します。CLI ツールの定番です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import argparse
parser = argparse.ArgumentParser(description='示例工具')
parser.add_argument('input', help='输入文件')
parser.add_argument('-v', '--verbose', action='store_true')
parser.add_argument('-n', '--count', type=int, default=1)
args = parser.parse_args()
print(args.input, args.verbose, args.count)
# 使い方:
# python3 tool.py data.txt -v -n 3
# ヘルプ:
# python3 tool.py --help
# オプション:
parser.add_argument('--out', default='out.txt')
# 排他引数:
# parser.add_mutually_exclusive_group()
# サブコマンド:
# subparsers = parser.add_subparsers()
# 型検証:type=int で自動変換

終了コード

0 が成功、それ以外が失敗。CI/スクリプトは終了コードで成否を判定します。未捕捉の例外はデフォルトで 1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import sys
# 正常終了:
sys.exit(0)
# 失敗:
sys.exit(1)
sys.exit('错误信息') # stderr に出力し,1 で終了
# 未捕捉例外は自動的に終了コード 1
# カスタムコード:
sys.exit(2) # 使い方エラーの慣例
# 終了コードの判定:
# コマンドライン:echo $? (POSIX)
# python3 script.py; echo $?
# 呼び出し側で捕捉:
import subprocess
r = subprocess.run(['python3', 'x.py'])
if r.returncode != 0:
print('失败')
# CI では 0 以外はビルド失敗

ファイルとプロセス

大容量ファイルの処理、ファイルロック、テンポラリファイル、原子的書き込み。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 一時ファイル:
import tempfile, os
with tempfile.NamedTemporaryFile(mode='w', delete=True) as f:
f.write('data')
name = f.name
# 一時ディレクトリ:
with tempfile.TemporaryDirectory() as d:
print(d) # 使い終わったら自動クリーンアップ
# アトミック書き込み(一時に書いてからリネーム):
import os
def atomic_write(path, content):
tmp = path + '.tmp'
with open(tmp, 'w') as f:
f.write(content)
os.replace(tmp, path) # アトミック置換
# ファイルロック:
# pip install filelock
# from filelock import FileLock
# with FileLock('app.lock'):
# 排他操作
# 大きなファイルは分割読み:行ごと/固定ブロック

シグナル処理

signal は Ctrl+C や SIGTERM を捕捉して Graceful shutdown を実現します。POSIX 中心で、Windows は制限あり。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import signal, sys, time
# グレースフル終了フラグ:
running = True
def handle_stop(signum, frame):
global running
print(f'收到信号 {signum},正在退出...')
running = False
signal.signal(signal.SIGINT, handle_stop) # Ctrl+C
signal.signal(signal.SIGTERM, handle_stop) # kill
# メインループ:
while running:
work()
time.sleep(1)
print('清理完成')
# シグナルを無視:
signal.signal(signal.SIGPIPE, signal.SIG_IGN)
# タイムアウトシグナル(POSIX):
# signal.alarm(5) 5 秒後に SIGALRM
# Windows は一部のシグナルのみ対応
# グレースフルシャットダウン:状態を保存、接続を閉じる

デーモンとバックグラウンド

バックグラウンド実行、ログローテーション、デーモン化。supervisor や systemd で運用します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# バックグラウンド実行:
# python3 script.py &
# 出力リダイレクト:
# python3 script.py > out.log 2>&1 &
# nohup でハングアップを無視:
# nohup python3 script.py &
# ログローテーション:
import logging
from logging.handlers import RotatingFileHandler
handler = RotatingFileHandler(
'app.log', maxBytes=1_000_000, backupCount=5)
logging.basicConfig(handlers=[handler])
logging.info('运行中')
# 本番ホスティング:
# systemd unit / supervisor で再起動ポリシー
# シングルインスタンス確認:
# ロックファイル pid で判定
# リソース制限:ulimit

18.正規表現とテキスト

re モジュールのマッチング、検索、置換、グループ、コンパイル。

match と search

re.match は先頭からの一致、re.search は全文検索。Match または None を返します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import re
# search:任意の位置
m = re.search(r'\d+', '订单号 123')
if m:
print(m.group()) # '123'
# match:先頭から
m2 = re.match(r'\d+', '123abc')
# m2.group() == '123'
# マッチ失敗は None を返す:
if re.search(r'xyz', 'abc'): pass
# 位置情報:
m.start(); m.end(); m.span()
# 境界:
re.search(r'\bcat\b', 'a cat eats')
# メタ文字はエスケープが必要:
r'\.' 匹配点号

findall と finditer

findall は全マッチのリストを返し、finditer はイテレータで 1 件ずつ返します(大きい入力に省メモリ)。

1
2
3
4
5
6
7
8
9
10
11
12
13
import re
# findall:すべてのマッチ
nums = re.findall(r'\d+', 'a1b22c333')
# ['1', '22', '333']
# グループがあるとタプルを返す:
re.findall(r'(\d+)-(\d+)', '1-2,3-4')
# [('1','2'), ('3','4')]
# finditer:Match をイテレート
for m in re.finditer(r'\d+', 'a1b22'):
print(m.group(), m.span())
# 位置情報が要るなら finditer
# 大きなテキストでは finditer が省メモリ
# マッチなしは空リスト/空イテレータ

sub での置換

re.sub は正規表現で置換し、置換文字列では \1 などの後方参照、関数を渡せば動的な置換もできます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import re
# 単純置換:
re.sub(r'\s+', ' ', 'a b c') # 'a b c'
# グループ参照:
re.sub(r'(\d{4})-(\d{2})-(\d{2})', r'\3/\2/\1',
'2026-08-02')
# '02/08/2026'
# 関数での置換:
re.sub(r'\d+', lambda m: str(int(m.group()) * 2),
'1 and 2')
# '2 and 4'
# 回数制限:
re.sub(r'a', 'x', 'aaa', count=1) # 'xaa'
# 大文字小文字を無視:
re.sub(r'hello', 'hi', 'HELLO', flags=re.I)

split

re.split は正規表現で分割し、複数区切り、キャプチャ保持、分割上限を指定できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import re
# カンマ/セミコロン/縦棒で分割:
re.split(r'[,;|]', 'a,b;c|d')
# ['a', 'b', 'c', 'd']
# 空白で分割(複数含む):
re.split(r'\s+', 'a b c')
# キャプチャグループは区切り文字を保持:
re.split(r'(,)', 'a,b,c')
# ['a', ',', 'b', ',', 'c']
# 制限:
re.split(r',', 'a,b,c', maxsplit=1)
# ['a', 'b,c']
# 行分割:
re.split(r'\r?\n', text)
# 段落ごと:
re.split(r'\n\s*\n', text)
# str.split が複数区切りを扱えない場合に re.split

グループ

() はキャプチャ、(?P<name>) は名前付き、(?:) は非キャプチャ、| は選択。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import re
# グループ:
m = re.search(r'(\w+)@(\w+)\.(\w+)', '[email protected]')
m.group(0) # '[email protected]' 全体マッチ
m.group(1) # 'a'
m.group(2) # 'b'
m.groups() # ('a','b','com')
# 名前付きグループ:
m = re.search(r'(?P<user>\w+)@(?P<domain>\w+)', '[email protected]')
m.group('user') # 'a'
m.groupdict() # {'user': 'a', 'domain': 'b'}
# 非キャプチャ:
re.search(r'(?:ab)+', 'abab')
# または:
re.search(r'cat|dog', 'a dog')
# グループ参照置換:\1
# ネストしたグループ番号は左括弧から数える

フラグ

re.I は大小無視、re.M は複数行、re.S は . が改行に一致、re.X は冗長モード。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import re
# re.I 大文字小文字を無視:
re.search(r'hello', 'HELLO', re.I)
# re.M 複数行アンカー:
re.search(r'^line', 'a\nline b', re.M)
# re.S ドットが改行にマッチ:
re.search(r'a.b', 'a\nb', re.S)
# re.X 詳細モード(コメント/空白):
pattern = re.compile(r'''
(\d{4}) # 年
[-/]
(\d{2}) # 月
''', re.X)
# フラグの組み合わせ:
re.search(r'x', 'X', re.I | re.M)
# コンパイルして再利用:
pat = re.compile(r'\d+', re.IGNORECASE)
pat.search('a1')
# プリコンパイルで高速化 + オプション固定

よく使うパターン

メール/電話番号/URL/IP/日本語などに使う定番パターンをまとめ、業務フォーマットの検証に使います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import re
# メール:
EMAIL = r'^[^\s@]+@[^\s@]+\.[^\s@]+$'
# 中国本土の携帯番号:
MOBILE = r'^1[3-9]\d{9}$'
# URL:
URL = r'^https?://[^\s]+$'
# IPv4:
IP = r'^(\d{1,3}\.){3}\d{1,3}$'
# 中国語:
CHINESE = re.compile(r'[\u4e00-\u9fff]+')
# 身分証(18 桁):
ID = r'^\d{17}[0-9Xx]$'
# 日付:
DATE = r'^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$'
# 検証関数:
def is_email(s): return bool(re.match(EMAIL, s))
# 中国語を抽出:
CHINESE.findall('hello 世界 你好')
# 色:
COLOR = r'^#[0-9a-fA-F]{6}$'

正規表現のパフォーマンス

事前コンパイル、再帰的量詞による catastrophic backtracking の回避、量詞の境界制限。大きなテキストは分割して。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import re
# プリコンパイル:
pattern = re.compile(r'\d+') # モジュールレベル
pattern.findall(text)
# 破滅的なバックトラックを避ける:
# BAD:ネストした量指定子
# re.match(r'(a+)+$', 'a' * 30 + '!') 非常に遅い
# GOOD:簡略化
re.match(r'a+$', 'aaa')
# 幅を限定:
re.match(r'[a-z]{1,20}', 'x' * 100)
# 文字クラスで or の代用:
# BAD:[a|b|c]
# GOOD:[abc]
# 大きなテキスト:
# 全体ではなく分割して処理
# 単純な解析は str メソッド優先:
# s.startswith / s.split
# 正規表現は構造マッチまで

19.ビルドとエンジニアリング

依存関係管理、仮想環境、テスト、Lint、CI。

requirements.txt

pip freeze で requirements.txt を生成し、pip install -r でインストール。バージョンを固定すれば再現性が保たれます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 現在の環境をエクスポート:
pip freeze > requirements.txt
# ファイル内容の例:
# requests==2.31.0
# httpx>=0.24.0
# # コメントで用途を説明
# インストール:
pip install -r requirements.txt
# 開発依存の分離:
# requirements-dev.txt にテストツール
# バージョン固定の戦略:
# == 完全固定
# >= アップグレード許可
# 本番は正確なバージョンを固定
# 現代の代替:
# pyproject.toml 宣言 + lock ファイル
# uv で超高速インストール

venv の実践

ベストプラクティス: プロジェクトごとに 1 つ、.venv はコミットしない、壊れたら作り直す。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 作成:
python3 -m venv .venv
# 有効化(POSIX):
source .venv/bin/activate
# 有効化(Windows):
# .venv\Scripts\activate
# 現在のインタープリタを確認:
which python
# 終了:deactivate
# 削除して再作成(依存が壊れたとき):
# rm -rf .venv && python3 -m venv .venv
# .gitignore に .venv/ を追加
# 依存は venv にインストール:
# pip install はシステム環境を使わない
# エディタは venv のインタープリタを選択

最新のパッケージマネージャ

uv は超高速な依存マネージャ、poetry は依存とパッケージングを統合。ロックファイルで再現性を確保します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# uv(推奨,超高速):
# pip install uv
uv venv # 仮想環境を作成
uv add requests # 依存を追加
uv sync # 同期インストール
# pip の代替:
uv pip install -r requirements.txt
# poetry:
# poetry new mypkg
# poetry add requests
# poetry install
# pyproject.toml + poetry.lock
# 一貫したやり方:
# 依存宣言 -> ロックファイル -> CI は lock でインストール
# ロックファイルで全員が同じバージョン
# アップグレード:uv lock --upgrade

pytest でのテスト

テスト関数は test_ で始めて assert を書きます。fixture で共有セットアップ、parametrize でパラメータ化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import pytest
from app import add
def test_add():
assert add(1, 2) == 3
def test_add_negative():
assert add(-1, 1) == 0
# パラメータ化:
@pytest.mark.parametrize('a,b,expected', [
(1, 2, 3),
(0, 0, 0),
])
def test_add_param(a, b, expected):
assert add(a, b) == expected
# fixture:
def setup_db():
db = create_db()
yield db
db.close()
def test_using_fixture(setup_db):
assert setup_db.query()
# 例外の検証:
with pytest.raises(ValueError):
int('x')
# 実行:pytest または python3 -m pytest
# カバレッジ:pytest --cov

unittest 標準ライブラリ

標準ライブラリのテストフレームワーク。TestCase を継承し assertXxx を使い、setUp / tearDown でライフサイクルを管理します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import unittest
from app import add
class TestAdd(unittest.TestCase):
def setUp(self):
self.data = [1, 2]
def test_add(self):
self.assertEqual(add(1, 2), 3)
def test_types(self):
self.assertIsInstance(add(1, 2), int)
def test_raises(self):
with self.assertRaises(TypeError):
add('a', 1)
if __name__ == '__main__':
unittest.main()
# 実行:
# python3 -m unittest test_app
# 発見モード:
# python3 -m unittest discover
# アサーションメソッド:
# assertEqual/assertTrue/assertIn
# assertAlmostEqual 浮動小数点
# mock:unittest.mock で依存をモック

Lint とフォーマッタ

ruff で高速 Lint とフォーマット、black でスタイル統一、mypy で型検査。ルールは pyproject.toml に集約。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# ruff(推奨,超高速):
# pip install ruff
ruff check . # チェック
ruff format . # フォーマット
ruff check --fix . # 自動修正
# black:
# pip install black
black --line-length 88 .
# mypy 型チェック:
# pip install mypy
mypy src/
# pyproject.toml の設定:
# [tool.ruff]
# line-length = 88
# [tool.mypy]
# strict = true
# エディタ連携:
# 保存時にフォーマット
# コミット前:pre-commit フック

パッケージングと公開

pyproject.toml でパッケージングを設定し、sdist/wheel をビルド、twine で PyPI に公開します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# pyproject.toml:
# [project]
# name = "my-pkg"
# version = "0.1.0"
# description = "..."
# requires-python = ">=3.10"
# dependencies = ["requests"]
#
# [build-system]
# requires = ["setuptools>=68"]
# build-backend = "setuptools.build_meta"
#
# [tool.setuptools.packages.find]
# include = ["mypkg*"]
# ビルド:
# pip install build
# python3 -m build
# dist/*.whl と tar.gz を生成
# 公開:
# pip install twine
# twine upload dist/*
# テスト版:test.pypi.org
# セマンティックバージョン:1.2.0
# 公開前のテストインストール:pip install .

CI とデプロイ

パイプラインの段階: lint、型検査、テスト、ビルド。GitHub Actions で構成します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# GitHub Actions:
# .github/workflows/ci.yml
# name: CI
# on: [push, pull_request]
# jobs:
# test:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v4
# - uses: actions/setup-python@v5
# with: python-version: '3.11'
# - run: pip install -e .[dev]
# - run: ruff check .
# - run: mypy src/
# - run: pytest --cov
# 依存キャッシュ:actions/cache
# 複数バージョンマトリクス:python-version: [3.10, 3.11, 3.12]
# 失敗はマージを阻止
# デプロイ:リリース時に PyPI へアップロード
# 環境変数:GitHub Secrets にキーを保存

公式リンク

公式ドキュメントとリソースへの直接リンク。

このチートシートについて

このページは Python 3.11 の自己完結型クイックリファレンスです。言語コアと、実プロジェクトで日常の約 80% を占める標準ライブラリを扱います。f-string、リスト内包表記、ジェネレータ、コンテキストマネージャ、型注釈、データクラス(dataclass)、そして collections、itertools、pathlib といった実用的な標準ライブラリモジュールを重視しています。Python は 1991 年に Guido van Rossum によって公開され、読みやすい構文と「バッテリー同梱」の標準ライブラリで知られ、データ分析、機械学習、自動化スクリプト、Web 開発(Django / FastAPI)で広く使われています。 19 のセクションはそれぞれ 1 つのテーマに焦点を当てます: 基本構文、変数、型と参照セマンティクス、制御フロー、関数、文字列、リストと辞書、メモリと参照カウント、クラスと OOP、例外処理、入出力、よくある落とし穴、並行処理(スレッド/非同期)、ネットワーク、時間、プロセス、正規表現、そしてビルドツール(pip/venv)。各サブセクションには短い概念説明とコピー&ペースト可能なコードスニペットが付いています。 すべてのコードとテキストはブラウザ内でレンダリングされ、データは一切デバイスから外部へ送信されません。正式なリファレンスは Python 公式ドキュメントを参照してください。

バージョン 2.1.0