高頻度版と落とし穴を削って再構成した補足ノートである。
実務で差がつきやすい観点だけを残し、各項目をすぐ試せる最小コードでまとめている。
参考として、次の記事も参照してください。
Pythonコーディングの落とし穴63選:実務で使えるベストプラクティス集
Pythonコーディングの頻出64ポイント:実務で使える要点とコード例
0. 基礎データ型 (Basic Data Types)
頻出型の早見表
| 型 |
英語名 |
例 |
可変(mutable) |
ポイント |
bool |
boolean |
True, False
|
いいえ |
真偽値評価(truthiness)、論理演算 |
int |
integer |
42, -7
|
いいえ |
整数除算、剰余、大きな整数 |
float |
floating-point number |
3.14, 0.5
|
いいえ |
精度誤差、比較 |
str |
string |
'abc' |
いいえ |
スライス、書式化、メソッド |
NoneType |
null-like singleton type |
None |
いいえ |
欠損値、デフォルト戻り値 |
list |
list |
[1, 2, 3] |
はい |
順序、変更、スライス |
tuple |
tuple |
(1, 2) |
いいえ |
アンパック(unpacking)、ハッシュ可能条件 |
set |
set |
{1, 2, 3} |
はい |
重複除去、集合演算、高速検索 |
dict |
dictionary / mapping |
{'a': 1} |
はい |
キーと値の対応付け(key-value mapping) |
- イテラブル(iterable)だからといって、シーケンス(sequence)とは限らず、位置アクセスやスライスができるとは限らない。
text = "python"
pairs = {"x": 10, "y": 20}.items()
print(text[1:4]) # yth
print(hasattr(text, "__getitem__")) # True
print(hasattr(pairs, "__getitem__")) # False
- in-place 操作は元オブジェクトを書き換え、新しい値を返す操作は元の値を残す。
nums = [3, 1, 2]
sort_result = nums.sort()
text = "python"
upper_text = text.upper()
print(nums) # [1, 2, 3]
print(sort_result is None) # True
print(text) # python
print(upper_text) # PYTHON
- shallow copy は外側しか複製しないので、入れ子の可変要素は共有されたままになる。
import copy
nested = [[1], [2]]
shallow = copy.copy(nested)
deep = copy.deepcopy(nested)
nested[0].append(99)
print(shallow) # [[1, 99], [2]]
print(deep) # [[1], [2]]
1. コア構文・式・演算子・真偽値評価 (Core Syntax, Expressions, Operators & Truthiness)
- 式は値を返すが、文は分岐や定義のような実行動作そのものを担う。
value = 3 + 4
score = 78
if score >= 60:
result = "pass"
else:
result = "fail"
print(value) # 7
print(result) # pass
- 短絡評価では、
and と or は必要になった時点で評価を止め、しばしば bool ではなくオペランドそのものを返す。
calls = []
def ping():
calls.append("called")
return 99
print("ready" or ping()) # ready
print(0 and ping()) # 0
print(calls) # []
- 条件式は値として使えるので、代入や式の中で分岐結果をそのまま扱える。
score = 85
grade = "A" if score >= 80 else "B"
print(grade) # A
- 解包代入を使うと、複数の値を一度に受け取って前処理のボイラープレートを減らせる。
left, right = (1, 2)
print(left) # 1
print(right) # 2
2. 制御フロー: 条件分岐・ループ・反復パターン (Control Flow: Conditionals, Loops & Iteration Patterns)
- ループ変数は、ループ終了後も同じスコープに残ることが多い。
for n in range(3):
pass
print(n) # 2
- accumulator pattern は、合計や集計を 1 つの変数へ順に畳み込む最小形である。
total = 0
for value in [3, 5, 7]:
total += value
print(total) # 15
- search pattern は、条件を満たしたら
break や return で早く抜けるのが実用的である。
target = 8
found = False
for value in [3, 5, 8, 13]:
if value == target:
found = True
break
print(found) # True
- 存在判定は
any(...)、全件判定は all(...) で手書きループを短くできる。
values = [2, 4, 6, 8]
print(any(v % 2 == 1 for v in values)) # False
print(all(v % 2 == 0 for v in values)) # True
- 長い
if / elif は、辞書ディスパッチに置き換えると条件分岐と処理本体を分離できる。
def to_hex(value):
return hex(value)
def to_bin(value):
return bin(value)
def format_number(kind, value):
handlers = {"hex": to_hex, "bin": to_bin}
if kind not in handlers:
return "unsupported"
return handlers[kind](value)
print(format_number("hex", 10)) # 0xa
print(format_number("oct", 10)) # unsupported
3. 文字列・文字・変換・エンコーディング (Strings, Characters, Conversion & Encoding)
文字列の操作・関数・メソッド
| 分類 |
メソッド 1 |
メソッド 2 |
メソッド 3 |
メソッド 4 |
メソッド 5 |
| リテラル |
' ' |
" " |
""" """ |
|
|
| エスケープ文字 |
\n |
\t |
\' |
\" |
|
| スライス |
s[i] |
s[i:j] |
s[i:j:k] |
|
|
| 操作 |
+ |
* |
in |
len() |
s.join() |
| 数値変換 |
int() |
float() |
str() |
|
|
| 文字コード |
ord() |
chr() |
s.encode() |
|
|
| 大文字・小文字変換 |
s.lower() |
s.upper() |
s.capitalize() |
s.title() |
s.swapcase() |
| 空白の削除 |
s.strip() |
s.lstrip() |
s.rstrip() |
|
|
| 文字列の分割 |
s.split() |
s.splitlines() |
|
|
|
| 文字配置 |
s.center() |
s.ljust() |
s.rjust() |
s.zfill() |
|
| 書式化 |
s.format() |
f-string |
|
|
|
| 文字列の属性 |
s.isalnum() |
s.isalpha() |
s.isascii() |
s.isdecimal() |
s.isdigit() |
| 文字列の属性 |
s.isnumeric() |
s.islower() |
s.isupper() |
s.istitle() |
s.isprintable() |
| 文字列の属性 |
s.isspace() |
s.isidentifier() |
|
|
|
- Python の
str はテキスト、bytes は生のバイト列であり、UTF-8 は両者をつなぐ代表的なエンコーディングである。
text = "caf\u00e9"
data = text.encode("utf-8")
print(type(text).__name__) # str
print(type(data).__name__) # bytes
print(data) # b'caf\xc3\xa9'
print(data.decode("utf-8")) # café
- 1 文字でも、文字数と UTF-8 のバイト数は一致しないことがある。
text = "\u00e9"
data = text.encode("utf-8")
print(len(text)) # 1
print(len(data)) # 2
- f-string の format specifier を使うと、桁数、寄せ、区切りを文字列化と同時に決められる。
name = "Ann"
price = 93.456
count = 12000
print(f"{name:>6}") # Ann
print(f"{price:.2f}") # 93.46
print(f"{count:,}") # 12,000
- エスケープ文字(
\\)を使うと、\n、\t、\'、\" などの特殊シーケンスを文字列内で安全に表現できる。
text = "He said, it\'s fine."
path = "C:\\new\\notes"
print(text) # He said, it's fine.
print(path) # C:\new\notes
print("A\nB\tC") # A と B の間は改行、B と C の間はタブ
- インデックスアクセスでは正方向(
0 から)と負方向(-1 から)の両方を使って、単一文字を取り出せる。
s = "Python"
print(s[0]) # P
print(s[1]) # y
print(s[-1]) # n
print(s[-2]) # o
- スライスは
s[start:stop:step] の形で、部分文字列の抽出・間引き・逆順を同じ記法で扱える。
s = "Python"
print(s[2:]) # thon
print(s[:4]) # Pyth
print(s[1:5]) # ytho
print(s[1:5:2]) # yh
print(s[::-1]) # nohtyP
-
str.replace(old, new, count) は部分文字列置換に使い、count を指定すると置換回数を制御できる。
s = "Simple is better than simple."
print(s.replace("simple", "clear")) # 大文字小文字は区別される
print(s.lower().replace("simple", "clear")) # すべて置換
print(s.lower().replace("simple", "clear", 1))
-
strip()、lstrip()、rstrip() は前後空白の正規化で頻出し、必要なら指定文字セットを端から除去できる。
s = "\t Python \n"
t = "---report---"
print(s.strip()) # Python
print(s.lstrip()) # Python \n
print(s.rstrip()) # \t Python
print(t.strip("-")) # report
print(t.rstrip("-")) # ---report
- 文字列排版では
center()、ljust()、rjust()、zfill() を使って表示幅やゼロ埋めを揃えるとログや表が読みやすくなる。
title = "Python"
idx = "7"
print(title.center(12, "=")) # ===Python===
print(title.ljust(12, ".")) # Python......
print(title.rjust(12, ".")) # ......Python
print(idx.zfill(4)) # 0007
- 文字列フォーマットは
str.format() と f-string が実務の基本で、可読性を保ちながら値や式を埋め込める。
name = "Ann"
age = 22
score = 93.456
print("{0} is {1} years old.".format(name, age))
print("{name}: {score:.1f}".format(name=name, score=score))
print(f"{name} next year: {age + 1}")
4. コンテナ・内包表記・ソート・探索 (Containers, Comprehensions, Sorting & Searching)
Python の主要コンテナ
| 構造 |
日本語名 |
分類 |
順序 |
可変 |
重複 |
向いている保存対象 / 用途 |
理由・補足 |
例 |
list |
リスト |
シーケンス型 |
はい |
はい |
許す |
順序があり重複を許す記録列 |
順序保持。インデックス、スライス、頻繁な追加 / 削除 / 更新に向く |
注文リスト、点数リスト |
tuple |
タプル |
シーケンス型 |
はい |
いいえ |
許す |
長さ固定で意味が安定した小さなレコード |
不変で「座標 / RGB / (name, age)」のような固定長データに向く |
(3, 5) |
str |
文字列 |
シーケンス型 |
はい |
いいえ |
許す |
テキスト |
文字を順序付きで持つ |
'abc' |
bytes |
バイト列 |
シーケンス型 |
はい |
いいえ |
許す |
バイト列 |
順序付きの不変バイナリデータ |
b'abc' |
range |
範囲オブジェクト |
シーケンス型 |
はい |
いいえ |
値は一意 |
等差数列の範囲 |
連番や反復範囲を軽量に表せる |
range(5) |
set |
集合 |
集合型 |
いいえ |
はい |
許さない |
重複しない値の集合 |
自動重複除去、集合演算、高速 membership |
ユーザー ID 集合 |
frozenset |
不変集合 |
集合型 |
いいえ |
いいえ |
許さない |
不変の集合 |
set の不変版 |
frozenset({1, 2}) |
dict |
辞書 |
写像型 |
キー順あり |
はい |
key は一意 |
key に対応する value の写像 |
参照、マッピング、カウント、集計。挿入順を保持(Python 3.7 以降で保証) |
user_id -> score |
コンテナ別の組み込み関数とメソッド
| 分類 |
内容1 |
内容2 |
内容3 |
内容4 |
内容5 |
| 生成 |
a = [] |
a = [1, 2, 3] |
(expression with x) for x in iterable |
|
|
| 操作 |
+ |
* |
in |
not in |
>, >=, <, <=, ==, !=
|
| 取得 |
a[index] |
a[start:] |
a[:stop] |
a[start:stop] |
|
| 使用可能な組み込み関数 |
len() |
min() |
max() |
|
|
| 他の型をリストに変換 |
list() |
|
|
|
|
| ソート |
a.sort() |
a.reverse() |
|
|
|
| 削除 |
del |
a.remove() |
a.pop() |
|
|
| 追加 |
a.insert(i, x) |
a.append() |
a.extend(t) |
|
|
| 複製 |
a.copy() |
|
|
|
|
| クリア |
a = [] |
a.clear() |
|
|
|
-
list は list() で生成・変換でき、len() max() min() で要素数や大小を確認できる。変更系では append() extend() insert() remove() pop() clear() copy() sort() reverse() と del が基本になる。
numbers = list(range(3))
numbers.append(10)
numbers.extend([20, 30])
numbers.insert(1, 99)
removed = numbers.pop()
numbers.remove(99)
snapshot = numbers.copy()
del snapshot[0]
numbers.sort()
numbers.reverse()
scratch = snapshot.copy()
scratch.clear()
print(numbers) # [20, 10, 2, 1, 0]
print(removed) # 30
print(len(snapshot)) # 4
print(max(numbers)) # 20
print(min(numbers)) # 0
print(scratch) # []
-
tuple は tuple() で生成・変換でき、len() max() min() sorted() で読み取り中心に扱う。immutable なので append() remove() sort() のような変更系 method は使えず、必要なら list() に変換して処理する。
values = tuple([3, 1, 2])
single = (2,)
as_list = list(values)
print(values) # (3, 1, 2)
print(single) # (2,)
print(len(values)) # 3
print(max(values)) # 3
print(min(values)) # 1
print(sorted(values)) # [1, 2, 3]
print(as_list) # [3, 1, 2]
-
set は set() で作るか他の sequence から cast して重複除去に使う。len() max() min() in が基本で、immutable 版として frozenset もある。
letters = set("abca")
frozen = frozenset([3, 1, 2, 2])
print(sorted(letters)) # ['a', 'b', 'c']
print(len(frozen)) # 3
print(max(frozen)) # 3
print(min(frozen)) # 1
print("b" in letters) # True
-
set の集合演算は union() intersection() difference() symmetric_difference()、比較は isdisjoint() issubset() issuperset()、更新は add() remove() discard() pop() clear() update() intersection_update() difference_update() symmetric_difference_update() をまとめて押さえる。
admins = {"Moose", "Joker"}
moderators = {"Ann", "Chris", "Moose"}
print(sorted(admins.union(moderators))) # ['Ann', 'Chris', 'Joker', 'Moose']
print(sorted(admins.intersection(moderators))) # ['Moose']
print(sorted(admins.difference(moderators))) # ['Joker']
print(sorted(admins.symmetric_difference(moderators))) # ['Ann', 'Chris', 'Joker']
users = {"ann", "bob", "cindy"}
users.add("dave")
users.discard("bob")
users.update({"eric"})
print(users.isdisjoint({"zoe"})) # True
print({"ann"}.issubset(users)) # True
print(users.issuperset({"ann", "dave"})) # True
-
dict では len() max() min() list() tuple() set() sorted() が既定で key に対して動き、keys() values() items() を使うと key・value・組をそれぞれ view として扱える。
phonebook = {"ann": 6575, "bob": 8982, "joe": 2598}
print(len(phonebook)) # 3
print(max(phonebook)) # joe
print(min(phonebook)) # ann
print(list(phonebook)) # ['ann', 'bob', 'joe']
print(tuple(phonebook)) # ('ann', 'bob', 'joe')
print(sorted(set(phonebook))) # ['ann', 'bob', 'joe']
print(list(phonebook.keys())) # ['ann', 'bob', 'joe']
print(list(phonebook.values())) # [6575, 8982, 2598]
print(list(phonebook.items())) # [('ann', 6575), ('bob', 8982), ('joe', 2598)]
-
dict の更新と取得では d[key] = value、del d[key]、update()、copy()、clear()、popitem()、pop()、get()、setdefault() を使い分けると、追加・削除・既定値付き参照をまとめて処理できる。
phonebook = {"ann": 6575, "bob": 8982}
phonebook["joe"] = 2598
phonebook.update({"zoe": 1225})
backup = phonebook.copy()
print(phonebook.get("adam", 3538)) # 3538
print(phonebook.setdefault("adam", 3538)) # 3538
print(phonebook.pop("bob")) # 8982
print(backup.popitem()) # ('zoe', 1225)
del phonebook["ann"]
backup.clear()
print(phonebook) # {'joe': 2598, 'zoe': 1225, 'adam': 3538}
print(backup) # {}
- 星付き解包は、先頭と末尾を固定しつつ中間をまとめて受け取りたい時に便利である。
data = [1, 2, 3, 4, 5]
head, *middle, tail = data
print(head) # 1
print(middle) # [2, 3, 4]
print(tail) # 5
-
list.remove(x) は値で最初の一致要素をその場で削除し、見つからないと ValueError になるので pop(i) とは用途が違う。
items = ["sql", "python", "sql", "git"]
result = items.remove("sql")
print(items) # ['python', 'sql', 'git']
print(result is None) # True
- 集合演算
|、&、- を使うと、差集合や共通集合を二重ループなしで表現できる。
left = {1, 2, 3}
right = {3, 4, 5}
print(left | right) # {1, 2, 3, 4, 5}
print(left & right) # {3}
print(left - right) # {1, 2}
- Python の
sorted() は stable sort なので、複数キーの並べ替えを段階的に書ける。
rows = [
{"name": "bob", "age": 20},
{"name": "ann", "age": 20},
{"name": "cindy", "age": 19},
]
# stable sort なので、副キーを先に、主キーを後に適用できる
rows = sorted(rows, key=lambda row: row["name"])
rows = sorted(rows, key=lambda row: row["age"])
print(rows)
# [{'name': 'cindy', 'age': 19}, {'name': 'ann', 'age': 20}, {'name': 'bob', 'age': 20}]
- 存在確認や重複除去では、二重ループより先に
set や dict を考えると計算量を落としやすい。
records = ["ann", "bob", "ann", "cindy"]
seen = set()
unique = []
for name in records:
if name not in seen:
seen.add(name)
unique.append(name)
print(unique) # ['ann', 'bob', 'cindy']
5. 関数・再帰・高階関数・状態の受け渡し (Functions, Recursion, Higher-Order Functions & State Passing)
-
print() は「渡された値を出力ストリームへ書き出す関数」であり、sep=' '・end='\n'・file=sys.stdout が既定値で、戻り値は常に None である。
import io
buffer = io.StringIO()
print("Hello", "world", file=buffer)
print("Hello", "world", sep="-", end="!", file=buffer)
result = print("done", file=buffer)
print(repr(buffer.getvalue())) # 'Hello world\nHello-world!done\n'
print(result is None) # True
- 位置引数(positional argument)は順番で意味が決まり、キーワード引数(keyword argument)は名前で値を指定して既定値を上書きできる。
def build_message(name, age=18, prefix="Hi"):
return f"{prefix}, {name} ({age})"
print(build_message("Ann")) # Hi, Ann (18)
print(build_message("Ann", 22)) # Hi, Ann (22)
print(build_message("Ann", age=22, prefix="Hello")) # Hello, Ann (22)
-
*args(Arbitrary Positional Arguments)を付けた仮引数は可変長の位置引数をタプルとして受け取る。0個でも複数個でも呼び出せ、関数内では反復処理しやすい。
-
def say_hi(*names):
for name in names:
print(f"Hi, {name}!")
say_hi()
say_hi("ann")
say_hi("mike", "john", "zeo")
- 既存のコンテナを可変長位置引数として渡すときは、呼び出し側で
* を使ってアンパックする。
def say_hi(*names):
for name in names:
print(f"Hi, {name}!")
names = ("mike", "john", "zeo")
say_hi(*names)
a_dictionary = {"ann": 2321, "mike": 8712, "joe": 7610}
say_hi(*a_dictionary) # dict はキーが展開される
-
**kwargs(Arbitrary Keyword Arguments)は可変長のキーワード引数を辞書として受け取り、呼び出し時に ** で辞書を展開できる。
def say_hi(**names_greetings):
for name, greeting in names_greetings.items():
print(f"{greeting}, {name}!")
say_hi(mike="Hello", ann="Oh, my darling", john="Hi")
data = {"mike": "Hello", "ann": "Oh, my darling", "john": "Hi"}
say_hi(**data)
- 引数の判定順序は Positional -> Arbitrary Positional -> Keyword -> Arbitrary Keyword で、
*args と **kwargs はそれぞれ 1 つだけ定義できる。
def say_hi(greeting, *names, capitalized=False, **options):
for name in names:
text = name.capitalize() if capitalized else name
print(f"{greeting}, {text}{options.get('suffix', '!')}")
say_hi("Hello", "mike", "john", capitalized=True, suffix="!!")
-
*args の後ろに置いた引数は keyword-only parameter になるので、呼び出し側の意図を明示しやすい。
def say_hi(*names, greeting="Hello", capitalized=False):
result = []
for name in names:
if capitalized:
name = name.capitalize()
result.append(f"{greeting}, {name}!")
return result
print(say_hi("mike", "zoe", greeting="Hi", capitalized=True))
# ['Hi, Mike!', 'Hi, Zoe!']
- 関数は first-class object なので、コンテナに入れて順番に適用する設計もできる。
def double(x):
return x * 2
def square(x):
return x * x
funcs = [double, square]
print([func(3) for func in funcs]) # [6, 9]
- クロージャ(closure) では、内側の関数が外側の変数を覚えたまま動く。
def make_multiplier(factor):
def multiply(x):
return x * factor
return multiply
double = make_multiplier(2)
print(double(10)) # 20
- 型注釈は読みやすさを上げるが、標準の Python はそれだけで実行時チェックを強制しない。
def echo(value: int) -> int:
return value
result = echo("3")
print(result) # 3
print(type(result).__name__) # str
print(echo.__annotations__)
# {'value': <class 'int'>, 'return': <class 'int'>}
- 状態が増えたら、
tuple より dict や dataclass にして名前付きで渡す方が崩れにくい。
from dataclasses import dataclass
@dataclass
class Context:
user_id: str
retry_count: int
state = Context("u001", 1)
print(state) # Context(user_id='u001', retry_count=1)
- 関数への代入はコピーではなく同一オブジェクトへの参照なので、別名(alias)を付けても元の関数と実体は同じである。
def is_leap(year):
return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
year_leap_bool = is_leap
print(year_leap_bool(800)) # True
print(id(year_leap_bool) == id(is_leap)) # True
print(type(year_leap_bool).__name__) # function
-
lambda は短い匿名関数を作る構文で、: の右側には 1 つの expression しか置けない。
add = lambda x, y: x + y
print(add(3, 5)) # 8
-
lambda は「関数の戻り値として関数を返す」場面でよく使われ、外側の値を保持したまま呼び出せる。
def make_incrementor(n):
return lambda x: x + n
f = make_incrementor(42)
print(f(0)) # 42
print(f(1)) # 43
print(id(f) == id(make_incrementor)) # False
-
lambda は map() や sort(key=...) のように「関数を引数に取る API」に渡すと、使い捨て処理を簡潔に書ける。
a_list = [1, 2, 3, 4]
doubled = list(map(lambda x: x * 2, a_list))
pairs = [(1, "one"), (2, "two"), (3, "three"), (4, "four")]
pairs.sort(key=lambda p: p[1])
print(doubled) # [2, 4, 6, 8]
print(pairs) # [(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]
6. ファイル・例外・モジュール・エラー処理 (Files, Exceptions, Modules & Error Handling)
- ファイル操作の入口は組み込み関数
open() であり、まず押さえる基本モードは r(読み込み)と w(書き込み)である。
file = open("sample.txt", "w", encoding="utf-8")
file.write("first line\nsecond line\n")
file.close()
file = open("sample.txt", "r", encoding="utf-8")
text = file.read()
file.close()
print(text)
- ファイル削除は
os.remove() を使い、削除前に os.path.exists() で存在確認を行うと失敗を避けやすい。
import os
path = "sample-delete.txt"
open(path, "w", encoding="utf-8").close()
if os.path.exists(path):
os.remove(path)
print("deleted")
else:
print("does not exist")
- 読み書きでは
file.write()、file.read()、file.readline()、file.readlines()、file.writelines() を使い分ける。
file = open("sample-io.txt", "w", encoding="utf-8")
file.write("zero line\n")
file.writelines(["first line\n", "second line\n", "third line\n"])
file.close()
file = open("sample-io.txt", "r", encoding="utf-8")
print(file.readline().strip()) # zero line
print(file.readlines()) # 残り行をリストで取得
file.close()
file = open("sample-io.txt", "r", encoding="utf-8")
print(file.read()) # 全文を文字列として取得
file.close()
-
with open(...) as file: を使うと、関連操作を同一ブロックにまとめられ、close() を明示しなくても安全に扱える。
with open("sample-with.txt", "w", encoding="utf-8") as file:
file.write("first line\nsecond line\nthird line\n")
with open("sample-with.txt", "r", encoding="utf-8") as file:
for line in file.readlines():
print(line.strip())
- custom exception を作ると、業務上の失敗を組み込み例外と区別して扱える。
class InvalidScoreError(ValueError):
pass
def normalize_score(score):
if not 0 <= score <= 100:
raise InvalidScoreError("score must be between 0 and 100")
return score / 100
try:
normalize_score(120)
except InvalidScoreError as exc:
print(type(exc).__name__, exc)
# InvalidScoreError score must be between 0 and 100
- 例外連鎖は
raise ... from exc で元の原因を残せるので、変換失敗と業務エラーを両方追える。
try:
try:
int("abc")
except ValueError as exc:
raise RuntimeError("bad age text") from exc
except RuntimeError as exc:
print(type(exc).__name__) # RuntimeError
print(type(exc.__cause__).__name__) # ValueError
- モジュールは 1 つの
.py、パッケージは複数のモジュールを束ねるディレクトリとして捉えると整理しやすい。
import collections
import mycode
print(mycode.__file__.endswith("mycode.py")) # True
print(hasattr(collections, "__path__")) # True
-
help() は関数・クラス・モジュールの docstring や使い方を確認する基本手段で、API の意図を素早く把握するのに有効である。
import mycode
help(mycode.is_prime)
help(mycode.say_hi)
print(mycode.say_hi.__doc__)
-
dir() はモジュールやオブジェクトで現在参照可能な名前一覧を返すため、import 後に何が使えるかを確認しやすい。
import mycode
names = dir(mycode)
public_names = [name for name in names if not name.startswith("_")]
print("is_prime" in public_names) # True
print("say_hi" in public_names) # True
print(public_names)
- 本体処理を
main() に集約し、if __name__ == "__main__": main() で入口を分けると、import 時の副作用を防ぎつつ、python file.py や python -m module_name 実行時だけ本処理を走らせられる(-m では .py を付けない)。
def main():
print("program started")
if __name__ == "__main__":
main()
# import that -> main() は自動実行されない
# python that.py -> main() が実行される
# python -m that -> main() が実行される
7. クラスとオブジェクト指向 (Classes & Object-Oriented Programming)
- オブジェクト指向では、現実のものを抽象化してクラスを作り、そこからインスタンスを生成して使う。カプセル化によって内部を隠し、外には属性とメソッドからなるインターフェースを見せる。継承を使うと、子クラスは元のクラスの性質を受け継ぎながら拡張できる。
| 用語 |
最短説明 |
| オブジェクト |
実際に使う対象 |
| 抽象化 |
必要な特徴だけを取り出すこと |
| カプセル化 |
内部実装を隠して外はインターフェースだけ見せること |
| インターフェース |
外から触る入口 |
| 属性 |
オブジェクトが持つデータ |
| メソッド |
オブジェクトが持つ振る舞い |
| クラス |
オブジェクトの設計図 |
| サブクラス |
クラスを受け継いで広げたもの |
| 継承 |
クラスの性質を引き継ぐ関係 |
| インスタンス |
クラスから作られた具体物 |
-
__repr__ は、開発者が中身を追いやすい表示を定義する場所である。
class Golem:
def __init__(self, name):
self.name = name
def __repr__(self):
return f"Golem(name={self.name!r})"
print(Golem("Clay")) # Golem(name='Clay')
-
@classmethod は、別形式の入力から生成する代替コンストラクタとして使いやすい。
class Golem:
def __init__(self, name):
self.name = name
@classmethod
def from_upper(cls, name):
return cls(name.upper())
print(Golem.from_upper("stone").name) # STONE
-
@staticmethod は、インスタンス状態にもクラス状態にも依存しない変換処理に向く。
class Temperature:
@staticmethod
def c_to_f(celsius):
return celsius * 9 / 5 + 32
print(Temperature.c_to_f(0)) # 32.0
- クラスやインスタンスの中身をざっと調べるときは、
help(obj)、dir(obj)、obj.__dict__、hasattr(obj, "name") が基本になる。
import datetime
class Golem:
def __init__(self, name=None):
self.name = name
self.built_year = datetime.date.today().year
def say_hi(self):
print("Hi!")
class RunningGolem(Golem):
def run(self):
print("running")
rg = RunningGolem("Clay")
help(rg)
dir(rg)
print(rg.__dict__)
print(hasattr(rg, "built_year")) # True
-
hasattr() は存在確認、getattr() は値の取得、setattr() は値の設定に使い、属性名を文字列で扱える。
class Golem:
population = 0
g = Golem()
print(hasattr(g, "population")) # True
print(getattr(g, "population")) # 0
setattr(Golem, "population", 10)
print(g.population) # 10
- クラス変数は class scope にあり、method の中でそのまま
population と書くと local scope と衝突しやすいので、ClassName.population か self.population で参照する。
class Golem:
population = 0
def __init__(self):
# population += 1 # UnboundLocalError
Golem.population += 1
g1 = Golem()
g2 = Golem()
print(Golem.population) # 2
-
self.xxx はインスタンス側、ClassName.xxx はクラス側を見に行くので、method 内では「どの scope の値か」を意識すると混乱しにくい。
class Golem:
population = 0
__life_span = 10
def __init__(self):
self.__active = True
def is_active(self):
return self.__active and Golem.__life_span > 0
g = Golem()
print(g.is_active()) # True
- カプセル化では、外から直接触らせたくない値を
__population のように隠し、必要な値だけ method や property 経由で公開する。
class Golem:
__population = 0
@property
def population(self):
return Golem.__population
g = Golem()
print(g.population)
-
@property と @<property_name>.setter を使うと、見た目は属性アクセスのまま、読み取り専用にするか外部更新を許すかを制御できる。
class Golem:
__population = 0
@property
def population(self):
return Golem.__population
@population.setter
def population(self, value):
Golem.__population = value
g = Golem()
g.population = 100
print(g.population) # 100
8. イテレータ・ジェネレータ・デコレータ・正規表現 (Iterators, Generators, Decorators & Regular Expressions)
-
iter() はイテラブル(iterable)をイテレータ(iterator)に変換する基本手段で、文字列・tuple・list などを「1 個ずつ取り出せる状態」に変える。
text_iter = iter("Python")
nums_iter = iter((1, 2, 3))
items_iter = iter(["item 1", "item 2", 3, 5])
print(type(text_iter).__name__) # str_ascii_iterator など
print(next(text_iter)) # P
print(next(nums_iter)) # 1
print(list(items_iter)) # ['item 1', 'item 2', 3, 5]
- イテレータプロトコル(iterator protocol)は
__iter__() と __next__() で成り立ち、自作オブジェクトも for で回せる。
class Countdown:
def __init__(self, start):
self.current = start
def __iter__(self):
# iterator 自身を返すと、そのまま for / list() に渡せる
return self
def __next__(self):
if self.current == 0:
raise StopIteration
value = self.current
self.current -= 1
return value
it = Countdown(3)
print(next(it)) # 3
print(list(it)) # [2, 1]
- generator expression は、全部を先に作らず必要になった値だけを流す。
even_sum = sum(x for x in range(10) if x % 2 == 0)
print(even_sum) # 20
- 引数付き decorator は 1 段ラップを増やす形で作るのが定番で、
wraps を使うと元関数の情報を残せる。
from functools import wraps
def repeat(times):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
result = None
for _ in range(times):
# 最後に呼んだ結果を返す
result = func(*args, **kwargs)
return result
return wrapper
return decorator
calls = []
@repeat(3)
def ping():
calls.append("ping")
ping()
print(calls) # ['ping', 'ping', 'ping']
-
re.compile() でコンパイル済みパターンを持っておくと、繰り返し検索するときに書き方が安定する。
import re
pattern = re.compile(r"[\w.-]+@[\w.-]+")
text = "ann@test.com and bob@test.org"
print(pattern.findall(text)) # ['ann@test.com', 'bob@test.org']
- 遅延評価(lazy evaluation)では、ジェネレータを作っただけでは中身はまだ評価されない。
events = []
def numbers():
events.append("yield 1")
yield 1
events.append("yield 2")
yield 2
stream = numbers()
print(events) # []
print(next(stream)) # 1
print(events) # ['yield 1']
print(sum(stream)) # 2
print(events) # ['yield 1', 'yield 2']
- Regex は mini language として見ると整理しやすく、Python では pattern を raw string の
r"..." で書くと \d や \b の escape が崩れにくい。
import re
text = r"C:\logs\app-01.txt"
print(re.findall(r"\\", text)) # ['\\', '\\']
print(re.findall(r"\d+", "room 42")) # ['42']
- Regex の atom は literal、集合
[]、種類 \d \w \s .、境界 ^ $ \b \B などで、文字そのもの・文字の種類・位置を表せる。
import re
text = "cat 123 dog"
print(re.findall(r"[a-z]+", text)) # ['cat', 'dog']
print(re.findall(r"\d+", text)) # ['123']
print(re.findall(r"\bdo\w", text)) # ['dog']
- 文字集合
[] では range の - と否定の ^ が重要で、[abc] は「どれか 1 文字」、[^abc] は「それ以外 1 文字」を意味する。
import re
text = "begin began begun bigins"
print(re.findall(r"beg[iau]n", text)) # ['begin', 'began', 'begun']
print(re.findall(r"[^0-9]+", "abc123")) # ['abc']
- 量指定子
+ ? * {n,m} は直前の atom にだけ作用するので、er、[er]、(?:er) は見た目が近くても意味が大きく違う。
import re
text = "er ee rr erer"
print(re.findall(r"er+", text)) # ['er', 'er', 'er']
print(re.findall(r"[er]+", text)) # ['er', 'ee', 'rr', 'erer']
print(re.findall(r"(?:er)+", text)) # ['er', 'erer']
-
| は「または」を表す優先順位の低い演算子で、集合 [] の中では各文字がすでに OR なので | 自体はただの文字になる。
import re
text = "begin began begun"
print(re.findall(r"begin|began|begun", text)) # ['begin', 'began', 'begun']
print(re.findall(r"[a|e]", "a|e")) # ['a', '|', 'e']
- 丸括弧
() は grouping と capturing を兼ね、Python の re.sub() では捕獲した値を \1、\2 の形で再利用できる。
import re
text = "The white dog wears a black hat."
pattern = r"The (white|black) dog wears a (white|black) hat."
print(re.findall(pattern, text))
print(re.sub(pattern, r"The \2 dog wears a \1 hat.", text))
# [('white', 'black')]
# The black dog wears a white hat.
-
(?:...) は grouping だけして capture せず、(?=...) (?!...) (?<=...) (?<!...) は前後関係だけを確認して文字自体は消費しない。
import re
print(re.findall(r"(?:white|black) dog", "white dog black dog"))
print(re.findall(r"Windows(?=2000)", "Windows2000 Windows3.1"))
print(re.findall(r"(?<=2000)Windows", "2000Windows 3.1Windows"))
# ['white dog', 'black dog']
# ['Windows']
# ['Windows']
- flag を付けると engine の挙動を変えられ、実務では
re.I、re.M、re.S、re.X をまず押さえると十分に戦える。
import re
text = "Python\npython"
print(re.findall(r"^python", text))
print(re.findall(r"^python", text, flags=re.I | re.M))
pattern = re.compile(r"""
\d{3}
-
\d{4}
""", re.X)
print(bool(pattern.fullmatch("123-4567")))
# []
# ['Python', 'python']
# True
- よく使う validation pattern は保存して再利用すると速く、username・HEX color・email のような定番は
fullmatch() で判定すると意図がぶれにくい。
import re
username = re.compile(r"^[a-z0-9_-]{3,16}$")
hex_color = re.compile(r"^#?([a-f0-9]{6}|[a-f0-9]{3})$")
email = re.compile(r"^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$")
print(bool(username.fullmatch("ann_42")))
print(bool(hex_color.fullmatch("#0fa")))
print(bool(email.fullmatch("ann@test.com")))
# True
# True
# True
9. 標準ライブラリと実用的な問題解決パターン (Standard Library & Practical Problem-Solving Patterns)
-
Counter.most_common(n) は、頻度の高い要素を top-n で出したい時の第一候補である。
from collections import Counter
counts = Counter("banana")
print(counts.most_common(2)) # [('a', 3), ('n', 2)]
-
defaultdict(list) と defaultdict(int) は、分組と計数を if 分岐なしで書ける。
from collections import defaultdict
groups = defaultdict(list)
totals = defaultdict(int)
groups["BJ"].append("Ann")
totals["python"] += 1
totals["python"] += 1
print(dict(groups)) # {'BJ': ['Ann']}
print(dict(totals)) # {'python': 2}
-
deque(maxlen=...) は、最新 N 件だけを残す固定長ウィンドウに向いている。
from collections import deque
window = deque(maxlen=3)
for value in [10, 20, 30, 40]:
window.append(value)
print(list(window)) # [20, 30, 40]
-
heapq.nlargest() / heapq.nsmallest() は、全件ソートせずに top-k を抜きたい時に実用的である。
import heapq
nums = [8, 2, 6, 1, 5]
print(heapq.nsmallest(2, nums)) # [1, 2]
print(heapq.nlargest(2, nums)) # [8, 6]
10. PEP 8 の要点 (PEP 8 Essentials)
頻出ポイント
- インデント(indentation)は 4 スペースで統一し、Tab と混在させない。
- 関数名・変数名は
snake_case を使う。
- クラス名は
CamelCase を使う。
- 定数(constant)は通常
UPPER_CASE を使う。
- モジュール名(module name)は短く小文字を基本とし、必要時のみアンダースコアを使う。
- 演算子の前後には空白を置く。例:
x = a + b
- カンマの後には空白を置く。例:
['a', 'b', 'c']
- トップレベルの関数定義とクラス定義の間は 2 行空ける。
- メソッド定義の間は 1 行空ける。
-
if x > 0: の形を使い、if(x>0): は避ける。
-
import は基本的にファイル先頭に置き、標準ライブラリ・第三者ライブラリ・ローカルモジュールで分ける。
- 1 行の長さ(line length)は長くしすぎず、79 または 88 桁以内を目安にする。
よくあるミス
- 命名規則が混在する。例:
userName, User_name, user-name
-
if(x>0): のような密着スタイルで可読性を落とす。
-
a=[1,2,3] のように空白を省略して読みづらくする。
- クラス名を
student_record、関数名を GetUserData() にするなど慣例に反する。
-
l, O, I のように 1, 0 と混同しやすい 1 文字変数名を使う。
-
import の順序が乱れ、標準ライブラリ・第三者ライブラリ・ローカルモジュールを混在させる。
- コメントが「何をしているか」だけを繰り返し、「なぜそうするか」を説明しない。
発展補足
- PEP 8 の核心は条文暗記ではなく、一貫性(consistency)と可読性(readability)である。
- 既存プロジェクトにスタイルがある場合は、まずその既定スタイルに合わせる。
- docstring は PEP 257 の方針に沿うが、復習段階では「関数の説明書」と捉えればよい。
- 長い式の改行は、バックスラッシュより括弧を優先する。
- 真偽値判定は
if is_valid: のように直接書き、if is_valid == True: は避ける。
-
None 比較は is None / is not None を優先し、== None は避ける。
- 単発ルールを丸暗記するより、プロジェクト全体の一貫性を優先する。
命名規約
| 対象 |
推奨スタイル |
例 |
| 変数 / 関数 |
snake_case |
user_name, load_data()
|
| クラス |
CamelCase |
StudentRecord, DataLoader
|
| 定数 |
UPPER_CASE |
MAX_RETRY, DEFAULT_TIMEOUT
|
| 非公開ヘルパー関数 |
_leading_underscore |
_parse_row() |
| メソッド引数 self / cls |
固定慣例 |
self, cls
|
詳細コメント付きの悪い例と良い例
# Bad example: 動くが、スタイル不統一で可読性が低い
class student_record:
def GetUserData(self,UserName,UserAge):
if(UserAge>0):
print(UserName,UserAge)
else:
print('bad age')
# このコードの問題点
# 1. クラス名 student_record は CamelCase の StudentRecord にすべき
# 2. インデントが 2 スペースで、4 スペース規約に反する
# 3. メソッド名 GetUserData は snake_case の get_user_data にすべき
# 4. 引数名 UserName / UserAge が snake_case に沿っていない
# 5. if の後ろに不要な括弧を付けている
# 6. print(UserName,UserAge) はカンマ後の空白がない
# 7. docstring などで意図を補足できる
# Good example: PEP 8 に沿った書き方
class StudentRecord:
def get_user_data(self, user_name, user_age):
"""Print user info if age is valid."""
if user_age > 0:
print(user_name, user_age)
else:
print('bad age')
詳細コメント付きの推奨スタイル例
# PEP 8: import はファイル先頭に置き、標準ライブラリを先に並べる
import math
from pathlib import Path
# PEP 8: 定数名は UPPER_CASE
MAX_RETRY = 3
DEFAULT_PATH = Path('data.txt')
# PEP 8: クラス名は CamelCase
class OrderSummary:
# PEP 257: 公開クラスには docstring を付ける
"""Represent a simple order summary."""
def __init__(self, order_id, item_count, total_amount):
self.order_id = order_id
self.item_count = item_count
self.total_amount = total_amount
# PEP 8: メソッド名は snake_case
def average_price(self):
"""Return average price per item."""
if self.item_count == 0:
return 0.0
return self.total_amount / self.item_count
# PEP 8: bool を返す関数名は is_/has_/can_ が読みやすい
def is_large_order(self):
return self.total_amount >= 1000
# PEP 8: 関数名・引数名は snake_case
def calculate_discount(price, discount_rate):
# PEP 257: 関数にも docstring を付ける
"""Return discounted price.
Args:
price: Original price.
discount_rate: A float between 0 and 1.
Returns:
Discounted price.
"""
if not 0 <= discount_rate <= 1:
raise ValueError('discount_rate must be between 0 and 1')
# PEP 8: 演算子の前後には空白を置く
final_price = price * (1 - discount_rate)
return round(final_price, 2)
def load_scores(file_path):
"""Read numeric scores from a text file."""
scores = []
# PEP 8: with を使ったリソース管理と、意味のある変数名(file_obj)
with open(file_path, 'r', encoding='utf-8') as file_obj:
for line in file_obj:
line = line.strip()
if not line:
continue
score = float(line)
scores.append(score)
return scores
def summarize_scores(scores):
"""Return summary statistics for a list of scores."""
if not scores:
# PEP 8: 長い辞書リテラルは複数行にして可読性を保つ
return {
'count': 0,
'mean': 0.0,
'max': None,
'min': None,
}
return {
'count': len(scores),
'mean': sum(scores) / len(scores),
'max': max(scores),
'min': min(scores),
}
def main():
sample_scores = [88, 92, 79, 95]
stats = summarize_scores(sample_scores)
print(stats)
print(calculate_discount(100, 0.15))
print(math.sqrt(16))
# PEP 8: モジュールの実行入口はファイル末尾に置く
if __name__ == '__main__':
main()
空白・改行・括弧の頻出規約
# サンプル値を先に置くと、Bad / Good の両方をそのまま実行できる
price, count, tax = 100, 2, 8
user_age = 20
first_value, second_value, third_value, fourth_value, fifth_value = 1, 2, 3, 4, 5
# Bad
result=price*count+tax
if(user_age>18):
print('adult')
# Good
result = price * count + tax
if user_age > 18:
print('adult')
# Bad: リストや引数が詰まりすぎ
names=['ann','bob','cindy']
total=sum([1,2,3])
# Good
names = ['ann', 'bob', 'cindy']
total = sum([1, 2, 3])
# Bad: バックスラッシュでの強引な継続
value = first_value + second_value + third_value + fourth_value + \
fifth_value
# Good: 括弧で改行
value = (
first_value
+ second_value
+ third_value
+ fourth_value
+ fifth_value
)
コメントと docstring の書き方
price, tax = 100, 8
# Bad comment: コードの表面説明を繰り返すだけ
total = price + tax # add price and tax
# Better comment: ビジネス上の意図を説明
total = price + tax # tax is added here because downstream report expects gross amount
def normalize_name(name):
"""Return a normalized user name with trimmed spaces and title case."""
cleaned_name = name.strip()
return cleaned_name.title()
import の一般的な順序
# 1. 標準ライブラリ(standard library)
import math
import os
from pathlib import Path
# 2. 第三者ライブラリ(third-party libraries)
import numpy as np
import pandas as pd
# 3. ローカルモジュール(local application imports)
# from myproject.utils import load_config