SE(たぶん)の雑感記

一応SEやっている筆者の思ったことを書き連ねます。会計学もやってたので、両方を生かした記事を書きたいと考えています。 でもテーマが定まってない感がすごい。

Pythonのディスクリプタについて:1.【前提知識】プロパティとフィールド

このところ、『Effective Python』を読んでいました。

honto.jp

その「項目31」に、

再利用可能な@propertyメソッドにディスクリプタを使う

という項目があります。

この内容がよく分からず、同じように書いても動かなくて困惑したので、調べました。

この記事では、その前提知識となる、プロパティとフィールド*1について書きます。
ディスクリプタについて知りたい方は、次の記事を参照してください。(投稿後、リンクを掲載)

C#でのプロパティ

C#のプロパティは、以下のように書きます。

  • 宣言側*2
public class Exam {
    private int _mathGrade;
    private int _writingGrade;
    private int _scienceGrade;

    public int MathGrade {
        get { return _mathGrade; }
        set { _mathGrade= value; }
    }

    public int WritingGrade {
        get { return _writingGrade; }
        set { _writingGrade= value; }
    }

    public int ScienceGrade {
        get { return _scienceGrade; }
        set { _scienceGrade= value; }
    }

    public int TotalGrade {
        get { return _mathGrade + _writingGrade + _scienceGrade; }
    }
}
  • 利用側
var exam = new Exam();
exam.MathGrade = 60;
exam.WritingGrade = 80;
exam.ScienceGrade = 90;
Console.WriteLine(exam.TotalGrade); //230

これだけだとありがたみがありませんね。
要するに、プロパティを介して、クラスが持つ情報にアクセスできるようになっています。
単なるフィールドだと、TotalGradeのような値アクセスはできませんが、プロパティなら、内部的にはメソッドなので、可能になります。

Pythonでのプロパティ

  • 宣言側
class Exam(object):
    """試験結果"""

    def __init__(self):
        """コンストラクタ"""
        self._math_grade = 0
        self._writing_grade = 0
        self._science_grade = 0

    def total(self):
        """合計点数"""
        return self.math_grade + self.writing_grade + self.science_grade

    @property
    def math_grade(self):
        """数学"""
        return self._math_grade

    @math_grade.setter
    def math_grade(self, value):
        self._math_grade = value

    @property
    def writing_grade(self):
        """記述"""
        return self._writing_grade

    @writing_grade.setter
    def writing_grade(self, value):
        self._writing_grade = value

    @property
    def science_grade(self):
        """科学"""
        return self._science_grade

    @science_grade.setter
    def science_grade(self, value):
        self._science_grade = value
  • 利用側
exam = Exam()
exam.math_grade = 60
exam.writing_grade = 80
exam.science_grade = 90
print(exam.total) # 230

getterとなるメソッドに@propertyを付し、setterとなるメソッドに@プロパティ名.setterを付します。
これで、プロパティとして認識されます。

Exam.__dict__で、クラス辞書を出力すると、

{
…省略
'math_grade': <property object at 0x0000019E44954E08>, 
'writing_grade': <property object at 0x0000019E44954E58>, 
'science_grade': <property object at 0x0000019E44954EA8>, 
…
}

のように、プロパティとして解釈されていることがわかります。

フィールド

上でプロパティについて書く際に、さらっと書いていますが、改めて説明します。

C#では、

public class Exam {
    private int _mathGrade;
    //以下省略
}

のように記述すると、フィールド変数(メンバ変数ともいう)を宣言できます。
この値は、各インスタンスで別の、固有の値として表現されます。

利用する際、

var exam1 = new Exam();
exam1.MathGrade = 60;

var exam2 = new Exam();
exam2.MathGrade = 80;

Console.WriteLine(exam1.MathGrade); //60
Console.WriteLine(exam2.MathGrade); //80

上のように値を設定すると、インスタンスによって別の値を持ちます。

同じように、Pythonで書いてみましょう。

class Exam(object):
    """試験結果"""
    math_grade_value= 0

    @property
    def math_grade(self):
        """数学"""
        return self.math_grade_value

    @math_grade.setter
    def math_grade(self, value):
        self.math_grade_value= value
exam1 = Exam()
exam1.math_grade = 60
exam2 = Exam()
exam2.math_grade = 80

print(exam1.math_grade) # 60
print(exam2.math_grade) # 80

C#と同じ結果になります。

…と思いきや、これは値変更不可のものが定義された場合のみそうなります。*3

例えば、これをlist*4へのアクセスに変更します。*5

class Exam(object):
    """試験結果"""
    math_grade_value = [0] # one element list defined

    @property
    def math_grade(self):
        """数学"""
        return self.math_grade_value[0] # index access

    @math_grade.setter
    def math_grade(self, value):
        self.math_grade_value[0] = value # index access
exam1 = Exam()
exam1.math_grade = 60
exam2 = Exam()
exam2.math_grade = 80

print(exam1.math_grade) # 80
print(exam2.math_grade) # 80

結果が変わります。

これは、Pythonクラス変数としてmutableオブジェクトが宣言された場合、各インスタンスで共有される仕様*6により起こります。

インスタンスによって、個別にリストを管理したい場合、

class Exam(object):
    """試験結果"""

    def __init__(self):
        self.math_grade_value = [0]
    #以下省略

のように、インスタンスの変数として宣言しましょう。

Pythonの変数宣言についてまとめると、

変数種類 変更可否 状態
インスタンス変数 問わない インスタンスで固有
クラス変数 immutable(変更不可) インスタンスで固有
クラス変数 mutable(変更可) インスタンスで共通

となります。

なお、C#で全インスタンス共通の変数を宣言するには、

public class Exam {
    private static int _gradeRate;
}

のように、フィールド変数宣言にstaticを付ければよいです。
Pythonと違い、mutable、immutable問わず、全インスタンスで共有となります。

まとめ

C#との違いに着目して、Pythonのプロパティとフィールドについて書きました。
本来書きたいことはこれじゃないのですが、ディスクリプタについて説明するなら、プロパティとフィールドについて知らないと、理解が難しいです。
興味がある方は、ぜひ、自分でソースを書いてみてください。

次回、ディスクリプタについて書きます。


*1:Pythonにある、C#のフィールド変数と同等の機能

*2:この構文なら、get,setのみで良いが、分かりやすくするために書いている

*3:いわゆるimmutableオブジェクト。int、str等が該当

*4:mutableオブジェクト

*5:意味のある実装ではない

*6:ポインタが共有されるため