Python3においてクラス外の関数内で渡されたインスタンスのメンバ変数を変更した場合に、そのメンバ変数の変更は関数の外でも適用されるかどうか?


以下のような処理をPython3で実行した場合に、modelのハイパーパラメータ(メンバ変数)の値の変更は関数learnの外でも適用されてしまうのでしょうか?  
また、メンバ変数の変更が行われるにせよ行われないにせよそのような動作をするのは何故でしょうか?  
どなたか分かる方がいらっしゃればご教授頂けると助かります。  
(Jupyter Notebook上でのコード実行を想定しています。)

[code]

    class Model:
        def train(self,..):
        #ハイパーパラメータ(メンバ変数)の値を変える処理
    
    def learn(model):
        model.train()
    
    model = Model()
    learn(model)
    
[/code]




インスタンスを作成したときに変数に代入されるのは、インスタンスそのものではなくて、作成されたインスタンスへの参照です。ほかの変数への代入や関数呼び出しの引数に渡されるのは、全て、参照です。

[code]

    class Apple:
        color = 'green'
    
        def __init__(self, _id):
            self.id = _id
    
        def ripen(self):
            self.color = 'red'
    
    def change_color(apple):
        apple.ripen()
    
    def main():
        first_apple = Apple(1)
    
        change_color(first_apple)
    
[/code]

上のコードで、`main()`から`change_color(first_apple)`を呼んだ時点で、インスタンスがどのように参照されているかを図示すると

[code]

    main()
        first_apple ---------------------+
                                         |
    change_color()                       |
        apple -----------------------+   |
                                     |   |
                                     V   V
      Apple(1)で作られたインスタンス +-------+
                                   | id    |
                                   | color |
                                   +-------+
    
[/code]

となり、つまり`main`内の`first_apple`も、`change_color`内の'apple'も、同じインスタンスを指しています。同じインスタンスなので、`change_color`で中身を変えれは、`main`でも変わった中身が見えるわけです。




ご指摘の通り、Python3においてはクラス変数への変更は、関数の中であるか外であるかにかかわらず、複数のインスタンスで共有されます。

[code]

    # https://stackoverflow.com/a/207128
    >>> class A: foo = []
    >>> a, b = A(), A()
    >>> a.foo.append(5)
    >>> b.foo
    [5]
    
[/code]

[Python3の公式ドキュメント](https://docs.python.org/ja/3.7/tutorial/classes.html#class-
and-instance-variables)には次の通り説明があります。

>
> 一般的に、インスタンス変数はそれぞれのインスタンスについて固有のデータのためのもので、クラス変数はそのクラスのすべてのインスタンスによって共有される属性やメソッドのためのものです:  
>  名前とオブジェクトについて で議論したように、共有データはリストや辞書のような mutable
> オブジェクトが関与すると驚くべき効果を持ち得ます。例えば、以下のコードの tricks
> リストはクラス変数として使われるべきではありません、なぜならたった一つのリストがすべての Dog インスタンスによって共有されることになり得るからです:

同時に、

> このクラスの正しい設計ではインスタンス変数を代わりに使用するべきです:

と説明されているとおり、このような用途においては常にインスタンス変数を使用するべきです。

「何故なのか」という質問に答えるのは難しいですが、[stackoverflow英語版の同様の質問への回答](https://stackoverflow.com/a/34126204)では「以下のような場合にクラス変数を用いるべき」と説明されています。

  * 数学定数など、不変の値を保存する場合
  * インスタンスにおけるデフォルトの値を定義する場合

お役に立てれば幸いです。

