68551


Pythonで下記のコードを書いたのですが、思っていたのとは違う動作をします。なぜなのでしょうか？

[code]

    class Shape():
        pass
        
    class Square(Shape):
        squs = []
        def __init__(self, w, h):
            self.width = w
            self.height = h
            self.squs.append((self.width, self.height))
    
    square = Square(40, 40)
    square1 = Square(50, 50)
    
    print(square1.squs)
    #結果[(40, 40), (50, 50)]空のリストを代入してから、appendしているので消えそうな気もするのですが？？
    
[/code]

追記

[code]

    class Shape():
        pass
        
    class Square(Shape):
        squs = []
        n = 1
        def __init__(self, w, h):
            self.width = w
            self.height = h
            self.squs.append((self.width, self.height))
            self.n = n + 100
    
    square = Square(40, 40)
    square1 = Square(50, 50)
    print(square1.n)
    print(square.n)
    
[/code]

nが定義されていないというメッセージが出てくるのですが、混乱してきました。  
予想では101、201と出力されると思いました。

詳しい方ご回答いただけると幸いです。


CC BY-SA 4.0
2020-07-13T02:30:45.560
68535
2020-07-16T01:31:37.140
2020-07-13T06:33:57.637
22565
22565
question
python python3
別々のインスタンスなのにリストの値が共有されるのはどうして


そのコードで言うところの `squs` (`Square.squs`) はインスタンス変数ではなくクラス変数です。

Python ドキュメント [9.3.5.
クラスとインスタンス変数](https://docs.python.org/ja/3/tutorial/classes.html#class-and-
instance-variables) を参照してください。


CC BY-SA 4.0
2020-07-13T04:28:27.213
68539
2020-07-13T04:28:27.213
13227
68535
answer


class文が何をするか? という話ですね。

<https://docs.python.org/ja/3/reference/compound_stmts.html#class-definitions>

> 次にクラスのスイートが、新たな実行フレーム (名前づけと束縛 (naming and binding) を参照してください)
> 内で、新たに作られたローカル名前空間と元々のグローバル名前空間を使って実行されます
> (通常、このスイートには主に関数定義が含まれます)。クラスのスイートが実行し終えると、実行フレームは破棄されますが、ローカルな名前空間は保存されます。次に、継承リストを基底クラスに、保存されたローカル名前空間を属性値辞書に、それぞれ使ってクラスオブジェクトが生成されます。最後に、もとのローカル名前空間において、クラス名がこのクラスオブジェクトに束縛されます。

  1. クラスのスイート(本体)が、新たな実行フレーム内で、新たに作られたローカル名前空間と元々のグローバル名前空間を使って実行される
  2. 実行フレームが破棄される
  3. ただしローカルな名前空間が保存される
  4. 継承リストを基底クラスに、保存されたローカル名前空間を属性値辞書に、それぞれ使ってクラスオブジェクトが生成される
  5. もとのローカル名前空間において、クラス名がこのクラスオブジェクトに束縛される

これだけのことが起こります。

* * *

ところで関数定義文は実行した時に[関数本体を実行しません](https://docs.python.org/ja/3/reference/compound_stmts.html#function-
definitions)。  
関数の本体は、"def文が実行された時"には **実行されず** 、"関数が呼び出された時"に初めて実行されます。

対してclass文の本体は、"class文が実行された時"に一度だけ **実行され** 、以降は **実行されることはありません** 。

質問者さんが誤解している、あるいは理解してないことはこの点かと思います。

* * *

(若干余談)

[code]

    class Hoge:
        print('body')
        def __init__(self):
            print('init')
    print('top level')
    Hoge()
    
[/code]

の実行結果を、実行せずに予測できますか?


CC BY-SA 4.0
2020-07-13T09:39:40.753
68551
2020-07-16T01:31:37.140
2020-07-16T01:31:37.140
12274
12274
68535
answer


前半について：

質問者さんに共有頂いたコードにおいて、`squs = []` は `Square` クラスを定義した際に一度だけ実行されます。その後、`Square`
をインスタンス化した際に実行されるのは `__init__` であり、`squs = []` は実行されません。

これらのことは、次のコードを実行することで確かめられます。

[code]

    class Square:
        squs = []
        print('Square defined')
    
        def __init__(self, w, h):
            print('__init__ called')
            self.squs.append((w, h))
    
    print('squs:', Square.squs)
    Square(1, 1)
    print('squs:', Square.squs)
    Square(2, 2)
    print('squs:', Square.squs)
    
    # Square defined
    # squs: []
    # __init__ called
    # squs: [(1, 1)]
    # __init__ called
    # squs: [(1, 1), (2, 2)]
    
[/code]

* * *

追記について：

変数 `n` は `__init__` のスコープにありません。`self.n` で呼び出す必要があります。

[code]

    class Square:
        n = 1
        squs = []
        def __init__(self, w, h):
            self.width = w
            self.height = h
            self.squs.append((w, h))
            self.n = self.n + 100
                                                                                             
    print('Square.n', Square.n)
    print('Square.squs', Square.squs)
                                                                            
    square1 = Square(1, 1)
    print('Square.n', Square.n)
    print('Square.squs', Square.squs)
    print('square1.n', square1.n)
    print('square1.squs', square1.squs)
    
    # Square.n 1
    # Square.squs []
    # Square.n 1
    # Square.squs [(1, 1)]
    # square1.n 101
    # square1.squs [(1, 1)]
    
[/code]

ここでもう一つ混乱しうる点は、`squs` はリスト型でミュータブルなのでインスタンス間で共有される一方、`n`
は整数型でイミュータブルなのでインスタンス間で共有されないことです。

<https://docs.python.org/ja/3/tutorial/classes.html#class-and-instance-
variables>

変数 `squs` がインスタンス間で共有されないようにするには、クラス変数ではなくインスタンス変数として導入します。

[code]

    class Square:
        def __init__(self, w, h):
            self.width = w
            self.height = h
            self.squs = []
            self.squs.append((w, h))
    
[/code]

クラス変数 `n` を `__init__` で書き換えたい場合は次のようにします。

[code]

    class Square:
        n = 1
        def __init__(self, w, h):
            self.width = w
            self.height = h
            self.__class__.n += 100
    
[/code]


CC BY-SA 4.0
2020-07-14T14:39:27.440
68591
2020-07-14T14:39:27.440
41038
68535
answer