引数の受け渡し、ミュータブルとイミュータブルの違いとは?
pythonには、ミュータブル(変更可能)とイミュータブル(変更不可)という概念があります。
引数を受け渡す際の両者の違いについて、見ていきたいと思います。
サンプル1(イミュータブルの例)
def main():
result = True
submain(result)
return result
def submain(result):
result = False
result = main()
print(result)
これは、result = main()で、main()関数からreturnで返されるresultの値を取得し、その値をprintで出力するプログラムとなっています。
main()関数では、resultの値はTrueとなり、submain()関数が実行されます。
submain()関数内でresultの値はFalseとなりますが、これはsubmain()関数内だけのローカル変数のため、main()関数内のresultには全く影響を及ぼしません。
resultは、それぞれの関数内のローカル変数であり、returnで返される値はあくまでもmain()関数内の値となります。
その結果、最終的にはprint(result)でTrueが出力されます。
サンプル2(ミュータブルの例)
def main():
result = [True]
submain(result)
return result[0]
def submain(result):
result[0] = False
result = main()
print(result)
これは、サンプル1とほぼ同じプログラムですが、main()関数のresultがリスト形式のデータとなっており、Trueという値を持っています。
そして、submain(result)で、resultのリストをsubmain()関数へ受け渡しています。
submain()関数では、result[0] = Falseで、リストの0番目の要素すなわちTrueをFalseに書き換えています。
リストは、ミュータブル(変更可能)な変数であるため、関数を跨いで変更を加えることができるのです。
submain()関数の処理が終わり、main()関数に戻って、returnでresult[0]すなわち、リストの0番目の要素を返しています。
その結果、最終的にはprint(result)でFalseが出力されます。
イミュータブル(変更不可)とミュータブル(変更可能)
サンプル1のresultは、イミュータブル(変更不可)であるため、main()関数とsubmain()関数におけるresultは、それぞれ関数内のローカル変数であり無関係の存在です。
一方、サンプル2のresultは、ミュータブル(変更可能)であるため、他の関数へ受け渡して他の関数内での変更されると、呼び出し元に反映される形になります。
この結果、サンプル1はTrue、サンプル2はFalseと出力されることとなりました。
ミュータブル型には、リストや辞書などがあります。一方、数値や文字列、小数、バイナリデータ、タプル、論理(True / False)などはイミュータブル型です。
イミュータブル型でも異なる関数へ受け渡して値を変更したい場合
イミュータブル型を異なる関数へ受け渡し、値を変更して反映したい場合、以下のようにクラス(class)を使用すれば可能です。
クラスとは、ここでは、複数の関数をまとめたかたまりと理解してください。
class Main:
def __init__(self):
self.result = True
Main.submain(self)
def submain(self):
self.result = False
result = Main().result
print(result)
ここでは、result = Main().resultで、Mainクラスを実行しMainクラスのresultの値を取得し、printするプログラムになっています。
def __init__(self):とは、Mainクラスを実行したときに、最初に実行される処理をまとめたものです。ここでは、selfという引数を使用し、self.result = Trueとしています。
self.resultとインスタンス化することにより、クラス内の複数の関数でselfを受け渡すことで、resultはローカル変数ではなくなり、自由に値を変更できる変数(インスタンス変数)となります。
Main.submain(self)により、Mainクラスのsubmain()関数が実行され、self.resultの値はFalseに書き換えられます。
Main().resultとすると、Mainクラスでインスタンス化されたresultという変数の値が呼び出されます。Mainクラスのself.resultの値がresultに代入されることになります。
その結果、最終的にはprint(result)で、Falseが出力されます。