順番に読み進めながら学べます

関数の引数と参照渡し — ミュータブルだと引数が変更される

Python の関数の引数と参照渡しを図解で解説します。list と int で挙動が違う仕組み、.copy() で引数を守るパターンまで一通り押さえます。

関数を呼び出す際、引数がミュータブル型list / dict / set)かイミュータブル型int / str / tuple)かで挙動が異なります。この記事では、その違いと安全に書くための基本パターンを見ていきましょう。

イミュータブル型の引数 — 関数の中の変更は外に出ない

整数・浮動小数点・文字列・タプルはイミュータブル(変更不可能)です。これらを関数の引数として受け取り、中で += 10= 別の値 のように書き換えても、呼び出し元の変数は元の値のままです。

関数の中で += 10 をすると、新しい値が作られて関数内の引数名だけが付け替わるため、呼び出し元の名札は元の値を指したまま残る、というのが仕組みです。

イミュータブル型の引数
呼び出し元x = 5関数内x = 5x = 5(変わらない)x = 15(新しい値)渡すx += 10
def try_modify_number(value):
    value += 10
    print(f"関数内: {value}")

x = 5
try_modify_number(x)
print(f"関数外: {x}")

# 関数内: 15
# 関数外: 5   ← 元のまま

# 文字列・タプルでも同じ
def try_modify_text(text):
    text = text + " world"
    print(f"関数内: {text}")

message = "hello"
try_modify_text(message)
print(f"関数外: {message}")   # hello ← 元のまま

税込価格に変換する関数を作り、引数の int が関数の外に影響しないことを確認します。

def add_tax(price): を定義し、price = int(price * 1.1) としたあとに print(f"関数内: {price}") を表示してください(この関数は戻り値を返しません)。

base_price = 1000 とし、add_tax(base_price) を呼び出したあとに print(f"関数外: {base_price}") を表示してください。

関数の外の base_price が変わっていないことが確認できれば成功です。

(正しく実行できれば解説が表示されます)

Python エディタ

コードを実行してください

ミュータブル型の引数 — 関数の中の変更が外にも見える

一方で、list / dict / set のようなミュータブル型を引数に渡すと、関数の中で .append() / .update() などで中身を直接書き換えると、呼び出し元の変数にも同じ変更が反映されます。

関数に渡された時点で、呼び出し元の名札と関数内の引数名は同じ箱を共有しているからです。以前の「ミュータブルとイミュータブル」で見た y = x で同じ箱を指すのと同じ仕組みが、関数呼び出しでも起きています。

ミュータブル型の引数
呼び出し元my_list = [1, 2, 3]関数内items = [1, 2, 3]my_list =[1, 2, 3, 100](一緒に変わる)items =[1, 2, 3, 100]同じ箱を渡すitems.append(100)反映
def try_modify_list(items):
    items.append(100)
    print(f"関数内: {items}")

my_list = [1, 2, 3]
try_modify_list(my_list)
print(f"関数外: {my_list}")

# 関数内: [1, 2, 3, 100]
# 関数外: [1, 2, 3, 100]   ← 関数外にも増えている

# 辞書でも同じ
def set_role(user, role):
    user["role"] = role

admin = {"name": "田中"}
set_role(admin, "admin")
print(admin)   # {'name': '田中', 'role': 'admin'}

関数内の変更が外にまで漏れると、原因が追いにくい

cart.append(...)その行だけを見ると意図どおりに見えます。しかし関数内で書いた append が外の my_list にも伝わるため、「いつの間にかリストの中身が増えていた」という現象が、別の場所で発覚することがあります。

意図せず外のデータが書き換わることを体感します。

def add_item(cart, item): を定義し、cart.append(item) したあとに print(f"関数内: {cart}") を表示してください(return は書きません)。

my_cart = ["牛乳", "パン"] を用意し、add_item(my_cart, "卵") を呼び出してから print(f"関数外: {my_cart}") を表示してください。

関数の外の my_cart にまで "卵" が混ざっていることを確認しましょう(これがイミュータブルな前節との違いです)。

Python エディタ

コードを実行してください

関数の中だけで変更したい — .copy() で引数を守る

呼び出し元のデータは残したまま、関数の中でだけ変更したいときは、関数の先頭で .copy() を挟めばよいです。受け取ったリスト/辞書/集合を別の箱にコピーしてから書き換えれば、呼び出し元には影響ありません。

変更した結果は return で返すので、呼び出し側は 元のカート新しいカート を両方持てます。このパターンを身につけると、副作用(呼び出し元の書き換え)のない安全な関数を書けます。

全体の流れ:①引数を .copy() で別の箱にコピー → ②コピー側を編集 → ③ return で結果を返す。この 3 ステップを覚えておけば、ミュータブル型を引数に取る関数を安全に設計できます。

.copy() で新しい箱を作ってから変更
items = cart.copy()items.append(x)元の cart は変わらない影響なし
def add_item_safely(cart, item):
    items = cart.copy()    # 別の箱にコピーしてから編集
    items.append(item)
    return items           # 結果は return で返す

my_cart = ["牛乳", "パン"]
new_cart = add_item_safely(my_cart, "卵")
print(my_cart)   # ['牛乳', 'パン']          ← 影響なし
print(new_cart)  # ['牛乳', 'パン', '卵']     ← 別物

前節の関数を、呼び出し元を書き換えない安全版に作り直します。

def add_item_safely(cart, item): を定義し、items = cart.copy() で別の箱を作ってから items.append(item) し、最後に return items してください。

my_cart = ["牛乳", "パン"] を用意し、new_cart = add_item_safely(my_cart, "卵") で結果を受け取ってください。

my_cartnew_cart をそれぞれ print() して、元の my_cart が変わっていないことと、new_cart にだけ "卵" が追加されていることを確認してください。

Python エディタ

コードを実行してください
QUIZ

理解度チェック

まずは1問ずつ答えてみましょう。

Q1次のコードを実行したときの x の値はどれですか?
def f(value):
value += 10

x = 5
f(x)
print(x)

Q2次のコードを実行したときの my_list の値はどれですか?
def g(items):
items.append(100)

my_list = [1, 2, 3]
g(my_list)
print(my_list)

Q3呼び出し元のリストを書き換えずに、新しいリストを返す関数を書くときに、最初にすべき処理はどれですか?