1001001

73。CTFのWrite-upから始まったけど最近は技術全般の備忘録となっています。

Pythonのクラスにおけるインスタンス変数とクラス変数の挙動の覚書

Python(3.6.3)でクラスを書いていた時に「え,これ動くんだ」と感じた挙動についてメモ.


結論から言うと,場合によっては<instance>.クラス変数 でクラス変数へアクセスできるが、 <class>.クラス変数 って書く方が良さそう、という話です.

self.クラス変数<instance>.クラス変数 でクラス変数にアクセスできる (特別な理由がなければ非推奨)

普通は <class>.クラス変数 という風にアクセスすると思うんですが,上記のような書き方が出来てしまうらしい.

# coding:utf-8
class Test:
    class_var = "Test Class Var"
    def __init__(self):
        print("2: "+self.class_var) # <instance>.クラス変数も許される
        pass

if __name__ == "__main__":
    print("1: "+Test.class_var) # 普通は<class>.クラス変数
    t = Test()
    print("3: "+t.class_var) # <instance>.クラス変数も許される

できてしまう、とか書いたが、これ自体はJavaとかでも同様で,インスタンス経由でクラス変数へアクセスすることは可能である.

public class A{
    static String a = "Class Variable";
    String ia;
    public A(){
        this.ia = "Instance Varialbe";
    }
}
public class Test{
    public static void main(String[] args){
        A i1 = new A();
        System.out.println(A.a);
        System.out.println(i1.a); // クラス変数を表示
        System.out.println(i1.ia);
    }
}

クラス変数とインスタンス変数を同名にすることが許される

例えば,クラス変数a,インスタンス変数aが許される

# coding:utf-8
class Test:
    a = "Class Variable"
    def __init__(self):
        self.a = "Instance Variable"
        print("2: "+self.a) # インスタンス変数のa
        pass

if __name__ == "__main__":
    print("1: "+Test.a) # クラス変数のa
    t = Test()
    print("3: "+t.a) # インスタンス変数のa

Javaとかでは,同じスコープ内に同名の変数を定義しようとするとエラーが出るから名前変えようってなる.

public class A{
    static String a = "Class Variable";
    String a; // <- 許されなさ
    public A(){
        this.a = "Instance Varialbe";
    }
}
A.java:3: エラー: 変数 aはすでにクラス Aで定義されています
    String a;
               ^

上二つが混ざるとややこしいことになる

クラス変数なのかインスタンス変数なのかよくわからない状態になる.

class Test:
    class_var = "Class Variable"
    def __init__(self):
        self.instance_var  = "Instance Variable"

    def set_instance_var(self):
        self.class_var = "aaa"

if __name__ == "__main__":
    t = Test()
    print(Test.class_var)
    print(t.class_var) # クラス変数へのアクセスとみなされる
    print()

    Test.class_var = "Modified Class Variable"
    print(Test.class_var)
    print(t.class_var) # クラス変数へのアクセスなので値が変わっている
    print()

    t.set_instance_var()
    print(Test.class_var)
    print(t.class_var) # インスタンス変数へのアクセスに変わる


    Test.class_var = "Class Variable" # 次の検証のため戻しておく
    print("--------")

    t2 = Test()
    print(Test.class_var)
    print(t2.class_var) # クラス変数へのアクセスとみなされる
    print()

    t2.class_var = "Modified Class Variable" # クラス変数にアクセスできていたので,クラス変数を変えたくて書いたとする
    print(Test.class_var)
    print(t2.class_var) # 上の式は「新たなインスタンス変数の定義」とみなされる

つまり,<instance>.クラス変数 でクラス変数にアクセスできてしまうんだけど,インスタンス変数とクラス変数が同名になることが許されているが故に,同名のインスタンス変数ができた瞬間,インスタンス変数の方を参照するようになるということ.

普通は同名の変数名はわかりにくいので付けなさそうだし,<instance>.クラス変数 なんて書き方はしないんじゃないかと思う(クラス変数の書き換えもあまりやらない?).前述したようにJavaとかならエラーを出してくれる.しかしPythonはエラーが起こらず正常に動いてしまうのでうっかりやっちゃうこともあるんじゃないかと感じた.

まとめ

場合によっては<instance>.クラス変数 でクラス変数へアクセスできるけど普通に <class>.クラス変数 って書こうと思った.
インスタンス経由でアクセスしたい時ってどういう時なんでしょうかね.経験不足で分かりません...

りふぁれんす

本件はよくある話みたいで,調べたら既存の記事が見つかった.
クラス変数にはどうアクセスすべき? #Python - Qiita
Pythonのクラス変数とインスタンス変数 | UX MILK