閉じる

【Python】デフォルト引数の正しい設定方法とNGパターンを一気に理解する

Pythonの関数定義では、引数にデフォルト値を設定することで、呼び出し時の記述を省略したり、使い勝手を大きく向上させることができます。

一方で、仕様を正しく理解していないと、意図しないバグの原因にもなります。

本記事では、Pythonのデフォルト引数の基本から、NGパターン、正しい設計パターン、実務で役に立つ設計Tipsまでを、図解とサンプルコードを交えて体系的に解説します。

Pythonのデフォルト引数とは

デフォルト引数の基本構文と挙動

Pythonでは、関数定義の引数に= デフォルト値を記述することで、呼び出し時にその引数を省略できるようにできます。

最も単純な例を示します。

Python
def greet(name, message="こんにちは"):
    """name 宛てに挨拶メッセージを表示する関数"""
    print(f"{name}さん、{message}")

この関数は、次のように動作します。

Python
greet("太郎")          # message を省略 → "こんにちは" が使われる
greet("花子", "おはよう")  # message を明示的に指定

実行結果の例を示します。

実行結果
太郎さん、こんにちは
花子さん、おはよう

ここで重要なのは、デフォルト引数は「省略したときに自動で補われる既定値」であり、呼び出し側で明示的に指定した値があれば必ずそちらが優先されるという点です。

デフォルト引数の定義ルール

Pythonでは、デフォルト引数にはいくつかの構文ルールがあります。

  • デフォルト引数は、必須引数の後ろにまとめて書く必要があります。
  • 位置引数、キーワード引数の並び順にも制約があります。

代表的なOK・NGパターンを表にまとめます。

パターン記述例結果・コメント
OK: 必須 → デフォルトdef f(a, b=10): ...正しい構文
NG: デフォルト → 必須def f(a=10, b): ...構文エラー
OK: デフォルト+キーワード専用def f(a, *, b=10, c=20): ...Python3で推奨される書き方
OK: 可変長引数との組み合わせdef f(a, *args, flag=True): ...ただし挙動理解が必要(後述)

「必須引数 → デフォルト引数 → 可変長引数 → キーワード専用引数」という順序を意識すると整理しやすくなります。

デフォルト引数が便利な代表的な使い方

デフォルト引数は、「よく使う値」をあらかじめ埋め込んでおくことで、関数の呼び出しを簡潔にし、コードの読みやすさを高めます。

ログ出力のログレベルにデフォルトを設定

Python
def log(message, level="INFO"):
    """シンプルなログ出力関数"""
    print(f"[{level}] {message}")

log("処理を開始します")        # level を省略 → "INFO"
log("ファイルが見つかりません", level="WARNING")
実行結果
[INFO] 処理を開始します
[WARNING] ファイルが見つかりません

多くの場合はINFOレベルで十分なため、代表的な使い方に合わせたデフォルト値を設定することで、呼び出し側の負担が減ります。

ページネーションなどの「よくある数値」をデフォルト化

Python
def fetch_items(page=1, per_page=20):
    """page ページ目のデータを、per_page 件ずつ取得するイメージ関数"""
    print(f"{page=}, {per_page=}")

fetch_items()           # page=1, per_page=20
fetch_items(page=3)     # page=3, per_page=20
fetch_items(2, 50)      # page=2, per_page=50

ページネーションやリトライ回数、タイムアウト秒などは、プロジェクトで標準的に使う値をデフォルト値としておくと、関数を使う側の選択肢が減り、APIがシンプルになります。

デフォルト引数でやってはいけないNGパターン

ミュータブルなオブジェクト(listやdict)をデフォルト引数にする危険性

Pythonのデフォルト引数で最も有名かつ危険なNGパターンが、ミュータブル(可変)なオブジェクトをデフォルト値にすることです。

典型的な例はlistdictです。

Python
def add_item(item, items=[]):
    """items に item を追加して返す(バグを含むNG例)"""
    items.append(item)
    return items

print(add_item("apple"))
print(add_item("banana"))
print(add_item("orange"))

想像通りの挙動を期待すると、次のような結果を想像するかもしれません。

実行結果
["apple"]
["banana"]
["orange"]

しかし実際には、こうなります。

実行結果
['apple']
['apple', 'banana']
['apple', 'banana', 'orange']

理由は、デフォルト引数の[]は、関数定義時に1度だけ作成され、その後の呼び出しですべて同じリストが再利用されているからです。

