関数を定義する際、引数にあらかじめ値を設定しておくことで、呼び出し時にその引数を省略可能にすることができます。これを「デフォルト引数」と呼びます。
非常に便利な機能ですが、リストや辞書などの「変更可能(ミュータブル)なオブジェクト」をデフォルト値に設定する際には、Python特有の挙動に注意が必要です。
この記事では、デフォルト引数の基本的な使い方と、よくある「落とし穴」の回避策について解説します。
デフォルト引数の基本的な使い方
関数定義の際、引数名=値 の形式で記述することで、デフォルト値を設定できます。
例として、商品の価格と消費税率を受け取り、税込価格を計算する関数を作成します。税率は頻繁に変わるものではないため、デフォルトで 0.1 (10%) を設定します。
def calculate_price_with_tax(price, tax_rate=0.1):
"""
税抜価格と税率から税込価格を計算する
tax_rateを省略すると 0.1 が適用される
"""
return int(price * (1 + tax_rate))
# 1. 引数を省略した場合(デフォルト値 0.1 が使われる)
print(f"1000円の税込: {calculate_price_with_tax(1000)}")
# 2. 引数を指定した場合(指定した値 0.08 が使われる)
print(f"軽減税率での税込: {calculate_price_with_tax(1000, 0.08)}")
実行結果:
1000円の税込: 1100
軽減税率での税込: 1080
定義時の注意点:引数の順序
デフォルト引数を使用する場合、デフォルト値を持たない引数(必須引数)を先に記述し、デフォルト値を持つ引数は後ろに記述する必要があります。
この順序を守らないと、SyntaxError が発生します。
# 文法エラーになる例
# 必須引数 'y' が、デフォルト引数 'x' の後ろにあるためエラー
# def invalid_func(x=1, y):
# print(x, y)
# SyntaxError: non-default argument follows default argument
落とし穴:ミュータブル(変更可能)なオブジェクトをデフォルト値にする
ここが最も重要なポイントです。リスト(list)や辞書(dict)などのミュータブルなオブジェクトをデフォルト引数として設定すると、予期せぬ挙動を引き起こすことがあります。
問題のあるコード例
タスクを追加する関数を作成し、デフォルトのタスクリストとして空のリスト [] を設定してみます。
def add_task(task_name, task_list=[]):
"""
タスクリストにタスクを追加して返す関数
(デフォルト値にリストを指定している悪い例)
"""
task_list.append(task_name)
return task_list
# 1回目の呼び出し
print(f"1回目: {add_task('メール確認')}")
# 2回目の呼び出し(新しいリストが作られることを期待しているが...)
print(f"2回目: {add_task('会議')}")
# 3回目の呼び出し
print(f"3回目: {add_task('報告書作成')}")
実行結果:
1回目: ['メール確認']
2回目: ['メール確認', '会議']
3回目: ['メール確認', '会議', '報告書作成']
なぜこうなるのか
Pythonでは、関数のデフォルト値は**「関数が定義された時点」で一度だけ生成(評価)されます**。
そのため、task_list=[] と記述すると、そのリストオブジェクトがメモリ上に一つだけ作成され、関数が呼び出されるたびに同じリストオブジェクトが使い回されます。その結果、前の呼び出しで追加された内容が残ってしまいます。
安全な実装方法:Noneを利用する
この問題を回避するための定石(イディオム)は、デフォルト値を None に設定し、関数の内部で「None だったら空リストを作成する」という処理を行うことです。
def add_task_safe(task_name, task_list=None):
"""
安全な実装:デフォルト値を None にする
"""
# 引数が省略された場合(Noneの場合)のみ、新しいリストを作成する
if task_list is None:
task_list = []
task_list.append(task_name)
return task_list
# 1回目の呼び出し
print(f"1回目: {add_task_safe('メール確認')}")
# 2回目の呼び出し
print(f"2回目: {add_task_safe('会議')}")
# 3回目の呼び出し
print(f"3回目: {add_task_safe('報告書作成')}")
実行結果:
1回目: ['メール確認']
2回目: ['会議']
3回目: ['報告書作成']
この書き方であれば、引数を省略して関数を呼び出すたびに task_list = [] が実行され、毎回新しいリストが生成されます。
まとめ
- デフォルト引数を使うと、引数を省略可能にできます。
- デフォルト引数は、必須引数よりも後ろに記述する必要があります。
- リストや辞書などのミュータブルなオブジェクトをデフォルト値に設定してはいけません(一度生成されたオブジェクトが使い回されるため)。
- ミュータブルな型を扱いたい場合は、デフォルト値を
Noneにし、関数内でif arg is None: arg = []のように初期化します。
