56197


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

[code]

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


CC BY-SA 4.0
2019-06-28T03:49:53.667
56172
2019-06-28T22:50:24.377
2019-06-28T11:16:07.780
31249
31249
question
python python3
Python3においてクラス外の関数内で渡されたインスタンスのメンバ変数を変更した場合に、そのメンバ変数の変更は関数の外でも適用されるかどうか?


まず、Python3ではメンバ変数ではなくクラス変数、及びインスタンス変数と言います。

[9\. クラス — Python 3.7.4rc1
ドキュメント](https://docs.python.org/ja/3/tutorial/classes.html#class-and-instance-
variables)

その上で回答すると、 **どのように値をセットしたかによって変わります** 。

以下に、インスタンス変数をセットしたもの、クラス変数を変更した例を示します。

[code]

    class Apple:
        color = 'green'
    
        def __init__(self, _id):
            self.id = _id
    
        def ripen(self):
            self.color = 'red'
    
        def ripen_apple_class(self):
            Apple.color = 'rainbow'
    
    
    def main():
        first_apple = Apple(1)
        second_apple = Apple(2)
        third_apple = Apple(3)
    
        print("================ At first")
        print("Apple #{apple.id}: {apple.color}".format(apple=first_apple))
        print("Apple #{apple.id}: {apple.color}".format(apple=second_apple))
        print("Apple #{apple.id}: {apple.color}".format(apple=third_apple))
    
        first_apple.ripen()
        print("================ After first apple ripen")
        print("Apple #{apple.id}: {apple.color}".format(apple=first_apple))
        print("Apple #{apple.id}: {apple.color}".format(apple=second_apple))
        print("Apple #{apple.id}: {apple.color}".format(apple=third_apple))
    
        first_apple.ripen_apple_class()
        print("================ All apple ripen")
        print("Apple #{apple.id}: {apple.color}".format(apple=first_apple))
        print("Apple #{apple.id}: {apple.color}".format(apple=second_apple))
        print("Apple #{apple.id}: {apple.color}".format(apple=third_apple))
    
    
    if __name__ == "__main__":
        main()
    
[/code]

出力

[code]

    ================ At first
    Apple #1: green
    Apple #2: green
    Apple #3: green
    ================ After first apple ripen
    Apple #1: red
    Apple #2: green
    Apple #3: green
    ================ All apple ripen
    Apple #1: red
    Apple #2: rainbow
    Apple #3: rainbow
    
[/code]


CC BY-SA 4.0
2019-06-28T11:03:22.153
56184
2019-06-28T22:50:24.377
2019-06-28T22:50:24.377
31249
29826
56172
answer


ご指摘の通り、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)では「以下のような場合にクラス変数を用いるべき」と説明されています。

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

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


CC BY-SA 4.0
2019-06-28T12:47:58.240
56191
2019-06-28T12:47:58.240
10743
56172
answer


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

[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`でも変わった中身が見えるわけです。


CC BY-SA 4.0
2019-06-28T14:20:29.403
56197
2019-06-28T14:20:29.403
3605
56172
answer