タイトル: 関数
SEOタイトル: Python 関数完全ガイド (def / lambda / 型ヒント / デコレータ / generator)
| この記事の要点 |
|
関数の基本定義
Python の関数は def キーワードで定義します。インデント (PEP 8 では 4 スペース) でブロック範囲を示します。
def greet(name):
"""挨拶を返す関数 (docstring)"""
return f"Hello, {name}!"
print(greet("Alice")) # Hello, Alice!
# 戻り値が無い関数 (None を返す)
def log(msg):
print(f"[LOG] {msg}")
result = log("started")
print(result) # None
引数の 5 つのスタイル
| 種類 | 書き方 | 呼び出し例 |
|---|---|---|
| 位置引数 | def f(a, b): | f(1, 2) |
| キーワード引数 | def f(a, b): | f(a=1, b=2) |
| デフォルト値 | def f(a, b=10): | f(1) → b=10 |
可変長位置 *args | def f(*args): | f(1, 2, 3) |
可変長キーワード **kwargs | def f(**kwargs): | f(x=1, y=2) |
def example(a, b=10, *args, key=None, **kwargs):
print(a, b, args, key, kwargs)
example(1)
# 1 10 () None {}
example(1, 2, 3, 4, key="abc", extra="z")
# 1 2 (3, 4) abc {'extra': 'z'}
# キーワード専用引数 (* の後はキーワード必須)
def calc(x, y, *, mode="add"):
return x + y if mode == "add" else x - y
calc(1, 2, mode="sub") # OK
# calc(1, 2, "sub") # TypeError: positional 不可
戻り値とアンパック
# 複数値はタプルで返る
def minmax(nums):
return min(nums), max(nums)
lo, hi = minmax([3, 1, 4, 1, 5]) # アンパック
print(lo, hi) # 1 5
# dict や名前付きタプルで返す方が可読性高い
from collections import namedtuple
Result = namedtuple("Result", ["min", "max"])
def minmax2(nums):
return Result(min(nums), max(nums))
r = minmax2([3, 1, 4])
print(r.min, r.max)
型ヒント (Python 3.5+)
型ヒントは実行時にチェックされないドキュメント・IDE 補完のための注釈です。mypy / pyright で静的解析できます。
from typing import Optional, Callable
def greet(name: str, times: int = 1) -> str:
return f"Hello, {name}!" * times
# Optional[X] = X | None (Python 3.10+ は X | None)
def find_user(id: int) -> Optional[dict]:
...
# 関数型 (Callable)
def apply(fn: Callable[[int, int], int], x: int, y: int) -> int:
return fn(x, y)
# Python 3.9+ 組込ジェネリクス
def total(nums: list[int]) -> int:
return sum(nums)
無名関数 lambda
# 短い式専用の無名関数
square = lambda x: x * x
print(square(5)) # 25
# sorted / map / filter と組み合わせて使う
users = [{"name": "B", "age": 30}, {"name": "A", "age": 25}]
sorted(users, key=lambda u: u["age"])
# 複数行は def を使う (lambda は式 1 つのみ)
スコープと LEGB ルール
Python の名前解決は Local → Enclosing → Global → Built-in の順で探します。
x = "global" # G
def outer():
x = "enclosing" # E
def inner():
x = "local" # L
print(x) # local
inner()
print(x) # enclosing
outer()
print(x) # global
# global 宣言 でグローバル変更
counter = 0
def incr():
global counter
counter += 1
# nonlocal で enclosing 変更
def make_counter():
count = 0
def add():
nonlocal count
count += 1
return count
return add
関数は第一級オブジェクト
関数は変数に代入したり、引数として渡したり、戻り値にしたりできます。これがデコレータの基盤です。
def double(x):
return x * 2
f = double # 関数を変数代入
print(f(5)) # 10
# 高階関数
def twice(fn, x):
return fn(fn(x))
print(twice(double, 3)) # 12
# クロージャ
def make_adder(n):
def adder(x):
return x + n
return adder
add10 = make_adder(10)
print(add10(5)) # 15
デコレータ
import time
from functools import wraps
def timing(fn):
@wraps(fn) # 元関数の __name__ などを保持
def wrapper(*args, **kwargs):
start = time.time()
result = fn(*args, **kwargs)
print(f"{fn.__name__}: {time.time() - start:.3f}s")
return result
return wrapper
@timing
def slow():
time.sleep(0.5)
slow() # slow: 0.501s
# 引数付きデコレータ
def retry(times=3):
def deco(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
for i in range(times):
try:
return fn(*args, **kwargs)
except Exception:
if i == times - 1:
raise
return wrapper
return deco
@retry(times=5)
def unstable_api():
...
再帰関数
def fact(n):
if n <= 1:
return 1
return n * fact(n - 1)
print(fact(5)) # 120
# Python 既定の再帰深度は 1000 → 深い再帰は注意
import sys
sys.setrecursionlimit(10000)
# 反復で書ける場合は反復推奨 (末尾再帰最適化なし)
def fact_iter(n):
result = 1
for i in range(2, n + 1):
result *= i
return result
ジェネレータ (yield)
yield を含む関数はジェネレータ関数になり、巨大データを遅延評価で処理できます。
def count_up(n):
i = 0
while i < n:
yield i
i += 1
for x in count_up(3):
print(x) # 0, 1, 2
# メモリ効率: 巨大ファイルを 1 行ずつ処理
def read_lines(path):
with open(path) as f:
for line in f:
yield line.rstrip()
for line in read_lines("huge.txt"):
process(line)
# ジェネレータ式 (リスト内包の () 版)
squares = (x * x for x in range(1000000))
FAQ
Q: 引数のデフォルト値にリストを使うと共有される
A: def f(a=[]) はモジュール初期化時に 1 度だけ [] が作られ、呼び出し間で共有されます。def f(a=None): a = a or [] が定石です。
Q: *args と **kwargs を同時に使える?
A: 可能です。並び順は def f(a, b=1, *args, c, **kwargs):。*args の後は全てキーワード専用引数になります。
Q: 関数内で別の関数を定義するのは普通?
A: クロージャやデコレータでは普通です。ただし毎回呼び出しのたびに再定義されるので、性能が必要なホットパスではモジュールトップに置きます。