2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PythonAdvent Calendar 2024

Day 11

pythonのclass variableに納得いかなかった話

Last updated at Posted at 2024-12-10

 愚痴です。それがpythonの思想だと言われればそれまでなんですけど……

他言語の例

 例えばC++。クラス変数というからにはこういうイメージでした。

#include <cassert>

class Foo{
public:
    static int value;
};

int Foo::value = 1;

int main(){
    Foo foo1 = Foo();
    Foo foo2 = Foo();
    
    assert(Foo::value == 1);    // クラスから値を取得
    assert(foo1.value == 1);    // インスタンスから値を取得
    
    Foo::value = 2;             // クラスから値を設定
    assert(Foo::value == 2);
    assert(foo1.value == 2);

    foo2.value = 3;             // インスタンスから値を設定
    assert(Foo::value == 3);
    assert(foo1.value == 3);
}

 pythonに明るい皆さんにおかれましては、もうオチが見えたことでしょう。

pythonの場合

 同じようにやってみます。

class Foo(object):
    value: int = 1                              # クラス変数
    
foo1 = Foo()
foo2 = Foo()

assert Foo.value == 1, f'{Foo.value=}'          # クラスから値を取得
assert foo1.value == 1, f'{foo1.value=}'        # インスタンスから値を取得

Foo.value = 2                                   # クラスから値を設定
assert Foo.value == 2, f'{Foo.value=}'
assert foo1.value == 2, f'{foo1.value=}'

 うんうん、良さそうですね。するとこれも、

foo2.value = 3                                  # インスタンスから値を設定
assert Foo.value == 3, f'{Foo.value=}'
assert foo1.value == 3, f'{foo1.value=}'
AssertionError                            Traceback (most recent call last)
Cell In[2], line 2
      1 foo2.value = 3                              # インスタンスから値を設定
----> 2 assert Foo.value == 3, f'{Foo.value=}'
      3 assert foo1.value == 3, f'{foo1.value=}'

AssertionError: Foo.value=2

えっ

何が起きている

 valueはクラス変数なのでインスタンスの中にはない筈ですが、覗いてみるとなにやらfoo2valueを持っています。

print(vars(foo1))   # {}
print(vars(foo2))   # {'value': 3}

 それもそのはず、実はこの行はクラス変数を書き換えているのではなく同名のインスタンス変数valueを新規に設定しているんですね。

foo2.value = 3      

何が嫌

いや、だって凄く紛らわしくないですか……?

Foo.value           # クラス変数へのアクセス
Foo.value = 2       # クラス変数へのアクセス
foo2.value          # クラス変数へのアクセス
foo2.value = 3      # インスタンス変数へのアクセス
foo2.value          # インスタンス変数へのアクセス

 どうしてこんな設計なのかとCopilotに訊くと、意図せず(インスタンス変数のつもりで)クラス変数を書き換えたりしないよう、安全策でこうなっているという説明。でもWe are all consenting adults hereだというなら、触ろうとしているのがクラス変数なのかインスタンス変数なのは理解している筈で、そんな安全策を設ける必要はないのではという気もします。
 また、その割にインスタンスからのクラス変数の取得は許しているのも一貫性がないような。これだって、迂闊にインスタンス変数のつもりでクラス変数を読んだりなんかしていたら、思わぬところで値が書き換わっていて……となりそうなものですけれど。
 個人的には、それならインスタンスを経由したクラス変数へのアクセスは読み書きともに禁止した方が良かったのではないかと思ってしまいます。型アノテーションでクラス名は分かるし、というのは近年のpythonに染まった考え方かもしれませんが、そうでなくともtype(instance).class_varとかすればいいわけですし。
 ……なんて言っている間に手を動かしてコードを書いていればいずれ慣れて気にならなくなるんでしょうけどね。どっとはらい。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?