LoginSignup
13
10

More than 5 years have passed since last update.

pythonの関数が、呼び出し元の引数に影響を与える時はいつか?

Last updated at Posted at 2015-12-14

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_listmylistの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']
13
10
8

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
10