@property:ゲッター・セッターを Pythonic に書く
Python でクラスを書くとき、インスタンス変数に直接アクセスするのが基本です。しかし、値の取得や設定に処理を挟みたい場面がある。そんなとき @property を使います。
ゲッター・セッターの古典的な書き方
Java や C# では、フィールドを private にして getter/setter メソッドを定義するのが一般的です。Python でも同じように書けますが、冗長になりがち。
class User:
def __init__(self, name):
self._name = name
def get_name(self):
return self._name
def set_name(self, value):
if not value:
raise ValueError("名前は空にできません")
self._name = value
user = User("Alice")
print(user.get_name()) # Alice
user.set_name("Bob")
get_name() や set_name() を呼ぶのは煩わしい。属性アクセスのように user.name と書きたいところです。
@property で属性のように扱う
@property を使えば、メソッドを属性のように見せかけられます。
class User:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
user = User("Alice")
print(user.name) # Alice(メソッド呼び出しではなく属性アクセス)
user.name と書くだけで、裏では name() メソッドが呼ばれる。これがプロパティの基本。
setter を定義する
読み取り専用では困る場合、@プロパティ名.setter で書き込みを許可します。
class User:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
@name.setter
def name(self, value):
if not value:
raise ValueError("名前は空にできません")
self._name = value
user = User("Alice")
user.name = "Bob" # setter が呼ばれる
print(user.name) # Bob
user.name = "" # ValueError
user.get_name() や user.set_name("Bob") のように呼び出す。明示的だが冗長。
user.name で取得、user.name = "Bob" で設定。属性アクセスと同じ見た目で、裏でメソッドが動く。
バリデーションを挟む
setter の本領はバリデーションにあります。値を設定するたびに検証ロジックを通せる。
class Temperature:
def __init__(self, celsius):
self.celsius = celsius # setter を通る
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("絶対零度より低い温度は存在しません")
self._celsius = value
temp = Temperature(25)
temp.celsius = -300 # ValueError
__init__ 内の self.celsius = celsius も setter を通るため、初期化時点でバリデーションが効く。これは重要なポイント。
計算プロパティ
プロパティは保存された値を返すだけでなく、その場で計算した値を返すこともできます。
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
@property
def area(self):
return self.width * self.height
rect = Rectangle(3, 4)
print(rect.area) # 12
rect.width = 5
print(rect.area) # 20(再計算される)
area は保存されていない。アクセスするたびに width * height を計算して返す。setter を定義しなければ読み取り専用になります。
deleter を定義する
まれに使いますが、del 文に対応する deleter も定義できます。
class User:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value
@name.deleter
def name(self):
print("名前を削除します")
self._name = None
user = User("Alice")
del user.name # "名前を削除します"
print(user.name) # None
リソースのクリーンアップやキャッシュの無効化に使うことがある。ただし、明示的なメソッド(clear_name() など)の方がわかりやすい場合も多い。
なぜ最初から @property を使わないのか
Python では「まず単純な属性アクセスで始める」のが推奨されます。
# 最初はシンプルに
class User:
def __init__(self, name):
self.name = name # 直接アクセス
user = User("Alice")
print(user.name)
user.name = "Bob"
バリデーションや計算が必要になったら、そのとき @property に変える。呼び出し側のコードは変更不要。これが Python 流のリファクタリング。
「今は必要ない」なら書かない。将来の拡張性のために最初から getter/setter を書くのは過剰設計。
@property のおかげで、属性アクセスのインターフェースを保ったまま内部実装を変えられる。だから最初はシンプルに書ける。
property() 関数による定義
デコレータを使わず、property() 関数で定義する方法もあります。
class User:
def __init__(self, name):
self._name = name
def _get_name(self):
return self._name
def _set_name(self, value):
self._name = value
name = property(_get_name, _set_name)
古いコードで見かけることがありますが、デコレータ構文の方が読みやすい。今はあまり使われません。
まとめ
@property は Python らしいカプセル化の手段。メソッドを属性のように見せかけることで、シンプルなインターフェースを保ちながら内部処理を柔軟に変えられます。