これが、多くの初心者を悩ませるデフォルト引数の落とし穴です。

デフォルト引数が一度だけ評価される仕様によるバグ例

ミュータブル以外にも、「デフォルト引数は関数が定義された時点で1度だけ評価される」という仕様が、思わぬバグにつながることがあります。

現在時刻をデフォルト値にしてしまう例

Python
import datetime

def log_created(message, created_at=datetime.datetime.now()):
    """作成時刻をログに出力する(バグを含むNG例)"""
    print(f"{created_at}: {message}")

この関数を、時間をあけて複数回呼び出してみます。

Python
import time

log_created("1回目のログ")
time.sleep(2)  # 2秒待つ
log_created("2回目のログ")
time.sleep(2)
log_created("3回目のログ")

想定としては、異なる時刻が表示されるイメージですが、実際にはすべて同じ時刻が出力されてしまいます。

これは、datetime.datetime.now()が、関数定義時(モジュール読み込み時)に1回だけ評価され、その結果がずっと使われるからです。

このように、実行のたびに変わることを期待する値(現在時刻、乱数、外部リソースの状態など)をデフォルト引数に直接書くのはNGです。

デフォルト引数と可変長引数(*args, **kwargs)の組み合わせで起きる誤用

可変長引数*args**kwargsとデフォルト引数を組み合わせると、引数がどこに入っているかが分かりにくくなりがちです。

Python
def process(*items, limit=10):
    """items を最大 limit 件まで処理する関数"""
    print(f"{items=}, {limit=}")

process(1, 2, 3)
process(1, 2, 3, limit=5)
実行結果
items=(1, 2, 3), limit=10
items=(1, 2, 3), limit=5

この例は一見分かりやすいですが、次のように書くと、挙動が読み取りにくくなります。

Python
def process(limit=10, *items):
    """NGに近い例: 読み手にとって分かりづらい"""
    print(f"{items=}, {limit=}")

process(1, 2, 3)

この場合、呼び出しprocess(1, 2, 3)はどう解釈されるでしょうか。

limit=1items=(2, 3)となります。

「最初の引数がlimitなのかitemsなのか」が読み手に直感的に伝わらないため、設計として好ましくありません。

また、**kwargsとデフォルト引数を混在させるときも注意が必要です。

Python
def log_with_options(message, level="INFO", **kwargs):
    """kwargs に level を入れてしまう誤用例"""
    if "level" in kwargs:
        # kwargs 経由の level があると、引数 level と二重になる
        print("level が二重に指定されています")
    print(f"[{level}] {message} {kwargs=}")

levelをキーワード限定引数にしたいのか、**kwargsで自由に受けたいのかが曖昧になり、インターフェースとして分かりにくくなるケースです。

型ヒントとデフォルト引数を混在させたときのよくあるミス

Python 3.5以降では型ヒントが導入され、デフォルト引数と組み合わせて使うことが一般的になりました。

このときのよくあるミスとして、デフォルト値と型ヒントが矛盾しているケースがあります。

型とデフォルト値が矛盾している例

Python
def send_message(user_id: int = None):
    """型ヒントとデフォルト値が矛盾しているNG例"""
    ...

この定義は、user_idの型をintと宣言しているにもかかわらず、デフォルト値としてNoneを与えています。

静的型チェッカー(mypyなど)ではエラーになります。

正しくは、Noneを許容する型にする必要があります。

Python
from typing import Optional

def send_message(user_id: Optional[int] = None):
    """None も OK であることを型にも反映した例"""
    ...

Python 3.10以降では、int | Noneという記法も使えます。

Python
def send_message(user_id: int | None = None):
    ...

「デフォルト値が実際に取りうる値の1つである」ことを型ヒントにも反映させるのがポイントです。

デフォルト引数の正しい設定パターン

Noneを使ってミュータブルを初期化するPython定番イディオム

ミュータブルなオブジェクトをデフォルト引数に直接書くのはNGでした。

そこでPythonでは、「デフォルト値としてNoneを置き、関数の中で必要に応じて実際のオブジェクトを生成する」というイディオムが広く使われています。

Python
from typing import Optional, List

def add_item(item: str, items: Optional[List[str]] = None) -> list[str]:
    """items が None なら新しいリストを作って item を追加する"""
    if items is None:
        items = []  # 呼び出しごとに新しいリストを作る
    items.append(item)
    return items

