Pythonの関数:def、return、引数、*argsと**kwargs、lambda、型注釈
Pythonの関数は、名前を付けたコードのまとまりで、def で定義し、名前と丸かっこで呼び出し、return で結果を返します。return がなければ None が返ります。引数は順番どおりにも名前を指定しても渡せます。*args は余分な位置引数をタプルに、**kwargs は余分な名前付き引数を辞書にまとめます。lambda は1つの式だけからなる短い無名関数を作ります。
Pythonの関数は、名前の付いたコードのまとまりです。何度でも、異なる入力を渡して呼び出せます。def で定義し、引数で値を受け取り、return で結果を返します。長いスクリプトを、分かりやすくテストしやすい手順に分ける最も基本的な方法です。
関数の定義:def
関数の定義は def で始まり、関数名、丸かっこの中の引数、コロンが続きます。本体は4つのスペースでインデントします。Pythonではインデントは飾りではなく、文法の一部です。
def greet(name):
"""ユーザーへの挨拶を返す。"""
return f"こんにちは、{name}さん!"
print(greet("花子")) # こんにちは、花子さん!
見出しの直後に三重引用符で書いた文字列は、docstring(ドキュメント文字列)です。help(greet) やエディタのヒントに表示されます。関数名は、PEP 8の慣習に従ってsnake_caseで書きます。例:add_expense、get_rate。
def greet(name): の部分を関数のシグネチャ(署名)といいます。関数名と引数の一覧(型注釈があればそれも)から成ります。シグネチャを見れば、本体を読まなくても呼び出し方が分かります。
Pythonのreturnの働き
return は関数を終了させ、呼び出し元に値を渡します。return が実行された後のコードは実行されません。return がない場合、または値なしの return の場合、関数は None を返します。
def area(w, h):
w * h # 計算はされるが、結果は捨てられる
print(area(3, 4)) # None
def area(w, h):
return w * h
print(area(3, 4)) # 12
return を書き忘れるのは、初心者に最も多いミスの1つです。関数は動くのに None が返り、エラーは次の行で見つかります。複数の値を返すこともできます。実際には1つのタプルで、分解代入で受け取れます。
def min_max(numbers):
return min(numbers), max(numbers)
low, high = min_max([4, 1, 9])
print(low, high) # 1 9
早めに return すると、入れ子を減らせます。まず特殊なケースを除外し、その後に本来の処理を書きます。
def discount(price, is_member):
if price <= 0:
return 0
if not is_member:
return price
return price * 0.9
関数の引数:位置引数と名前付き引数
パラメータは関数の定義にある名前、引数は呼び出し時に渡す値です。引数は順番に(位置引数として)渡すこともできますし、名前を指定して(名前付き引数として)渡すこともできます。名前付き引数を使うと呼び出しが自己説明的になり、順番を気にしなくてよくなります。
def add_expense(title, amount, category="その他"):
return {"title": title, "amount": amount, "category": category}
add_expense("コーヒー", 250) # 位置引数
add_expense(amount=250, title="コーヒー") # 名前付き
add_expense("タクシー", 600, category="交通費") # 組み合わせ
デフォルト値を持つパラメータは省略できます。ルールは次のとおりです。デフォルト値のあるパラメータは、必須のパラメータの後に置きます。呼び出し時には、位置引数を名前付き引数より前に書きます。守られていないと、Pythonは SyntaxError を出します。
注意:変更可能なデフォルト値
デフォルト値は、関数が呼び出されるたびではなく、def の行が実行されたときに一度だけ評価されます。デフォルト値がリストや辞書の場合、すべての呼び出しで同じオブジェクトが共有されます。
def add_item(item, acc=[]):
acc.append(item)
return acc
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2] — リストが前回の呼び出しを覚えている
正しい方法は、デフォルト値を None にして、関数の中で新しいオブジェクトを作ることです。
def add_item(item, acc=None):
if acc is None:
acc = []
acc.append(item)
return acc
*argsと**kwargs:任意の数の引数
パラメータの前にアスタリスク1つを付けると、余分な位置引数がすべてタプル(tuple)に集められます。アスタリスク2つを付けると、余分な名前付き引数がすべて辞書(dict)に集められます。args と kwargs という名前は慣習にすぎません。大切なのはアスタリスクの方です。
def total(*args):
return sum(args)
print(total(100, 250, 40)) # 390
def make_user(name, **kwargs):
user = {"name": name}
user.update(kwargs)
return user
print(make_user("花子", city="大阪", age=30))
# {'name': '花子', 'city': '大阪', 'age': 30}
同じアスタリスクは逆方向にも使えます。関数を呼び出すときに、並びや辞書を引数に展開します。
values = [3, 4]
print(pow(*values)) # 81
opts = {"sep": " | ", "end": "\n"}
print("a", "b", **opts) # a | b
位置専用パラメータと名前専用パラメータ
パラメータ一覧の中にある、スラッシュ / とアスタリスク * は、関門のような働きをします。/ より前のパラメータは順番に渡すしかありません(Python 3.8で導入されたPEP 570)。* より後のパラメータは名前で渡すしかありません。
def convert(amount, /, *, rate, currency="USD"):
return round(amount / rate, 2), currency
convert(100, rate=90.5) # (1.1, 'USD')
convert(amount=100, rate=90.5) # TypeError: amount は位置専用
convert(100, 90.5) # TypeError: rate は名前でのみ渡せる
名前専用のパラメータは、convert(100, 90.5, "EUR") のような、何がどの値か分かりにくい呼び出しを防ぎます。
変数のスコープ:LEGBの規則
Pythonは変数の名前を次の順で探します。Local(関数の中)→ Enclosing(外側の関数)→ Global(モジュールの最上位)→ Built-in(len のような組み込みの名前)。関数の中から大域変数を読むことはできますが、その値に新しい代入をするには global 宣言が必要です。
limit = 1000
def check(amount):
return amount > limit # 大域変数の読み取りは可能
def raise_limit():
global limit
limit = 2000 # global がないと、ローカル変数が新しく作られる
実際には global はあまり使わない方が良いでしょう。外部の状態を変更する関数はテストしにくくなります。値を引数で受け取り、結果を返すようにする方が確実です。
入れ子の関数とクロージャ
関数の中に別の関数を定義することもできます。内側の関数は、外側の関数が終わった後でも外側の変数を参照できます。これをクロージャ(閉包)といいます。こうした変数を変更するには nonlocal を使います。
def make_counter():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter
c = make_counter()
print(c(), c(), c()) # 1 2 3
クロージャは、デコレータや関数を生成するファクトリの基礎になっています。
関数もオブジェクト
Pythonでは、関数は普通のオブジェクトです。変数に代入でき、別の関数に渡し、関数から返し、辞書に保存できます。関数には __name__、__doc__、__defaults__、__annotations__ といった属性があります。
def double(x):
return x * 2
f = double
print(f(5), f.__name__) # 10 double
print(list(map(double, [1, 2, 3]))) # [2, 4, 6]
Pythonのlambda:無名関数
lambda 関数は、名前のない短い関数です。本体は1つの式だけで、その結果が自動的に返されます。return は書けません。
square = lambda x: x ** 2
print(square(4)) # 16
expenses = [("コーヒー", 250), ("タクシー", 600), ("ランチ", 450)]
expenses.sort(key=lambda e: e[1], reverse=True)
print(expenses) # [('タクシー', 600), ('ランチ', 450), ('コーヒー', 250)]
print(list(filter(lambda n: n % 2 == 0, range(10)))) # [0, 2, 4, 6, 8]
Pythonのlambdaは、sorted、max、map、filter に渡す短い引数として役立ちます。上の square のように、lambdaを変数に代入するのはPEP 8で推奨されていません。関数に名前が必要なら def を使いましょう。そうすれば、トレースバックに正しい __name__ が表示され、docstringも書けます。
| def | lambda | |
|---|---|---|
| 名前 | あり | なし(<lambda>) |
| 本体 | 任意の数の文 | 1つの式 |
| return | 明示的 | 暗黙的 |
| docstring、注釈 | あり | なし |
| 向いている場面 | あらゆる場面 | 短い key やコールバック |
型注釈:ヒントであって、検査ではない
型注釈は、パラメータと戻り値に期待される型を示します。Python自身は実行時に注釈をチェックしません。x: int と書かれた関数に文字列を渡しても、そのまま動きます。注釈を検査するのは、mypy、pyright、ty などの外部ツールや、実行前にエラーを表示するエディタです。
def add_expense(title: str, amount: float, category: str = "その他") -> dict[str, object]:
return {"title": title, "amount": amount, "category": category}
def area(w: float, h: float) -> float:
w * h # mypy: Missing return statement
インストールせずに検査するには、uvx mypy . を実行します。上の例は注釈の効果を示しています。-> float と書いた関数で return を忘れると、解析ツールがすぐに見つけてくれます。
Python 3.12(PEP 695)からは、ジェネリックな関数を TypeVar なしで書けます。型パラメータは角かっこで指定し、型のエイリアスは type 文で宣言します。
def first[T](xs: list[T]) -> T:
return xs[0]
type Money = int | float
Python 3.14(PEP 649)では、注釈が遅延評価されるようになりました。注釈は最初に参照されたときに評価されるため、後で定義されるクラスを引用符なしで書けます。from __future__ import annotations も不要です。
「渡されなかった」と「None が渡された」の区別:Python 3.15のsentinel
デフォルト値が None だと、「引数が渡されなかった」のと「あえて None が渡された」のを区別できません。長年、次のような独自の目印を使って解決されてきました。
_MISSING = object()
def get_rate(currency, default=_MISSING):
rates = {"USD": 90.5}
if currency in rates:
return rates[currency]
if default is _MISSING:
raise KeyError(currency)
return default
Python 3.15 では、組み込みの sentinel 関数が追加されました(PEP 661)。MISSING = sentinel("MISSING") と書くと、分かりやすい repr を持つ一意のマーカーが作られます。コピーしても同じものが残り、pickleでシリアライズでき、float | MISSING のような注釈にも使えます。古いバージョンでは、object() を使う方法がそのまま使えます。
関数でよくあるミス
- return を忘れる — 関数は
Noneを返します。 - かっこを忘れる —
print(greet)は、呼び出し結果ではなく関数オブジェクトを表示します。 - 変更可能なデフォルト値 — すべての呼び出しで同じリストが共有されます。
- 引数の数の間違い —
TypeError: greet() missing 1 required positional argument: 'name'。 - 組み込みの名前と衝突する —
listやsumという名前の関数は、組み込みを隠してしまいます。
まとめ
Pythonの関数は def で定義し、return で結果を返し、引数を順番どおりにも名前を指定しても受け取れます。*args と **kwargs で柔軟性を、/ と * で呼び出しの厳密さを、lambda で一度だけ使う関数の簡潔さを得られます。型注釈は、mypyと組み合わせれば実行前にエラーを見つけてくれます。講座では、この内容を実践で扱います。家計簿ツールを型注釈付きの関数に分け、mypyで検査します。
よくある質問
引数(パラメータ)と引数(アーギュメント)はどう違いますか?
パラメータは関数定義の中にある名前、引数は呼び出し時に渡す具体的な値です。def greet(name) の name はパラメータ、greet("花子") の "花子" は引数です。
関数を定義する前に呼び出せますか?
モジュールの最上位では無理です。def の行が先に実行されていないと NameError になります。ただし、別の関数の中から、その関数の定義より後に書かれた関数を呼び出すことは、その呼び出しが定義の後に行われるなら可能です。
関数は複数の値を返せますか?
形式的には1つの値ですが、その値をタプルにすれば複数の値を返せます。return a, b はタプルを返し、x, y = f() のように分解して受け取れます。
型注釈は必ず書く必要がありますか?
いいえ。注釈がなくても Python は動きます。ただし、他の人が読むコードでは、注釈があると保守が格段に楽になり、mypyやpyrightでエラーを早期に見つけられます。
lambdaの中でifは使えますか?
条件式なら使えます。例:lambda x: "偶数" if x % 2 == 0 else "奇数"。文、ループ、代入を lambda の中に書くことはできません。