[Python] クラス変数をもつクラスを継承する

ポイント：

- `self.変数名`でアクセスすると，インスタンス変数→クラス変数→親クラスのクラス変数の順に探索する
- `self.辞書[キー] = 値`は新たな辞書を作らず，`self.辞書`を探索する
    - `self.辞書`というインスタンス変数がない場合，クラス変数や親クラスのクラス変数を参照するため，それらが（意図せず）変更される可能性がある
    - 新たに辞書を作るには，`self.辞書 = {キー: 値}`

**2020/07/06追記**: @shiracamusさんのコメントを受けて内容を修正．

## 症状

```Python
# 親クラス
class AbstClass:
    class_number = 0
    dict_number = {'class': 0}

# 子クラス1
class Class1(AbstClass):
    def __init__(self):
        self.class_number = 1
        self.dict_number['class'] = 1

# 子クラス2
class Class2(AbstClass):
    def __init__(self):
        self.class_number = 2
        self.dict_number['class'] = 2

obj1 = Class1()
print(obj1.class_number, obj1.dict_number['class']) # 1, 1 ←わかる
obj2 = Class2()
print(obj2.class_number, obj2.dict_number['class']) # 2, 2 ←わかる
print(obj1.class_number, obj1.dict_number['class']) # 1, 2 ←!?
```

「2つの子クラスが同じ親クラスのクラス変数にアクセスしている」なら分かりやすいが，
上記の例では

- `class_number`は2つのクラスで別々に管理
- `dict_number`は2つのクラスで共有→Class2()をしたときにClass1で書き換えたdict_numerを上書き

になっている．




## 対応

### `辞書[キー] = 値`は元々ある辞書を探索

```Python
class Class1(AbstClass):
    def __init__(self):
        self.class_number = 1         # 新たなインスタンス変数class_numberが作られる
        self.dict_number['class'] = 1 # インスタンス変数にdict_numberがないので，
                                      # Class1のクラス変数→AbstClassのクラス変数を探索
                                      # AbstClassのdict_numberが更新される
```

という動作になっている．

「どうしてself.dict_numberは新しく辞書を作らないのだろう？」について，
[Qiita@ponnhide: pythonの参照について](https://qiita.com/ponnhide/items/cda0f3f7ac88262eb31e)に，

> 変数に新たなオブジェクトそのものが代入されるときには、これまでの参照先から変わって新たなオブジェクトが作られた場所を参照するようになる

とある．従って，上記`self.dict_number['class'] = 1`の代わりに

```Python
self.dict_number = {'class': 1}
```

とすると，他のクラス変数に影響を与えずに新たなインスタンス変数`dict_number`が作られる．


### クラス変数を明示して代入するには`type(self).クラス変数名`

```Python
class Class1:
    class_number = 0

    def __init__(self):
        self.class_number = 1 # インスタンス変数（そのインスタンスにのみ保持）

obj1 = Class1()
print(obj1.class_number)   # 1
print(Class1.class_number) # 0
```

```Python
class Class1:
    class_number = 0

    def __init__(self):
        type(self).class_number = 1 # クラス変数に代入される

obj1 = Class1()
# インスタンスを作ったあとには
# type(obj1).class_number = 1
print(obj1.class_number)   # 1
print(Class1.class_number) # 1
```


## ~~原因の仮説~~

~~mutable/immutableが関係している？と推測．つまり，~~

- ~~mutableなクラス変数（上記の`dict_number`）: 子クラスでも共有~~
- ~~immutableなクラス変数（上記の`class_number`）: 子クラスでは別々に管理~~

~~実際，AbstClassに`class_number_tuple = (0,)`というタプルを定義すると，~~
~~`class_number`と同様の振る舞いが確認された．~~
~~（※intはimmutable．参考：
　[GAMMASOFT: Pythonの組み込みデータ型の分類表（ミュータブル等）]
(https://gammasoft.jp/blog/python-built-in-types/)）~~


## 対策

~~子クラス内でクラス変数を再定義する：~~

```Python
class Class1(AbstClass):
    class_number = 0
    dict_number= {'class': 0}

class Class2(AbstClass):
    class_number = 0
    dict_number= {'class': 0}
```
~~と，各インスタンスではそれぞれのクラスのクラス変数を優先する＝別々に管理することになる．~~

~~ただし，欲を言えば言語として自動的に例外を出してほしい．~~

- ~~実際，辞書のキーにリストを入れるとunhashableと怒られる~~
- ~~ABCの`@abstractmethod`のように，「共有すべきクラス変数」「子クラスで上書きすべきクラス変数」を明示できるようにできれば…~~

~~現状の対策としては，~~

- ~~継承されることが前提の抽象クラスにはクラス変数を定義しない（上記の通り結局子クラスで再定義する方が安全なので単純に行数の無駄）~~
- ~~そうでないクラスのクラス変数にアクセスする必要がある時は，継承ではなく委譲を使う（理想）~~
- ~~継承するクラスのソースコードは冒頭だけでも読む（現実）~~

~~ぐらいでしょうか…．何か良い方策などございましたらご教示頂ければ幸いです．~~