print(add_item("apple"))
print(add_item("banana"))
print(add_item("orange", items=["grape"]))
実行結果
['apple']
['banana']
['grape', 'orange']

関数を呼び出すたびに新しいリストが必要な場合は、必ずこのパターンを使うと覚えておくと安全です。

辞書やセットなど、他のミュータブルなオブジェクトでも同じ考え方を適用できます。

不変(イミュータブル)な値をデフォルト引数に使うときのポイント

デフォルト引数として安全に使えるのは、イミュータブル(不変)なオブジェクトです。

代表的なものは次の通りです。

種類デフォルト引数としての扱い
数値0, 1.0問題なく使用可能
真偽値True, False問題なく使用可能
文字列"", "default"問題なく使用可能
タプル(), (1, 2)中身も変更しないなら安全
NoneNone「未指定」を表す特別な値としてよく使われる
Python
def connect(host="localhost", port=5432, use_ssl=False):
    """データベース接続のためのパラメータ例"""
    print(f"host={host}, port={port}, use_ssl={use_ssl}")

connect()
connect(port=3306)
connect("db.example.com", use_ssl=True)

イミュータブルなオブジェクトであれば、デフォルト値が共有されても中身が変更されることはないため、安心して使うことができます。

呼び出し側に意味が伝わるデフォルト値の設計

デフォルト引数は、単に「省略できるようにする」だけでなく、「呼び出し側に関数の意図や推奨設定を伝える」役割も担います。

Python
def download(url, timeout=5.0, retries=3):
    """
    URL からデータをダウンロードする関数

    timeout: 接続タイムアウト秒数(通常は 5 秒程度が妥当)
    retries: 一時的なエラー時のリトライ回数(3 回程度)
    """
    ...

ここで、timeout=0.1retries=20のような、極端な値をデフォルトにするとどうでしょうか。

使い手は、「なぜそんな値がデフォルトなのか」「どう調整すればよいのか」が分かりにくくなります

逆に、「現実的な一般ケースで最もよく使うであろう設定」をデフォルト値にすることで、APIを初めて使う人でも、大きくハマることなく試せるようになります。

オプション引数と必須引数を見分けやすくする引数順序の決め方

関数の引数が多くなると、「どれが必須なのか」「どれがオプションなのか」が分かりにくくなりがちです。

読みやすさのためには、次のような順序を意識すると良いです。

  1. 必須の位置引数
  2. デフォルト値を持つ位置引数(オプション)
  3. より前の可変長位置引数args(必要な場合のみ)
  4. *以降のキーワード専用引数(多くはデフォルト値付き)
  5. 可変長キーワード引数**kwargs(必要な場合のみ)
Python
def fetch_resource(
    user_id: int,
    resource: str,
    page: int = 1,
    per_page: int = 20,
    *,
    cache: bool = True,
    timeout: float = 5.0,
):
    """API からリソースを取得する関数の例"""
    ...

このように並べると、「user_id と resource は必ず必要」「page, per_page は頻度高めのオプション」「cache, timeout は高度なオプション」という意図が自然と伝わるようになります。

デフォルト引数とキーワード引数で可読性を高める書き方

デフォルト引数とキーワード引数を組み合わせると、「何を指定しているのか」が非常に読みやすくなります

Python
def download(
    url: str,
    page: int = 1,
    per_page: int = 100,
    cache: bool = True,
    timeout: float = 5.0,
):
    ...

この関数を、すべて位置引数で呼び出すと次のようになります。

Python
download("https://example.com", 2, 50, False, 10.0)

慣れていないと、250が何を意味するのか、一瞬考える必要があります。

これをキーワード引数で書き直すと、次のようになります。

Python
download(
    "https://example.com",
    page=2,
    per_page=50,
    cache=False,
    timeout=10.0,
)

引数名がそのまま「ドキュメント」として機能するため、数か月後に読んでも意図をすぐに理解できます。

特に、デフォルト値を上書きする引数はキーワード引数で明示する、というスタイルはおすすめです。

実践で使えるデフォルト引数の設計Tips

API設計でのデフォルト引数の考え方と互換性の守り方

外部公開APIやライブラリの関数を設計する際、デフォルト引数は互換性(後方互換)に直結する重要な要素です。

APIをバージョンアップする際の基本方針は次の通りです。

  • 既存の引数のデフォルト値は、むやみに変更しない
  • 引数を追加する場合は、末尾にデフォルト値付きで追加し、既存の呼び出しコードに影響を与えない。
  • 挙動を変えたい場合は、新しい引数で制御するか、新しい関数名・エンドポイントを用意する。
