引数の渡し方を理解しよう(Python編)

  • 7
    Like
  • 0
    Comment
More than 1 year has passed since last update.

Pythonにおいて、関数や、オブジェクトのメソッドに引数を渡す際の取り扱(Evaluation strategy)についてまとめておきます。
Pythonの変数とオブジェクトについてで説明したように、Pythonではすべての値はオブジェクトであり、変数にはオブジェクトのリファレンス(参照)が格納されています。
関数が呼び出されると、実引数(呼び出し側の引数)のリファレンスが仮引数(関数側の引数)に"コピー"されて渡されと考えるとよいでしょう。この方式を「共有渡し」(call by sharing)あるいは「参照の値渡し」(call by value where value is the reference copy)などと呼びます。

■ イミュータブルな値を引数として渡す場合

Pythonでは、関数内部で引数に別の値を再代入して、変数の参照先つまりリファレンスを変更した場合には呼び出し側に反映されません。まず、数値や文字列のようなイミュータブルな値の変数を引数にして渡す場合を考えてみましょう。次の例では、関数test1()の内部で引数argの値に10を加えています。これは、実際には、引数の値に10を加えた新たな整数オブジェクトを生成し、そのリファレンスを変数argに再代入しています。

def test1(arg):
    arg += 10 # ① argの値に10を足して再代入

num = 3
test1(num)
print(num) # ② numの値を表示

関数test1()を呼び出し後に、関数の外部②で変数numの値を表示してみると、①の関数の内部での引数の値の変更は反映されていません。

$ python3 arg_test1.py 
3   ←②変数numの値は「3」のまま

これは①で引数argに再代入することにより、引数argの参照先が変更されてしまったからです。①が実行する前は、関数外部のグローバル変数numと、引数argは同じ値を参照していますが、①の後では別の値を参照しているわけです。
参照先が変化したことは、次のようにオブジェクトのID番号を表示するid()関数で調べることができます。

def test1(arg):
    # 関数の内部で引数を変更
    print("arg: {}".format(id(arg))) 
    arg += 10 # ①
    print("arg: {}".format(id(arg)))

num = 3
test1(num)
print(num) # numの値を表示

実行結果は次のようになります

$ python3 arg_test1.py 
arg: 4297537952 ←①の前
arg: 4297538272 ←①の後 IDが変化した
3   ←関数の外側では変数numの値はそのまま

■ ミュータブルな値を引数として渡す場合(その1)

リストや辞書のようなミュータブルな値を引数として渡す場合も同様です、リファレンスが変化した場合には呼び出し側には反映されません。次の例では、リストを引数に渡し、関数の内部で「arg + [4, 5]」により要素を追加し、argに再代入しています。

def test2(arg):
    # 関数の内部で引数を変更
    print("arg: {}".format(id(arg)))
    arg = arg + [4, 5] # ①要素を追加
    print("arg: {}".format(id(arg)))

myLst = [1, 2, 3]
test2(myLst)
print(myLst) # myLstの値を表示

この場合①では「+」演算子で「[4, 5]」が結合された新たなリストオブジェクトが生成されるため、リファレンスが変化します。したがって呼び出しもとには反映されません。

$ python3 arg_test2.py 
arg: 4329564104
arg: 4329566024 ←IDが変化した
[1, 2, 3]   ←関数の外側では値が変化しない

■ ミュータブルな値を引数として渡す場合(その2)

ミュータブルな値を引数として渡す場合に、関数内で参照先が変更されなければ、関数内での変更は呼び出し側に反映されます。たとえば、オブジェクトの値を直接変更するメソッドを実行した場合などです。
次の例では関数内部の①で前の例の「+」演算子の代わりにextend()メソッドによりリストに要素を追加しています。また②で「arg[1] = 10 」によりリストの2番目の要素を「10」に変更しています。

def test3(arg):
    # 関数の内部で引数を変更
    print("arg: {}".format(id(arg)))
    arg.extend([4, 5]) # ① [4, 5]を追加
    arg[1] = 10 # ② 2番目の要素を変更
    print("arg: {}".format(id(arg)))

myLst = [1, 2, 3]
test3(myLst)
print(myLst) # myLstの値を表示

この場合、①②で新たなオブジェクトは生成されないため、リファレンスは変化しません。したがって、関数内部での引数の値の変更は呼び出しもとに反映されます。

$ python3 arg_test3.py 
arg: 4321175496
arg: 4321175496 ←IDは変化しない
[1, 10, 3, 4, 5]