pythonの関数の引数には、何が渡されているのでしょうか?それがわからないと、もとのオブジェクトを破壊してしまいます。今回はそれを確かめる実験です。pythonのバージョンは3.4.3です。
配列をコピーする
pythonでは関数内で渡された引数を変更しても、元の引数に影響はありません。
mystr = "test"
myint = 100
def modify_str_and_int(mystr, myint):
mystr = "after"
myint = 200
modify_str_and_int(mystr, myint)
print("mystr = %s" % mystr)
print("myint = %s" % myint)
# =>
# mystr = test
# myint = 100
配列を引数に取ると、呼び出し元の配列にも影響がでます。そのため、戻り値として加工した配列を返す必要はありません。
mylist = ["foo","bar",100]
def modify_list(mylist):
mylist.append("new")
print(mylist)
modify_list(mylist)
print(mylist)
['foo', 'bar', 100]
['foo', 'bar', 100, 'new']
しかし、変更したくない場合もあります。呼び出し元の配列を使い回す時などです。このような場合は、関数内で新しい変数を定義して、値を移しましょう。
mylist = ["foo","bar",100]
def modify_list(mylist):
new_list = mylist
new_list.append("new")
return new_list
print(mylist)
modify_list(mylist)
print(mylist)
# =>
# ['foo', 'bar', 100]
# ['foo', 'bar', 100, 'new']
もとの値が書き換わっていますね。これはnew_list
には値のコピーではなく、参照を渡しているからです。なので、new_list
とmylist
の2つの変数は同じlistオブジェクトを指しています。これはidで確認することができます。
mylist = ["foo"]
new_list = mylist
print(id(mylist))
print(id(new_list))
print(id(mylist) == id(new_list))
# =>
# 4528474888
# 4528474888
# True
そこで、mylist
と同じ値を持つlistオブジェクトを新しく生成してみましょう。組み込み関数listはシーケンス型をlistに変換するための関数ですが、listから新しくlistオブジェクトを生成することも可能です。
mylist = ["foo"]
new_list = list(mylist)
print(id(mylist))
print(id(new_list))
print(id(mylist) == id(new_list))
# =>
# 4313320328
# 4313327816
# False
同じように関数内で使えば、もとの配列には影響を与えません。
mylist = ["foo","bar",100]
def modify_list(mylist):
new_list = list(mylist)
new_list.append("new")
return new_list
print(mylist)
new_list = modify_list(mylist)
print(mylist)
print(new_list)
# =>
# ['foo', 'bar', 100]
# ['foo', 'bar', 100]
# ['foo', 'bar', 100, 'new']
intやstrも参照渡し
最初に見たサンプルではintやstrは関数内で変更しても、もとのオブジェクトに影響はありませんでした。配列に影響があったのは、同じオブジェクトを指していたからですよね。つまり、オブジェクトのidが同じだったからということです。ではintやstrだとidが違うのでしょうか?
mystr = "test"
myint = 100
def modify_str_and_int(mystr, myint):
print("mystr = %s" % id(mystr))
print("myint = %s" % id(myint))
print("mystr = %s" % id(mystr))
print("myint = %s" % id(myint))
modify_str_and_int(mystr, myint)
# =>
# mystr = 4308535648
# myint = 4305807456
# mystr = 4308535648
# myint = 4305807456
あれ?どちらも同じですね。これだともとの値にも影響が出るはずですよね。もう一度試してみましょう。
mystr = "test"
myint = 100
def modify_str_and_int(mystr, myint):
mystr = "after"
print("mystr = %s" % id(mystr))
print("myint = %s" % id(myint))
print("mystr = %s" % id(mystr))
print("myint = %s" % id(myint))
modify_str_and_int(mystr, myint)
# =>
# mystr = 4557928800
# myint = 4555208800
# mystr = 4557705768
# myint = 4555208800
あれ!?今度はmystrだけidが変わっています。これはスコープの問題です。pythonは全てオブジェクトのため引数ではオブジェクトのidをそのまま渡しています。関数内で同じ変数を新しく定義した場合は、配列とintやstrで挙動が変わります。今回だとstrのidが変わりましたよね。この基準はオブジェクトがミュータブル(可変)かイミュータブル(不変)かの違いです。イミュータブルだと同じ値を持つオブジェクトが生成されているのでidが変わっていたということです。
関数の中でなくても、変数に同じid値が渡されることは確認できます。1つ違うのは同じ引数名にすると自分自身を指すことになってしまうということです。関数内だと初めの1回は新しい変数として扱われます。
mystr = "test"
myint = 100
mylist = ['foo', 'bar']
new_str = mystr
new_int = myint
new_list = mylist
print(id(new_str) == id(mystr))
print(id(new_int) == id(myint))
print(id(new_list) == id(mylist))
# =>
# True
# True
# True
def display_outer():
print("mystr = %s" % mystr)
print("myint = %s" % myint)
print("mylist = %s" % mylist)
display_outer()
# =>
# mystr = test
# myint = 100
# mylist = ['foo', 'bar']
関数は外側の変数を参照できる
引数にはid値がそのまま渡されていることがわかりました。ということは、関数内では外側の変数を参照することが可能ということになります。
mystr = "test"
myint = 100
mylist = ['foo', 'bar']
def display_outer():
print("mystr = %s" % mystr)
print("myint = %s" % myint)
print("mylist = %s" % mylist)
display_outer()
# =>
# mystr = test
# myint = 100
# mylist = ['foo', 'bar']