モダンPython基礎 #5 関数 — 引数パターン
#4 コレクションと内包表記 でデータを扱う道具を見たなら、今回は そのデータを受け取って働く関数 のシグネチャの書き方です。Python の関数は 引数の表記がとても豊富 です。それが強みですが、最初は混乱するところでもあります。一箇所に集めて整理します。
最も単純な関数 #
def greet(name: str) -> None:
print(f"hi, {name}!")
greet("カーティス") # hi, カーティス!def キーワード、コロンの後にインデント。#2 で見た型ヒント (: str、-> None) をすべての関数に書くのがモダン Python の約束です。
return と多重戻り値
#
def split_name(full: str) -> tuple[str, str]:
first, last = full.split(" ", 1)
return first, last
first, last = split_name("カーティス キム")
print(first, last) # カーティス キムreturn a, b は実は return (a, b) です。受け取る側は unpack で開きます。JavaScript の分割代入と似た効用を tuple で行います。
return がなければ関数は None を返します。 明示的に書いても、書かなくても結果は同じです。
デフォルト値 #
def greet(name: str, greeting: str = "hi") -> None:
print(f"{greeting}, {name}!")
greet("カーティス") # hi, カーティス!
greet("カーティス", "こんにちは") # こんにちは, カーティス!デフォルト値のある仮引数は ない仮引数の後ろ に来る必要があります。
def bad(x: int = 0, y: int) -> int: ...
# SyntaxError: non-default argument follows default argument罠 — 可変デフォルト値 #
これは Python で最も有名な罠です。
def append_to(item, target=[]):
target.append(item)
return target
print(append_to(1)) # [1]
print(append_to(2)) # [1, 2] ← ?!
print(append_to(3)) # [1, 2, 3]デフォルト値 [] が 関数定義時に一度だけ作られ、すべての呼び出しで同じオブジェクトを共有します。呼び出すたびに新しい空リストができるわけではありません。
解決策: None をデフォルト値にして、関数の中で新しく作る。
def append_to(item, target: list[int] | None = None):
if target is None:
target = []
target.append(item)
return targetこのパターンは本当によく見かけます。覚えておいてください。
Positional と Keyword 呼び出し #
呼び出し時に引数を二つの方法で渡せます。
def create_user(name: str, age: int, role: str = "member") -> dict:
return {"name": name, "age": age, "role": role}
# positional — 位置で
create_user("カーティス", 30)
create_user("カーティス", 30, "admin")
# keyword — 名前で
create_user(name="カーティス", age=30)
create_user(age=30, name="カーティス") # 順序を変えても OK
# 混ぜて使う (positional が先)
create_user("カーティス", age=30, role="admin")呼び出しコードだけで意図が見えるようにするには keyword 呼び出し が強力です。create_user("カーティス", 30, True, False, "admin") のようなシグネチャは読みづらいですが、create_user(name="カーティス", age=30, is_active=True, is_admin=False, role="admin") は一目で分かります。
*args — 可変位置引数
#
仮引数の前に * を付けると「残りすべての位置引数を tuple で受け取る」という意味になります。
def add(*nums: int) -> int:
total = 0
for n in nums:
total += n
return total
print(add(1, 2, 3)) # 6
print(add(1, 2, 3, 4, 5)) # 15
print(add()) # 0名前は慣例上 args ですが、*nums のように意味のある名前を付けても構いません。
開いて渡す #
* は 呼び出す側 でも使えます。シーケンスを開いて位置引数として渡します。
def add(a: int, b: int, c: int) -> int:
return a + b + c
nums = [1, 2, 3]
print(add(*nums)) # 1, 2, 3 に開かれて入る → 6**kwargs — 可変キーワード引数
#
** を付けると「残りすべてのキーワード引数を dict で受け取る」。
def configure(**options: str) -> None:
for key, value in options.items():
print(f"{key} = {value}")
configure(host="localhost", port="5432", db="myapp")
# host = localhost
# port = 5432
# db = myapp開いて渡す #
呼び出し側の ** は dict を開いて keyword 引数として渡します。
def create_user(name: str, age: int, role: str) -> dict:
return {"name": name, "age": age, "role": role}
data = {"name": "カーティス", "age": 30, "role": "admin"}
print(create_user(**data))API レスポンスを関数引数にそのまま流す場面でよく使います。
全部合わせた形 #
def fn(a: int, b: int, *args: int, **kwargs: str) -> None:
print(a, b, args, kwargs)
fn(1, 2, 3, 4, 5, name="カーティス", role="admin")
# 1 2 (3, 4, 5) {'name': 'カーティス', 'role': 'admin'}順序: 通常の引数 → *args → 通常の引数 (keyword-only) → **kwargs です。
Keyword-only — * 単独使用
#
* だけを置くと、その後の仮引数は 必ず keyword でのみ受け取る ようになります。
def create(name: str, *, role: str = "member", active: bool = True) -> dict:
return {"name": name, "role": role, "active": active}
create("カーティス") # OK
create("カーティス", role="admin") # OK
create("カーティス", "admin") # ✗ TypeError
# create() takes 1 positional argument but 2 were given呼び出し側が create("カーティス", "admin", True) のような形を使えなくします。ブール値のフラグが複数あるとき に呼び出しを意味のある形に強制できます。
Positional-only — / 単独使用 (3.8+)
#
逆に / の前は 位置でのみ 受け取るよう強制します。
def power(base: float, exp: float = 2, /) -> float:
return base ** exp
power(3) # 9
power(2, 3) # 8
power(base=3) # ✗ TypeError — base は keyword 不可いつ使うのか? 仮引数名を将来変える自由を残しておきたいとき。 呼び出し側が名前で縛られていなければ、ライブラリのメンテナーは名前をリネームしても呼び出し側のコードを壊しません。組み込み関数 (abs(x)、len(seq)) はほとんどがこの形です。
三つを一箇所に #
def f(pos1: int, pos2: int, /, normal: int, *, kw1: int, kw2: int) -> None:
print(pos1, pos2, normal, kw1, kw2)
# pos1, pos2 → 位置のみ
# normal → 位置または keyword
# kw1, kw2 → keyword のみ
f(1, 2, 3, kw1=4, kw2=5) # OK
f(1, 2, normal=3, kw1=4, kw2=5) # OK
f(pos1=1, pos2=2, ...) # ✗ pos1, pos2 は keyword 不可
f(1, 2, 3, 4, 5) # ✗ kw1, kw2 は位置不可読み方:
/の 前 = positional-only/と*の 間 = 通常*の 後ろ = keyword-only
lambda — 一行関数 #
lambda は無名関数です。式を一つだけ 持てます。
square = lambda x: x ** 2
print(square(5)) # 25
# sorted の key のようなコールバック位置でよく使う
people = [("カーティス", 30), ("スミス", 25), ("ジョン", 40)]
people.sort(key=lambda p: p[1])
print(people)
# [('スミス', 25), ('カーティス', 30), ('ジョン', 40)]lambda を変数に代入して使うパターン (square = lambda x: ...) は 推奨されません。 普通に def square(x): ... と書いてください。lambda は名前が要らない場面でだけ使うのが慣例です。
nonlocal と global — スコープを少しだけ
#
関数の中で外の変数を 修正 するには明示が必要です。
def outer():
count = 0
def inner():
nonlocal count # outer の count を修正すると宣言
count += 1
inner()
inner()
print(count) # 2
outer()nonlocal なしで count += 1 をすると 新しいローカル変数 が inner の中に作られ、outer のものは変わりません (正確には UnboundLocalError が起きます — 読み込み前に書き込みをするため)。
グローバル変数を関数内で修正するには global を使います。ただし可能なら グローバル変数自体を使わない 方が良いです。
関数も第一級オブジェクト #
関数は変数に入れられ、引数として渡せ、戻り値にもできます。
def add(a: int, b: int) -> int:
return a + b
op = add # 関数を変数に入れる
print(op(2, 3)) # 5
def apply(fn, x: int, y: int) -> int:
return fn(x, y)
print(apply(add, 2, 3)) # 5この性質が旧講座のデコレータとクロージャの土台です。モダンシリーズでは 次のシリーズ (Python 中級) でデコレータを再び扱います。
Callable 型 — 関数を受け取る関数の型
#
関数を引数として受け取るとき、そのシグネチャを書きたいなら Callable を使います。
from collections.abc import Callable
def apply(fn: Callable[[int, int], int], x: int, y: int) -> int:
return fn(x, y)
apply(lambda a, b: a + b, 1, 2) # 3Callable[[引数の型たち], 戻り値の型] の形です。引数の型をリストで 書くのが最初は違和感があります。
docstring — ドキュメンテーション文字列 #
関数の最初の行の文字列が docstring になります。
def add(a: int, b: int) -> int:
"""二つの整数を足して返す。"""
return a + b
print(add.__doc__)
# 二つの整数を足して返す。
help(add)型ヒントが「何を受け取って何を返すか」を埋めてくれるので、docstring は 「なぜ / どう使うか」 を書くところです。短く言い切る一行がベストです。
まとめ #
今回扱ったすべての引数パターン:
- デフォルト値 — 可変オブジェクト (list/dict) を絶対にデフォルト値にせず、
None+ 関数内で生成 - positional vs keyword 呼び出し — keyword 呼び出しは意図が伝わりやすい
*args— 可変位置引数、tuple で受け取る**kwargs— 可変キーワード引数、dict で受け取る- 呼び出し側
*seq、**dict— 開いて渡す *,の後ろは keyword-only — ブールフラグが複数あるとき呼び出しの可読性を強制/,の前は positional-only — 仮引数名の自由を保つlambdaはコールバック位置で、変数代入はしないnonlocalで外側スコープの変数を修正- 関数は第一級オブジェクト —
Callable[[...], ...]で型ヒント - docstring は
"""..."""で最初の行に
次回 (#6 エラーと例外処理) では try/except/else/finally とユーザー定義例外、そして Python 3.11 で入った except* (exception group) まで扱います。