Python での getter/setter の書き方 3 選

## まえがき

私が資格取得もかねて Python の基礎知識を見直していたところ、Python の getter/setter にはいろいろ書き方があることを知りました。業務で 1 年半 Python を書いてきて「もう使いこなしているぞ」という段階においてです。

プログラミング経験者で他の言語を習得しようとするときには、概念だけを知っている状態でとりあえず実装してみようとなる人もいますが、このときに、習得しようとしている言語にもっと便利な書き方があることに気付かず、すでに知っている言語と同じような書き方で終わってしまうことがあります。

備忘録を兼ねつつ、Python を学習中の方にも、すでに習得しているという方にも知っていてほしいと思ったので、今回の記事を書くことにします。

## 前提

この記事では、getter/setter の概念は習得済みであるとします。  
deleter については省略します。  
また、getter/setter の是非論については扱いません。

## Python での getter/setter の書き方

:::note info
今回、私が知っている 3 通りの方法をご紹介しますが、言語仕様に即した他の書き方がもしありましたら、コメントでお寄せいただけると幸いです。
:::

今回は、本の価格をプロパティに持つ BookPrice クラスを作成します。

### get_\* や set_\* といったメソッドを用意する

おそらく最も知られている方法かと思います。

`get_price` を getter、`set_price` を setter のメソッドとします。  
プロパティ名を `__price` のように `__`（アンダースコア 2 つ）で始めるとプライベートになります。

```python:sample1.py
from typing import Optional


class BookPrice:
    def __init__(self) -> None:
        self.__price: Optional[int] = None
    
    def get_price(self) -> int:
        print('getter called')
        return self.__price
    
    def set_price(self, value: int) -> None:
        print('setter called')
        self.__price = value

book_price = BookPrice()
book_price.set_price(1200)
print(book_price.get_price())
```

価格を取得したいときは `get_price` メソッドを実行し、価格を設定したい場合は `set_price` の引数に価格を指定して実行します。

実行すると以下のようになります。

```text
$ python sample1.py
setter called
getter called
1200
```

### property クラスをインスタンス化する

https://docs.python.org/3/library/functions.html#property

Python で property について調べると最初に出てくる書き方ですが、私はこの記事を書いていて初めて知りました。

```python:sample2.py
from typing import Optional


class BookPrice:
    def __init__(self) -> None:
        self.__price: Optional[int] = None
    
    def get_price(self) -> int:
        print('getter called')
        return self.__price
    
    def set_price(self, value: int) -> None:
        print('setter called')
        self.__price = value
    
    price = property(fget=get_price, fset=set_price, fdel=None, doc='本の価格')

book_price = BookPrice()
book_price.price = 1200
print(book_price.price)
```

`get_price` と `set_price` を用意するところまでは先ほどの方法と同じですが、最後に `property` クラスをインスタンス化する形でプロパティを設定します。  
getter/setter/deleter およびプロパティの説明を指定します。

価格を取得したいときは `book_price.price` で、価格を設定したい場合は `book_price.price = 1200` のようにします。  
呼び出し側では、`price` というプロパティに対して値を直接取得したり設定したりしているように見えます。

実行すると以下のようになります。

```text
$ python sample2.py
setter called
getter called
1200
```

### property クラスをデコレータに使用する

https://docs.python.org/3/library/functions.html#property.getter

私は Python の資格の勉強で知りました。

```python:sample3.py
from typing import Optional


class BookPrice:
    def __init__(self) -> None:
        self.__price: Optional[int] = None
    
    @property
    def price(self) -> int:
        """本の価格"""
        print('getter called')
        return self.__price
    
    @price.setter
    def price(self, value: int) -> None:
        print('setter called')
        self.__price = value

book_price = BookPrice()
book_price.price = 1200
print(book_price.price)
```

呼び出し方は先ほどの方法と同じですが、クラスの実装が少し異なります。  
getter には `@property` デコレータを設定し、メソッド名はプロパティ名の `price` にします。setter には `@{プロパティ名}.setter` デコレータを設定し、メソッド名は getter と同じで `price` にします。

同じ名前のメソッドが複数存在するため、違和感がありますが、これで問題ないようです。  
また、setter のデコレータで `price` なんて補完されるのかよ、という感じですが、VSCode および PyCharm においては、getter が存在することでプロパティ名が自動補完できるようなので、開発者体験としても問題ありません。

実行すると以下のようになります。

```text
$ python sample3.py
setter called
getter called
1200
```

## まとめ

Python の getter/setter は、調べてみると方法が 3 通りあったわけですが、こんなに方法があることに気付くには、 **実装方法を知っていても改めて調べてみる** ことが必要でした。  
実務においても、要件を満たせばそれでよいのではなく、時間の許す限り、より良い書き方を求めていきたいと思いました。