【Python】プロパティの便利な使い方5選

# プロパティとは

Pythonには「プロパティ(property)」という、クラスの属性（フィールド）のアクセス方法を
カスタマイズするための仕組みです。

プロパティを使うことで、クラスの内部の実装を隠蔽し、属性へのアクセスや変更を制御できます。
つまり、クラスの変数について考えなくても属性の制御ができるようになるのです。

実際に見てみたほうが分かりやすいと思います。

```py
class Person:
    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

    @name.deleter
    def name(self):
        raise AttributeError("名前は削除できません")

# クラスの使い方
person = Person("Alice")
print(person.name)  # getterが呼ばれる -> "Alice"
person.name = "Bob"  # setterが呼ばれる
print(person.name)  # getterが呼ばれる -> "Bob"
del person.name  # deleterが呼ばれる -> AttributeError
```

このコードではプロパティは3つの例があり、それぞれ以下のように使うことができます。
```
取得: person.name は @property デコレータが付いた name メソッドを呼び出します。
設定: person.name = "Bob" は @name.setter デコレータが付いた name メソッドを呼び出します。
削除: del person.name は @name.deleter デコレータが付いた name メソッドを呼び出します。
```

# 便利な使い方例紹介


## 1. データの検証

プロパティのgetter/setter内で、取得・設定する値に対して検証を行うことができます。
例えば、以下のように、負の値が設定されないように制限することができます。

```py
class Person:
  def __init__(self, age):
    self._age = age

  @property
  def age(self):
    return self._age

  @age.setter
  def age(self, value):
    if value < 0:
      raise ValueError("年齢は負の値を設定できません。")
    self._age = value
```
この例では、ageプロパティのsetter内で、設定値が0未満の場合はエラーを発生させています。
データの整合性を保つ際に便利な使い方だと思います。

## 2. 計算ロジックのカプセル化

複雑な計算ロジックを、プロパティのgetter/setter内にカプセル化することができます。
例えば、円周計算を行うプロパティを以下のように定義できます。

```py
class Circle:
  def __init__(self, radius):
    self._radius = radius

  @property
  def circumference(self):
    return 2 * math.pi * self._radius

  @circumference.setter
  def circumference(self, value):
    raise NotImplementedError("円周は直接設定できません。")
```

この例では、`circumference`プロパティのgetter内で円周を計算し、setterでは設定不可としています。
このように、内部ロジックを隠蔽し、インターフェースをシンプルに保つことに使えます。
VScodeとかのサジェストも見やすくなるのでコーディングがしやくなって便利です。

## 3. ログを出しまくる

デコレータを利用して、すべてのプロパティにログを出力するような仕組みを作ることができます。

```py
def log_property(cls):
  for name, value in cls.__dict__.items():
    if isinstance(value, property):
      @property
      def wrapper(self):
        print(f"プロパティ '{name}' を取得します。")
        return value.fget(self)

      setattr(cls, name, wrapper)
  return cls

@log_property
class Person:
  def __init__(self, name, age):
    self.name = name
    self.age = age
```

setterでこれを使えば、値の状態監視もできます。

```py
class Person:
  def __init__(self, name):
    self._name = name

  @property
  def name(self):
    return self._name

  @name.setter
  def name(self, new_name):
    if self._name != new_name:
      print(f"名前が '{self._name}' から '{new_name}' に変更されました。")
    self._name = new_name
```

## 4. 仮想プロパティ

存在しない属性に対して、プロパティのようにアクセスできるようにすることができます。

```py
class MyList(list):
  @property
  def length(self):
    return len(self)
```
`MyList`クラスは通常のリストのサブクラスとして定義されており、
lengthプロパティを追加することで、リストの要素数にアクセスできるようにしています。

## 5. 関連プロパティの自動更新
関連するプロパティ間の整合性を保つために、
一方の値を変更すると自動的に他方の値を更新することができます。

```py
class Address:
  def __init__(self, prefecture, city, address, postcode):
    self.prefecture = prefecture
    self.city = city
    self.address = address
    self._postcode = postcode

  @property
  def postcode(self):
    return self._postcode

  @postcode.setter
  def postcode(self, value):
    self._postcode = value
    # 郵便番号変更時に住所を更新する処理
    self.update_address_from_postcode()

  def update_address_from_postcode(self):
    # 実際の処理は省略
    pass
```

この例では、`postcode`プロパティのsetter内で、郵便番号が変更された際に
`update_address_from_postcode`メソッドを呼び出し、住所を更新する処理を実行しています。