このところ、『Effective Python』を読んでいました。
その「項目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
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(変更可) | 全インスタンスで共通 |
となります。
public class Exam { private static int _gradeRate; }
のように、フィールド変数宣言にstatic
を付ければよいです。
Pythonと違い、mutable、immutable問わず、全インスタンスで共有となります。
まとめ
C#との違いに着目して、Pythonのプロパティとフィールドについて書きました。
本来書きたいことはこれじゃないのですが、ディスクリプタについて説明するなら、プロパティとフィールドについて知らないと、理解が難しいです。
興味がある方は、ぜひ、自分でソースを書いてみてください。
次回、ディスクリプタについて書きます。