Python
# v1
def search(query: str, limit: int = 10):
    """
    検索API v1
    - query: 検索クエリ
    - limit: 最大件数(デフォルト 10)
    """
    ...

# v2: fuzzy オプションを追加
def search(
    query: str,
    limit: int = 10,
    *,
    fuzzy: bool = False,  # 新機能はキーワード専用 + デフォルト値付き
):
    """
    検索API v2
    - fuzzy: あいまい検索を有効にするかどうか(デフォルト False)
    """
    ...

このように設計すれば、search("python")という既存の呼び出しは、そのまま同じ挙動を維持できます。

「古いコードが壊れないか」を常に意識しながらデフォルト引数を設計することが大切です。

ライブラリ開発でのデフォルト値の変更と非推奨(deprecated)戦略

ライブラリ開発では、既に公開してしまったデフォルト引数を後から変えたくなることがあります。

しかし、即座に変えてしまうと、利用者のコードの挙動が変わり、大きな問題になります。

そこで一般的には、次のような段階的な戦略を取ります。

  1. まずはデフォルト値は変えずに、将来の変更予定をドキュメントと警告で案内する。
  2. 利用者が明示的に旧挙動を選べるように、引数を追加または説明を強化する。
  3. メジャーバージョンアップのタイミングで、デフォルト値を変更する。
Python
def parse_config(path: str, strict: bool | None = None):
    """
    設定ファイルを読み込む関数

    strict:
        - None の場合: 将来の挙動変更に備えて警告を出しつつ、現状は False と同じ扱い
        - True の場合: 厳密チェックを有効化
        - False の場合: 従来通りゆるいチェック
    """
    if strict is None:
        import warnings
        warnings.warn(
            "strict のデフォルト挙動は将来変更されます。"
            "明示的に True または False を指定してください。",
            DeprecationWarning,
            stacklevel=2,
        )
        strict = False  # 既存挙動
    ...

このようにNoneを特別な値として扱い、非推奨を案内しつつ将来のデフォルト変更に備える」パターンは、ライブラリ開発でよく使われます。

テストしやすいデフォルト引数とテストコードの書き方

テストの観点からも、デフォルト引数の設計は重要です。

「引数を省略したときの挙動」をテストしやすくすることで、将来の変更による破壊的影響を早期に検知できます。

以下は、先ほどのdownload関数をpytestでテストするイメージです。

Python
# app.py
from dataclasses import dataclass

@dataclass
class DownloadOptions:
    url: str
    page: int = 1
    per_page: int = 100
    cache: bool = True
    timeout: float = 5.0

def build_options(
    url: str,
    page: int = 1,
    per_page: int = 100,
    cache: bool = True,
    timeout: float = 5.0,
) -> DownloadOptions:
    """実際にダウンロードする代わりに、オプション構造体を返す"""
    return DownloadOptions(url, page, per_page, cache, timeout)
Python
# test_app.py
from app import build_options

def test_build_options_default():
    """引数省略時にデフォルト値が正しく設定されることをテスト"""
    opt = build_options("https://example.com")
    assert opt.page == 1
    assert opt.per_page == 100
    assert opt.cache is True
    assert opt.timeout == 5.0

def test_build_options_override():
    """キーワード引数でデフォルト値を上書きできることをテスト"""
    opt = build_options(
        "https://example.com",
        page=2,
        per_page=50,
        cache=False,
        timeout=10.0,
    )
    assert opt.page == 2
    assert opt.per_page == 50
    assert opt.cache is False
    assert opt.timeout == 10.0

このように、「デフォルト引数をそのままロジックに埋め込まず、オプションをまとめたオブジェクトに変換して扱う」と、テストも書きやすくなります。

また、将来デフォルト値を変更するときも、テストが壊れることで影響範囲がすぐに分かります。

まとめ

Pythonのデフォルト引数は、正しく使えばAPIの使いやすさとコードの可読性を大きく高められる一方で、誤った使い方をするとバグの温床にもなりやすい機能です。

本記事では、ミュータブルを直接デフォルトにしない、Noneイディオムを活用する、イミュータブルな値を中心に設計する、引数の順序とキーワード引数で意図を伝えるといった実践的なパターンを紹介しました。

日々のコーディングやAPI設計、ライブラリ開発の場面で、本記事の考え方を意識しながらデフォルト引数を設計してみてください。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!