@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
getter/setter メソッド

user.get_name()user.set_name("Bob") のように呼び出す。明示的だが冗長。

@property

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 流のリファクタリング。

YAGNI 原則

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

最初は単純な属性アクセスで書く
バリデーションや計算が必要になったら @property に変える
呼び出し側のコードは変更不要