タイトル: 関数の定義
SEOタイトル: Python 関数定義完全ガイド(def / 引数 / 戻り値 / Type Hint / lambda / yield)
| この記事の要点 |
|
基本構文: def
def greet(name: str) -> str:
"""挨拶文字列を返す(docstring)"""
return f"Hello, {name}!"
print(greet("taro")) # Hello, taro!
# 引数なし、戻り値なし
def show():
print("Hello")
show()
引数の 6 種類
def func(
pos_only, # 通常位置引数
/, # ← この前は位置専用 (Python 3.8+)
normal, # 位置でもキーワードでも可
*, # ← この後はキーワード専用
kw_only, # キーワード専用
default_kw="x", # デフォルト付きキーワード専用
):
...
# 呼び出し
func(1, 2, kw_only=3) # OK
func(1, normal=2, kw_only=3) # OK (normal はキーワードも可)
# func(pos_only=1, normal=2, kw_only=3) # ❌ pos_only は位置専用
# func(1, 2, 3) # ❌ kw_only はキーワード必須
デフォルト引数の落とし穴
デフォルト引数の値は関数定義時に 1 回だけ評価されます。可変オブジェクト(list / dict)をデフォルトにすると、呼び出しごとに同じインスタンスが使い回されます。
# ❌ 危険: 可変オブジェクトをデフォルトに
def append_item(item, items=[]):
items.append(item)
return items
print(append_item(1)) # [1]
print(append_item(2)) # [1, 2] ← 共有されている!
print(append_item(3)) # [1, 2, 3]
# ✅ 正しいパターン
def append_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
*args と **kwargs(可変長引数)
def func(*args, **kwargs):
print(args) # tuple
print(kwargs) # dict
func(1, 2, 3, name="taro", age=30)
# args = (1, 2, 3)
# kwargs = {'name': 'taro', 'age': 30}
# アンパック呼び出し
def add(a, b, c):
return a + b + c
nums = [1, 2, 3]
print(add(*nums)) # 6
params = {"a": 1, "b": 2, "c": 3}
print(add(**params)) # 6
戻り値: return と複数値
def divmod_(a, b):
"""商と余りをタプルで返す"""
return a // b, a % b # 実はタプル
q, r = divmod_(10, 3) # アンパック
print(q, r) # 3 1
# return を書かないと None
def show(x):
print(x)
result = show(1) # 1
print(result) # None
# 途中 return で早期リターン
def first_positive(nums):
for n in nums:
if n > 0:
return n
return None
Type Hint(型ヒント)
from typing import Optional, Union, Callable
def parse(s: str) -> int:
return int(s)
def find(name: str) -> Optional[dict]:
# None もありうる
return None
# Python 3.10+ の Union 短縮記法
def fetch(id: int) -> dict | None:
...
# 関数型
def apply(fn: Callable[[int, int], int], a: int, b: int) -> int:
return fn(a, b)
# 実行時に強制はされない(型違いでも動く)
parse(123) # 動くが mypy は警告
ラムダ式(無名関数)
square = lambda x: x ** 2
print(square(5)) # 25
# 高階関数と組み合わせ
nums = [3, 1, 4, 1, 5, 9, 2, 6]
print(sorted(nums, key=lambda x: -x)) # 降順
print(list(filter(lambda x: x % 2 == 0, nums)))
print(list(map(lambda x: x * 10, nums)))
# 注意: ラムダは式 1 つのみ。文 (if / for / try) は書けない
# 複雑なら def を使う
ジェネレータ関数(yield)
yield を含む関数はジェネレータになり、呼び出し時に即実行せずイテレータを返す。メモリ効率がよい。
def count_up(n):
i = 0
while i < n:
yield i # 値を 1 つ返して中断、次の next() で再開
i += 1
for x in count_up(3):
print(x) # 0, 1, 2
# 無限ストリームも可
def fib():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
from itertools import islice
print(list(islice(fib(), 10)))
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
async def(非同期関数)
import asyncio
import aiohttp
async def fetch(url: str) -> str:
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
return await resp.text()
async def main():
urls = ["https://example.com", "https://example.org"]
results = await asyncio.gather(*(fetch(u) for u in urls))
print(len(results))
asyncio.run(main())
デコレータ
import time
from functools import wraps
def measure(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
t0 = time.time()
result = fn(*args, **kwargs)
print(f"{fn.__name__}: {time.time() - t0:.3f}s")
return result
return wrapper
@measure
def slow_sum(n):
return sum(range(n))
slow_sum(10_000_000)
# slow_sum: 0.234s
関数の一級オブジェクト性
# 変数に代入
f = print
f("Hello") # Hello
# 引数として渡す
def apply(fn, x):
return fn(x)
apply(str.upper, "hello") # 'HELLO'
# 戻り値として返す(クロージャ)
def make_adder(n):
def adder(x):
return x + n
return adder
add5 = make_adder(5)
print(add5(10)) # 15
print(add5(20)) # 25
docstring と help()
def factorial(n: int) -> int:
"""n の階乗を計算する。
Args:
n: 0 以上の整数
Returns:
n! の値
Raises:
ValueError: n が負のとき
"""
if n < 0:
raise ValueError("n must be >= 0")
return 1 if n <= 1 else n * factorial(n - 1)
help(factorial) # docstring が表示される
print(factorial.__doc__)
FAQ
Q: 関数内で外側の変数を書き換えたい
A: global x でグローバル、nonlocal x で 1 つ外の関数スコープ。
Q: 再帰の深さ制限はある?
A: デフォルト 1000。sys.setrecursionlimit(10000) で変更可。Python は末尾再帰最適化を持たないためループに書き換えるのが原則。
Q: 関数の引数を強制的に型チェックしたい
A: 標準では型ヒントは強制されません。実行時チェックには pydantic.validate_call / typeguard / beartype を使います。