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


pythonの関数の引数には、何が渡されているのでしょうか？それがわからないと、もとのオブジェクトを破壊してしまいます。今回はそれを確かめる実験です。pythonのバージョンは3.4.3です。

### 配列をコピーする

pythonでは関数内で渡された引数を変更しても、元の引数に影響はありません。

```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
```

配列を引数に取ると、呼び出し元の配列にも影響がでます。そのため、戻り値として加工した配列を返す必要はありません。

```python
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']
```

しかし、変更したくない場合もあります。呼び出し元の配列を使い回す時などです。このような場合は、関数内で新しい変数を定義して、値を移しましょう。

```python
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で確認することができます。

```python
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オブジェクトを生成することも可能です。

```python
mylist = ["foo"]
new_list = list(mylist)

print(id(mylist))
print(id(new_list))
print(id(mylist) == id(new_list))

# =>
# 4313320328
# 4313327816
# False
```

同じように関数内で使えば、もとの配列には影響を与えません。

```python
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が違うのでしょうか？

```python
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
```

あれ？どちらも同じですね。これだともとの値にも影響が出るはずですよね。もう一度試してみましょう。

```python
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回は新しい変数として扱われます。

```python
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値がそのまま渡されていることがわかりました。ということは、関数内では外側の変数を参照することが可能ということになります。

```python
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']
```