20. 関数パラメータと引数
第19章では、基本的なパラメータを使って関数を定義し、呼び出す方法を学びました。ここでは、Python の柔軟なパラメータと引数の仕組みを深く掘り下げていきます。これらのメカニズムを理解することで、強力で使いやすい関数を書けるようになります。
20.1) 位置引数とキーワード引数
関数を呼び出すとき、引数を渡す方法は大きく2つあります。位置で渡すか、名前(キーワード)で渡すかです。
20.1.1) 位置引数
位置引数(positional arguments) は、引数の順序に基づいてパラメータに対応付けられます。最初の引数は最初のパラメータに、2番目の引数は2番目のパラメータに、という具合です。
def calculate_discount(price, discount_percent):
"""割引を適用した後の最終価格を計算します。"""
discount_amount = price * (discount_percent / 100)
final_price = price - discount_amount
return final_price
# 位置で引数を渡す
result = calculate_discount(100, 20)
print(result)出力:
80.0この例では、関数呼び出し時の位置だけに基づいて 100 が price に、20 が discount_percent に代入されます。
位置引数では順序が決定的に重要です:
# 例: 100ドルの商品を20%割引で計算したい
# 正しい順序: 先にprice、次にdiscount
print(calculate_discount(100, 20))
# 間違った順序: 先にdiscount、次にprice
print(calculate_discount(20, 100))出力:
80.0
-16.0引数を入れ替えても、Python はあなたが間違えたことを知りません。単に順番通りに代入するだけです。その結果、数式としては成り立っていても、論理的には誤った結果(負の価格!)になります。
20.1.2) キーワード引数
キーワード引数(keyword arguments) は、パラメータ名に続けて等号と値を指定することで、どのパラメータにどの値を渡すかを明示します。これにより、コードの可読性が上がり、順序のミスを防げます。
def create_user_profile(username, email, age):
"""指定された情報でユーザープロファイルを作成します。"""
profile = f"User: {username}\nEmail: {email}\nAge: {age}"
return profile
# キーワード引数を使用
profile = create_user_profile(username="alice_smith", email="alice@example.com", age=28)
print(profile)出力:
User: alice_smith
Email: alice@example.com
Age: 28キーワード引数では順序は重要ではありません:
# 結果は同じで、順序だけ違う
profile1 = create_user_profile(username="bob", email="bob@example.com", age=35)
profile2 = create_user_profile(age=35, username="bob", email="bob@example.com")
profile3 = create_user_profile(email="bob@example.com", age=35, username="bob")
# 3つとも同一の結果になる
print(profile1 == profile2 == profile3)出力:
Trueこの柔軟性は、関数に多くのパラメータがある場合に特に価値があります。どの値がどのパラメータに対応するのかを簡単に確認できるためです。
20.1.3) 位置引数とキーワード引数の混在
1回の関数呼び出しの中で両方のスタイルを組み合わせられますが、重要なルールがあります: 位置引数はキーワード引数より前に書かなければなりません。
def format_address(street, city, state, zip_code):
"""郵送先住所を整形します。"""
return f"{street}\n{city}, {state} {zip_code}"
# 有効: 先に位置引数、その後にキーワード引数
address = format_address("123 Main St", "Springfield", state="IL", zip_code="62701")
print(address)出力:
123 Main St
Springfield, IL 62701ここでは "123 Main St" と "Springfield" が位置引数(street と city に代入)で、state と zip_code は名前で指定されています。
キーワード引数の後に位置引数を置こうとするとエラーになります:
# 無効: キーワード引数の後に位置引数
# address = format_address(street="123 Main St", "Springfield", state="IL", zip_code="62701")
# SyntaxError: positional argument follows keyword argumentPython がこのルールを強制するのは、キーワード引数を使い始めた後に名前のない引数が来ると、それがどの位置パラメータを埋めるべきかが曖昧になるからです。
20.1.4) それぞれのスタイルを使うタイミング
位置引数を使うのは次のときです:
- 関数のパラメータが少ない(通常 1〜3 個)
- パラメータの順序が明白で直感的
- 関数がよく使われ、順序がよく知られている
# 明快で簡潔
print(len("hello"))
result = max(10, 20, 5)キーワード引数を使うのは次のときです:
- 関数のパラメータが多い
- パラメータの意味がすぐには明確でない
- デフォルト値があるパラメータをいくつかスキップしたい(次で扱います)
- コードを自己記述的にしたい
# 明確で明示的
user = create_user_profile(username="charlie", email="charlie@example.com", age=42)20.2) デフォルトパラメータ値
関数は、パラメータに デフォルト値(default values) を指定できます。呼び出し側がデフォルトを持つパラメータに引数を渡さなかった場合、Python は代わりにデフォルト値を使用します。
20.2.1) デフォルト付きパラメータの定義
デフォルト値は、関数定義で代入演算子を使って指定します:
def greet_user(name, greeting="Hello"):
"""カスタマイズ可能な挨拶でユーザーに挨拶します。"""
return f"{greeting}, {name}!"
# デフォルトの挨拶を使用
print(greet_user("Alice"))
# カスタムの挨拶を渡す
print(greet_user("Bob", "Good morning"))
print(greet_user("Carol", greeting="Hi"))出力:
Hello, Alice!
Good morning, Bob!
Hi, Carol!パラメータ greeting には "Hello" というデフォルト値があります。greet_user("Alice") を呼ぶと、Python はこのデフォルトを使います。2つ目の引数を指定すると、デフォルトが上書きされます。
20.2.2) デフォルト付きパラメータは必須パラメータの後に置く必要がある
Python では、デフォルト値を持つパラメータは、デフォルトを持たないすべてのパラメータの後に置く必要があります。このルールは、どの引数がどのパラメータに対応するかの曖昧さを防ぎます。
# 正しい: 必須パラメータを先に、その後にデフォルト
def create_product(name, price, category="General", in_stock=True):
"""商品レコードを作成します。"""
return {
"name": name,
"price": price,
"category": category,
"in_stock": in_stock
}
product = create_product("Laptop", 999.99)
print(product)出力:
{'name': 'Laptop', 'price': 999.99, 'category': 'General', 'in_stock': True}必須パラメータをデフォルト付きパラメータの後に置こうとすると、構文エラーになります:
# 無効: デフォルトパラメータの後に必須パラメータ
# def invalid_function(name="Unknown", age):
# return f"{name} is {age} years old"
# SyntaxError: non-default argument follows default argumentこれは理にかなっています。name にデフォルトがあるのに age にはない場合、invalid_function(25) が name=25 で age が未指定なのか、それとも age=25 で name はデフォルトを使うのか、Python には判断できません。このルールはその曖昧さを取り除きます。
20.2.3) デフォルトパラメータの実用的な使い方
デフォルトパラメータは、特定の引数がほとんど変わらない関数に最適です:
def calculate_shipping(weight, distance, express=False):
"""重量と距離に基づいて配送料を計算します。"""
base_rate = 0.50 * weight + 0.10 * distance
if express:
base_rate *= 2 # 速達配送は料金が2倍
return round(base_rate, 2)
# ほとんどの配送は通常便
standard_cost = calculate_shipping(5, 100)
print(f"Standard: ${standard_cost}")
# ときどき速達が必要なこともある
express_cost = calculate_shipping(5, 100, express=True)
print(f"Express: ${express_cost}")出力:
Standard: $12.5
Express: $25.0この設計により、一般的なケース(通常便)は簡単に呼び出せる一方で、必要なときには一般的でないケース(速達)もサポートできます。
20.2.4) 複数のデフォルトと選択的な上書き
関数に複数のデフォルト付きパラメータがある場合、キーワード引数を使って任意の組み合わせを上書きできます:
def format_currency(amount, currency="USD", show_symbol=True, decimal_places=2):
"""数値を通貨として整形します。"""
symbols = {"USD": "$", "EUR": "€", "GBP": "£", "JPY": "¥"}
formatted = f"{amount:.{decimal_places}f}"
if show_symbol and currency in symbols:
formatted = f"{symbols[currency]}{formatted}"
return formatted
# すべてのデフォルトを使用
print(format_currency(42.5))
# 通貨だけ上書き
print(format_currency(42.5, currency="EUR"))
# 複数のデフォルトを上書き
print(format_currency(42.5, currency="JPY", decimal_places=0))出力:
$42.50
€42.50
¥42この柔軟性により、呼び出し側は必要な部分だけをカスタマイズでき、関数呼び出しも簡潔なままにできます。
20.3) *args による可変長引数リスト
事前に引数の数がいくつになるか分からないまま、任意の数の引数を受け取れる関数にしたいことがあります。Python では、そのために *args を提供しています。
20.3.1) *args の理解
パラメータリストでの *args 構文は、追加の位置引数をすべてタプルに集めます。args という名前は慣習("arguments" の略)ですが、アスタリスクの後には任意の有効なパラメータ名を使えます。
def calculate_total(*numbers):
"""任意の数の値の合計を計算します。"""
total = 0
for num in numbers:
total += num
return total
# 任意の数の引数で動作する
print(calculate_total(10))
print(calculate_total(10, 20))
print(calculate_total(10, 20, 30, 40))
print(calculate_total())出力:
10
30
100
0関数内では、numbers は関数に渡されたすべての位置引数を含むタプルです。引数が渡されなかった場合は空のタプルになります。
20.3.2) 通常のパラメータと *args の組み合わせ
*args の前に通常のパラメータを置けます。通常のパラメータが最初のいくつかの引数を消費し、*args が残りを集めます:
def create_team(team_name, *members):
"""名前と任意の数のメンバーでチームを作成します。"""
member_list = ", ".join(members)
return f"Team {team_name}: {member_list}"
# 最初の引数はteam_nameに、残りはmembersに入る
print(create_team("Alpha", "Alice", "Bob"))
print(create_team("Beta", "Carol"))
print(create_team("Gamma", "Dave", "Eve", "Frank", "Grace"))出力:
Team Alpha: Alice, Bob
Team Beta: Carol
Team Gamma: Dave, Eve, Frank, Grace最初の引数("Alpha"、"Beta"、または "Gamma")は team_name に代入され、残りの引数はすべて members タプルにまとめられます。
20.4) キーワード専用パラメータと **kwargs パラメータ
Python には、引数を扱うための追加メカニズムが2つあります。キーワード専用パラメータと、任意のキーワード引数を集める **kwargs です。
20.4.1) キーワード専用パラメータ
キーワード専用パラメータ(keyword-only parameters) は、キーワード引数で指定しなければならず、位置引数として渡すことはできません。パラメータリストで * の後、または *args の後に置くことで作れます。
def create_account(username, *, email, age):
"""アカウントを作成します。email と age は名前で指定する必要があります。"""
return {
"username": username,
"email": email,
"age": age
}
# 正しい: email と age をキーワードで指定
account = create_account("alice", email="alice@example.com", age=28)
print(account)
# 無効: email と age を位置で渡そうとする
# account = create_account("bob", "bob@example.com", 30)
# TypeError: create_account() takes 1 positional argument but 3 were given出力:
{'username': 'alice', 'email': 'alice@example.com', 'age': 28}パラメータリスト中の * は区切りとして機能します。これより後はすべてキーワード引数として渡す必要があります。これは、特定のパラメータについて呼び出し側に明示を強制し、コードを読みやすくしてミスを減らしたいときに役立ちます。
通常のパラメータ、*args、キーワード専用パラメータを組み合わせることもできます:
def log_event(event_type, *details, severity="INFO", timestamp=None):
"""任意の詳細とメタデータを付けてイベントをログに記録します。"""
# datetime モジュールは第39章で詳しく学びますが、
# いまは、これらの行が現在時刻を取得し、
# タイムスタンプ文字列として整形することだけ知ってください
from datetime import datetime
if timestamp is None:
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
details_str = " | ".join(details)
return f"[{timestamp}] {severity}: {event_type} - {details_str}"
# event_type は位置引数、details は *details で収集され、
# severity と timestamp はキーワード専用
print(log_event("Login", "User: alice", "IP: 192.168.1.1"))
print(log_event("Error", "Database connection failed", severity="ERROR"))出力(タイムスタンプはコードを実行した時刻によって変わります):
[2025-12-18 19:29:16] INFO: Login - User: alice | IP: 192.168.1.1
[2025-12-18 19:29:16] ERROR: Error - Database connection failed20.4.2) **kwargs の理解
**kwargs 構文は、追加のキーワード引数をすべて辞書に集めます。args と同様に、kwargs という名前は慣習("keyword arguments" の略)ですが、二重アスタリスクの後には任意の有効な名前を使えます。
def create_product(**attributes):
"""任意の数の属性を持つ商品を作成します。"""
product = {}
for key, value in attributes.items():
product[key] = value
return product
# 好きなキーワード引数を渡せる
laptop = create_product(name="Laptop", price=999.99, brand="TechCorp", in_stock=True)
print(laptop)
phone = create_product(name="Phone", price=699.99, color="Black")
print(phone)出力:
{'name': 'Laptop', 'price': 999.99, 'brand': 'TechCorp', 'in_stock': True}
{'name': 'Phone', 'price': 699.99, 'color': 'Black'}関数内では、attributes はキーがパラメータ名、値が渡された引数になっている辞書です。
20.4.3) 通常パラメータ、*args、**kwargs の組み合わせ
これらの仕組みはすべて一緒に使えますが、特定の順序で書く必要があります:
- 通常の位置パラメータ
*args(あれば)- キーワード専用パラメータ(あれば)
**kwargs(あれば)
def complex_function(required, *args, keyword_only, **kwargs):
"""すべてのパラメータタイプを同時に使う例を示します。"""
print(f"Required: {required}")
print(f"Args: {args}")
print(f"Keyword-only: {keyword_only}")
print(f"Kwargs: {kwargs}")
complex_function(
"value1", # required
"value2", "value3", # args
keyword_only="kw", # keyword_only
extra1="e1", # kwargs
extra2="e2" # kwargs
)出力:
Required: value1
Args: ('value2', 'value3')
Keyword-only: kw
Kwargs: {'extra1': 'e1', 'extra2': 'e2'}この柔軟性は強力ですが、慎重に使うべきです。ほとんどの関数で、これらすべてが必要になるわけではありません。
20.4.4) 実用例: 設定用関数
**kwargs の一般的な用途は、設定オプションを受け取れる関数を作ることです:
def connect_to_database(host, port, **options):
"""柔軟な設定オプションでデータベースに接続します。"""
connection_string = f"Connecting to {host}:{port}"
# 追加オプションを処理する
if options.get("ssl"):
connection_string += " with SSL"
if options.get("timeout"):
connection_string += f" (timeout: {options['timeout']}s)"
if options.get("pool_size"):
connection_string += f" (pool size: {options['pool_size']})"
return connection_string
# 基本の接続
print(connect_to_database("localhost", 5432))
# SSL あり
print(connect_to_database("db.example.com", 5432, ssl=True))
# 複数オプションあり
print(connect_to_database("db.example.com", 5432, ssl=True, timeout=30, pool_size=10))出力:
Connecting to localhost:5432
Connecting to db.example.com:5432 with SSL
Connecting to db.example.com:5432 with SSL (timeout: 30s) (pool size: 10)このパターンにより、パラメータリストですべてを明示的に定義しなくても、任意の数のオプション設定パラメータを受け取れるようになります。
20.5) 関数呼び出し時の引数アンパック
*args と **kwargs が関数定義時に引数を収集するのと同様に、関数を呼び出すときには * と ** を使ってコレクションを アンパック(unpack) できます。
20.5.1) * によるシーケンスのアンパック
* 演算子は、シーケンス(リスト、タプルなど)を個別の位置引数にアンパックします:
def calculate_rectangle_area(width, height):
"""長方形の面積を計算します。"""
return width * height
# 引数を個別に渡す代わりに
dimensions = [5, 10]
area = calculate_rectangle_area(dimensions[0], dimensions[1])
print(area)
# リストを直接アンパックする
area = calculate_rectangle_area(*dimensions)
print(area)出力:
50
50*dimensions と書くと、Python はリスト [5, 10] を2つの別々の引数にアンパックします。これは calculate_rectangle_area(5, 10) と書いたのと同じです。
これは任意のイテラブル(iterable)で動作します:
def format_name(first, middle, last):
"""フルネームを整形します。"""
return f"{first} {middle} {last}"
# タプルをアンパック
name_tuple = ("John", "Q", "Public")
print(format_name(*name_tuple))
# リストをアンパック
name_list = ["Jane", "M", "Doe"]
print(format_name(*name_list))
# 文字列をアンパックすることもできる(各文字が引数になる)
# ただし、関数が期待する引数の数と一致する場合に限る
def show_first_three(a, b, c):
return f"{a}, {b}, {c}"
print(show_first_three(*"ABC"))出力:
John Q Public
Jane M Doe
A, B, C20.5.2) ** による辞書のアンパック
** 演算子は、辞書をキーワード引数にアンパックします:
def create_user(username, email, age):
"""ユーザープロファイルを作成します。"""
return f"User: {username}, Email: {email}, Age: {age}"
# キーがパラメータ名と一致している辞書
user_data = {
"username": "alice",
"email": "alice@example.com",
"age": 28
}
# 辞書をアンパック
profile = create_user(**user_data)
print(profile)出力:
User: alice, Email: alice@example.com, Age: 28**user_data と書くと、Python は辞書をキーワード引数にアンパックします。これは次と等価です:
create_user(username="alice", email="alice@example.com", age=28)辞書のキーは関数のパラメータ名と一致している必要があり、一致しない場合はエラーになります:
# 無効: 辞書のキーがパラメータ名と一致していない
invalid_data = {"name": "bob", "email": "bob@example.com", "age": 30}
# profile = create_user(**invalid_data)
# TypeError: create_user() got an unexpected keyword argument 'name'20.5.3) 通常の引数とアンパックの組み合わせ
アンパックした引数と通常の引数を混在させることができます:
def calculate_total(base_price, tax_rate, discount):
"""税と割引を反映した合計価格を計算します。"""
subtotal = base_price * (1 + tax_rate)
total = subtotal * (1 - discount)
return round(total, 2)
# 一部は通常の引数、一部はアンパック
pricing = [0.08, 0.10] # tax_rate と discount
total = calculate_total(100, *pricing)
print(total)出力:
97.21回の呼び出しで複数のコレクションをアンパックすることもできます:
def create_full_address(street, city, state, zip_code, country):
"""完全な住所を作成します。"""
return f"{street}, {city}, {state} {zip_code}, {country}"
street_address = ["123 Main St", "Springfield"]
location_details = ["IL", "62701", "USA"]
address = create_full_address(*street_address, *location_details)
print(address)出力:
123 Main St, Springfield, IL 62701, USA20.5.4) 実用例: 柔軟な関数呼び出し
アンパックは、外部ソースからのデータを扱うときに特に便利です:
def send_email(recipient, subject, body, cc=None, bcc=None):
"""オプションの CC と BCC を付けてメールを送信します。"""
message = f"To: {recipient}\nSubject: {subject}\n\n{body}"
if cc:
message += f"\nCC: {cc}"
if bcc:
message += f"\nBCC: {bcc}"
return message
# 設定ファイルやデータベースからのメールデータ
email_config = {
"recipient": "user@example.com",
"subject": "Welcome",
"body": "Thank you for signing up!",
"cc": "manager@example.com"
}
# 設定を直接アンパックする
result = send_email(**email_config)
print(result)出力:
To: user@example.com
Subject: Welcome
Thank you for signing up!
CC: manager@example.comこのパターンにより、関数の引数をデータ構造として扱って受け渡ししやすくなります。これは API を構築したり設定ファイルを処理したりするときによくあることです。
20.6) ミュータブルなデフォルト引数の罠(なぜリストのデフォルトが持続するのか)
Python の最も有名な落とし穴の1つに、ミュータブル(mutable)なオブジェクト(リストや辞書など)をデフォルトのパラメータ値として使うことがあります。この問題を理解することは、正しい関数を書くうえで非常に重要です。
20.6.1) 問題: 共有されるミュータブルなデフォルト
一見すると無害に見える次の関数を考えてみましょう:
def add_student(name, grades=[]):
"""成績とともに学生を追加します。"""
grades.append(name)
return grades
# 1回目の呼び出し
students1 = add_student("Alice")
print(students1)
# 2回目の呼び出し - 新しいリストを期待
students2 = add_student("Bob")
print(students2)
# 3回目の呼び出し
students3 = add_student("Carol")
print(students3)出力:
['Alice']
['Alice', 'Bob']
['Alice', 'Bob', 'Carol']この挙動は多くのプログラマーを驚かせます。grades 引数を指定せずに add_student() を呼ぶたびに、新しい リストではなく、同じ リストオブジェクトが使われます。リストが関数呼び出しをまたいで持続し、値が蓄積されていきます。
20.6.2) なぜ起きるのか: デフォルト値は一度だけ作られる
この挙動を理解する鍵は、デフォルト値が いつ 作られるかを知ることです。Python はデフォルトパラメータ値を、関数が呼ばれるたびではなく、関数が定義されたときに 一度だけ 評価します。
def demonstrate_default_creation():
"""デフォルトがいつ作られるかを示します。"""
print("Function defined!")
def use_default(value=demonstrate_default_creation()):
"""関数を呼び出すデフォルトを使います。"""
return value
# メッセージは、呼び出し時ではなく関数が定義されたときに表示される出力:
Function defined!Python が def use_default の行に遭遇すると、デフォルトパラメータ value=demonstrate_default_creation() を評価します。これにより demonstrate_default_creation() が呼ばれ、"Function defined!" が即座に表示されます。その後 use_default() を呼び出しても、デフォルトは再評価されないため、追加で何も表示されません。
Python が def add_student(name, grades=[]): に遭遇すると、空のリストオブジェクトを作成し、それを grades のデフォルト値として保存します。以後、grades 引数を指定しないすべての呼び出しは同じリストオブジェクトを使います。
オブジェクトの同一性を使った、より分かりやすいデモは次のとおりです:
def show_list_identity(items=[]):
"""同じリストオブジェクトが再利用されることを示します。"""
print(f"List ID: {id(items)}")
items.append("item")
return items
# 各呼び出しは同じリストオブジェクト(同じ ID)を使う
show_list_identity()
show_list_identity()
show_list_identity()出力:
List ID: 140234567890123
List ID: 140234567890123
List ID: 140234567890123正確な ID の値は環境によって変わりますが、3回とも同じ ID が表示されている点に注目してください。これは同じリストオブジェクトを使っている証拠です。id() 関数はメモリ上の各オブジェクトに一意の識別子を返します。ID が一致するなら同じオブジェクトです。
20.6.3) 正しいパターン: デフォルトに None を使う
標準的な解決策は、デフォルトに None を使い、関数内で新しいミュータブルオブジェクトを作ることです:
def add_student_correct(name, grades=None):
"""成績とともに学生を追加します(正しい版)。"""
if grades is None:
grades = [] # 毎回 NEW なリストを作成する
grades.append(name)
return grades
# これで各呼び出しはそれぞれ独立したリストになる
students1 = add_student_correct("Alice")
print(students1)
students2 = add_student_correct("Bob")
print(students2)
students3 = add_student_correct("Carol")
print(students3)出力:
['Alice']
['Bob']
['Carol']このパターンが機能するのは、None がイミュータブル(immutable)であり、grades が None のときに関数本体で毎回新しいリストが作られるからです。
20.6.4) 辞書でも同じ問題が起きる
この問題はリストだけでなく、すべてのミュータブル型に影響します:
# WRONG: 辞書のデフォルト
def create_config_wrong(key, value, config={}):
"""設定を作成します(バグのある版)。"""
config[key] = value
return config
config1 = create_config_wrong("theme", "dark")
print(config1)
config2 = create_config_wrong("language", "en")
print(config2)
print("---")
# CORRECT: デフォルトに None
def create_config_correct(key, value, config=None):
"""設定を作成します(正しい版)。"""
if config is None:
config = {}
config[key] = value
return config
config1 = create_config_correct("theme", "dark")
print(config1)
config2 = create_config_correct("language", "en")
print(config2)出力:
{'theme': 'dark'}
{'theme': 'dark', 'language': 'en'}
---
{'theme': 'dark'}
{'language': 'en'}20.6.5) まとめ: 黄金律
ミュータブルなオブジェクト(リスト、辞書、セット)をデフォルトのパラメータ値として使ってはいけません。 常に None を使い、関数内でミュータブルなオブジェクトを作成してください:
# ❌ WRONG
def function(items=[]):
pass
# ✅ CORRECT
def function(items=None):
if items is None:
items = []
# これで items を安全に使えるこのパターンにより、各関数呼び出しが独立したミュータブルオブジェクトを持てるようになり、呼び出し間でデータが漏れてしまうような不可解なバグを防げます。
この章では、Python の柔軟なパラメータと引数の仕組みを深く掘り下げました。位置引数とキーワード引数の使い方、デフォルト値の指定、*args と **kwargs による可変個の引数の扱い、関数呼び出し時のコレクションのアンパック、そしてミュータブルなデフォルト引数の罠を回避する方法を学びました。
これらの仕組みによって、柔軟で使いやすい関数インターフェースを設計するための強力な道具が手に入ります。より多くの関数を書くうちに、状況ごとにどのパラメータパターンが最適かという直感が身についていきます。重要なのは、柔軟性と明確さのバランスです。関数を「正しく呼びやすく」、そして「間違って呼びにくく」設計しましょう。