デコレーター@propertyはメソッドを「仮想」クラス属性に変換します:これはクラスのユーザーにとって通常のプロパティのように見えますが、Python関数のロジックによって管理されます。元々Pythonではすべてのインスタンス属性は直接アクセス可能でしたが、カプセル化をサポートするためには、クラスのインターフェースを変更せずにデータへのアクセスを制御するメカニズムが必要でした。
歴史:
初期のPythonバージョンでは、属性へのアクセス制限を明示的に制御するメカニズムはありませんでした。カプセル化の問題は、慣習(例えば、アンダースコア)を通じて解決されましたが、値の保存やバリデーションのロジックに変更を加えると、互換性が失われることがありました。デコレーター@propertyが登場したことで、メソッドを属性として宣言し、ゲッター、セッター、デリタを付けることができ、従来のクラスインターフェースを保持することができました。
問題:
後でフィールドのバリデーションや値の計算ロジックを追加する必要がある場合、インターフェース全体を再構築するのは非常にコストがかかります—変数へのすべてのアクセス場所を変更する必要があります。この際、フィールドの内部実装の可視性はクラスの外部消費者に公開されるべきではありません。@propertyは、簡単に「仮想化」されたアクセスを可能にします。
解決策:
@propertyは「インターフェースを保護したゲッター/セッター」のパターンを実装しており、計算可能なプロパティを追加したり、クライアントコードを変更せずに値の設定を制御できます。
コード例:
class Temperature: def __init__(self, celsius): self._celsius = celsius @property def celsius(self): return self._celsius @celsius.setter def celsius(self, value): if value < -273.15: raise ValueError("絶対零度未満の温度!") self._celsius = value @property def fahrenheit(self): return self._celsius * 9 / 5 + 32 # 使用例 obj = Temperature(25) print(obj.celsius) # 25 print(obj.fahrenheit) # 77.0 obj.celsius = -300 # ValueError
主要な特徴:
プロパティをクラスの任意のフィールドに追加することは常に可能であり、後方互換性を損なわないか?
いいえ。属性がパブリックであり、それを通常の変数として使用していた場合、プロパティへの置き換えは透明に行うことができます。しかし、どこかで__dict__を介してアクセスがあったり、type(obj.attr)による型チェックが行われた場合、予期しない動作が発生する可能性があります。
プロパティのゲッターなしにセッターのみを宣言することはできますか?
いいえ、最初にゲッター(@propertyを持つメソッド)が必要です。さもなければ、Pythonはセッターを「バインド」するプロパティがわかりません。
デコレーター@property/@setter/@deleterの順序はどうなっているか、なぜか?
常に最初に@propertyを書きます(これはプロパティオブジェクトを作成します)、その後、メソッド名を介して@<name>.setterと@<name>.deleterを書きます(これらは以前に作成されたプロパティを補完します)。
class A: @property def value(self): ... @value.setter def value(self, v): ...
クラスのフィールドがパブリックだったが、パブリックAPIのリリース後にプロパティを介して計算可能なプロパティを追加した。その結果、古いコードで属性へのアクセスが暗黙的に動作を変更したり、エラーを引き起こすことがある。
利点:
欠点:
フィールドは設計時にプライベートとして定義され、ユーザーはメソッド/プロパティを介してのみ作業する。将来的にプロパティへの新しいロジックの追加は、クライアントインターフェースを変更することなく行われた。
利点:
欠点: