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 らしいカプセル化の手段。メソッドを属性のように見せかけることで、シンプルなインターフェースを保ちながら内部処理を柔軟に変えられます。