可変な関数引数
Pythonには様々なデータ型があり、その中には作成後に変更可能なものと変更不可能なものがあります。変更可能な型を「可変型」と呼び、反対に変更できない型を「不変型」と呼びます。
可変型
最も一般的な可変型はlist、dict、そしてsetです。以下にlistの例を示します:
fruits = ['apple', 'banana', 'cherry']
print(fruits, type(fruits)) # ['apple', 'banana', 'cherry'] <class 'list'>listでは、その要素を変更したり、新しい要素を追加したり、既存の要素を削除したりできます:
fruits[1] = 'blueberry' # 'banana'を'blueberry'に変更
fruits.append('dragonfruit') # 'dragonfruit'をリストの末尾に追加
print(fruits) # ['apple', 'blueberry', 'cherry', 'dragonfruit']不変型
一方、int、float、string、そしてtupleは不変型の例です。これらは一度作成すると、その内容を変更することはできません。以下にstringの例を示します:
message = 'hello'
print(message, type(message)) # hello <class 'str'>stringの一部を変更しようとすると、Pythonはそれを許可しません:
message[0] = 'H' # 'h'を'H'に変更しようとすると、TypeErrorが発生しますなぜintは不変型なのでしょうか?+=や-=で値を変えられるのでは?
Pythonで可変性を議論する際、それはメモリに格納された実際の値を直接変更できるかどうかを意味します。例えば、リストがある場合、リストのアイテムを変更しても、それはメモリ上で同じリストのままです。
Pythonの整数(int型)は不変型です。つまり、メモリ内の値自体を変更することはできません。例えば、a = 5という整数があり、a += 2のような操作をすると、一見aの値を変更したように見えます。しかし、Pythonが実際に行っているのは、値7を持つ新しいオブジェクトをメモリに作成し、aがその新しいオブジェクトを指すように更新しているのです。元の5はメモリ内で変更されていません。これが、int型が不変とされる理由です。
a = 5
print(id(a)) # これはaのメモリ上の位置を出力します。仮に1001とします
a += 2
print(a) # これは7を出力します
print(id(a)) # これは異なるメモリ位置を出力します。仮に1002としますPythonのid関数は、オブジェクトの識別子(ID)を返します。これは、そのオブジェクトの生存期間中、一意で不変です。a += 2の操作後にaのIDが変わっていることから、aが新しいメモリ上のオブジェクトを指していることが明確にわかります。元の整数5は変更されていません。これにより、整数がPythonで実際に不変であることが確認できます。
関数内での可変な引数
可変なオブジェクト(リストなど)を関数に渡すと、Pythonはそのオブジェクトへの参照を関数に渡します。つまり、関数がその可変オブジェクトを変更した場合、その変更は関数の外部でも反映されます。
リストを受け取り、それに要素を追加する関数を使用してみましょう:
def add_item(my_list):
my_list.append('added item') # リストに新しい項目を追加
shopping_list = ['apples', 'bananas', 'cherries']
print(shopping_list) # Prints: ['apples', 'bananas', 'cherries']
# 関数を使ってショッピングリストにアイテムを追加します
add_item(shopping_list)
print(shopping_list) # Prints: ['apples', 'bananas', 'cherries', 'added item']ご覧のとおり、add_item関数を呼び出した後にshopping_listを出力すると、リストに新しいアイテムが追加されています。これは、関数の外部でshopping_listを直接変更していないにもかかわらず起こりました。
しかし、関数内で可変オブジェクト全体に新しい値を代入しても、元のオブジェクトには影響しないことを理解することが重要です。これは、関数のローカル変数に新しい参照を割り当てており、元のオブジェクト自体を変更していないからです。以下にその例を示します:
def change_list(my_list):
my_list = ['entirely', 'new', 'list'] # これは元のリストに影響を与えません
shopping_list = ['apples', 'bananas', 'cherries']
change_list(shopping_list)
print(shopping_list) # Still prints: ['apples', 'bananas', 'cherries']shopping_listを引数としてchange_list関数を呼び出しましたが、shopping_listの内容は変わっていません。これは、関数内でmy_listに新しい参照(完全に新しいリスト)を割り当てた一方、shopping_list自体の参照は変更されていないからです。
ここでの重要なポイントは、Pythonで可変なオブジェクトを関数に渡す際、関数がそのオブジェクトを変更すれば(リストに要素を追加するなど)、その変更は関数の外部でも反映されるということです。しかし、関数内でオブジェクト全体に新しい値を代入した場合、元のオブジェクトには影響しません。