【中級者への道】Pythonのプロパティを理解する


# リンク

[【中級者への道】記事リンクまとめ](https://qiita.com/ganariya/items/fb3f38c2f4a35d1ee2e8)

# はじめに

[Python チュートリアル](https://docs.python.org/ja/3/tutorial/)で取り扱われていない「**プロパティ**」についてのまとめです。

僕は現在Pythonで研究のコードを書いていますが、如何せん適当に書いてしまっていたため、グチャグチャのギタギタになってしまいました。
そこで、Pythonの中級者を目指し、きちんと誰が読んでも分かるようなコードを目指そうと思います。

# プロパティって？

そもそもプロパティってなんでしょうか。
プロパティは、`オブジェクトの属性・性質データ`を表します。

...よく分かりませんね。

例えば、二次元のシューティングゲームで使用するクラスPlayerについて考えてみます。

Playerインスタンスは、シューティングゲームで移動したり、描画をするために

- 横座標`x`
- 縦座標`y`

の情報を持っています。

このように、各インスタンスが持つ属性・性質のデータを**プロパティ**と呼びます。

# Playerを実装してみる

とりあえず、通常のPlayerクラスを実装してみます。

各座標と移動するmove関数を用意しましょう。

今回のPlayerは、$x, y$ともに$0{\leqq}x, y{\leqq}500$までを移動可能範囲とします。

```python
class Player:

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f'(x = {self.x}, y = {self.y})'

    def move(self, dx, dy):
        self.x += dx
        self.y += dy


"""
(x = 100, y = 100)
"""
player = Player(100, 100)
print(player)

"""
(x = 0, y = -100)
"""
player.move(-100, -200)
print(player)
```


上のソースコードは普通に動きますし、シンプルです。
しかし、オブジェクト指向という点で、上記のソースコードには問題点がいくつか存在します。

## 問題点1 カプセル化

上記のクラスは、**直接**プロパティ変数をいじることができてしまいます。

これはあまり良い状態ではありません。
不都合な変数の設定・変更の可能性が存在するためです。

また、メソッドによって、状態が変化する　というのがオブジェクト指向の基本です。

## 問題点2 範囲外

このPlayerクラスは、$x, y$ともに$0{\leqq}x, y{\leqq}500$の中で動いてほしいです。
しかし、上記のmove関数によって、範囲外に出てしまいました。

なんとかして、変数の設定と同時に、範囲内に収める処理が設定できないでしょうか？


# プロパティ

そこで、Pythonの記法プロパティを利用します。

以下のようにソースコードを書き換えてみましょう。

```py
class Player:
    MIN_X = 0
    MAX_X = 500

    MIN_Y = 0
    MAX_Y = 500

    def __init__(self, x, y):
        self._x = x
        self._y = y

    def __repr__(self):
        return f'(x = {self.x}, y = {self.y})'

    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, dx):
        self._x += dx
        self._x = max(0, min(self.MAX_X, self._x))

    @property
    def y(self):
        return self._y

    @y.setter
    def y(self, dy):
        self._y += dy
        self._y = max(0, min(self.MAX_Y, self._y))

    def move(self, dx, dy):
        self.x += dx
        self.y += dy


player = Player(100, 100)

player.move(-200, -200)

"""
(x = 0, y = 0)
"""
print(player)

```

一つ一つ中身を見ていきます。

## __init__

initでは、変数の初期化を_xで行っています。
内部のみで扱いたい変数は_xなど、アンダースコアを使って表現することが多いようです。

## ＠property

```py
@property
def x(self):
  return self._x

@property
def 外部公開変数名(self):
  return 内部変数
```

次に、`x(self)`を見てみます。
これは、メソッドのように機能していますが、クラスの外に対しては`ただの変数`のように見せることができます。
これは**値を外部に返す**ためのものです。

このようになるのは、この関数定義の上に`@property`という**デコレータ**という記法を利用しているためです。
デコレータに関しては、また今度記事を書こうと思います。

とりあえず、@propertyというキーワードをメソッドにつけることで、そのメソッド名でクラス外部から値を取得できます。

## @外部公開変数名.setter

```py
    @x.setter
    def x(self, dx):
        self._x += dx
        self._x = max(0, min(self.MAX_X, self._x))

    @外部公開変数名.setter
    def 外部公開変数名(self, dx):
        self.内部変数 += dx
        self.内部変数 = max(0, min(self.MAX_X, self._x))
```

先程の@propertyは、クラスの外から値を取得して利用するためのキーワードでした。

ということは、外から値を与えて内部に値を`set`するキーワードも存在します。

`@外部公開変数名.setter`というキーワードを利用します。
これによって、通常のような記法で内部に値を設定できます。


# プロパティの嬉しさ

プロパティの嬉しさはなんでしょうか？

実は、Pythonのプロパティは他の言語と異なる形をしています。
もともと自分はC#やC++、Javaを触っていたため、かなりびっくり・違和感を感じました。

Pythonのプロパティは、

- 外部にとっては、**変数の見た目そのまま**で利用できる
- 内部にとっては、大切な変数を隠して、追加処理を記述できる

という嬉しさがあります。

他の言語では、

```java
player.setValueA(50);
```

のように、関数・メソッドの形で記述しなければならず、言ってしまえば**冗長**でした。


Pythonではデコレータという動的言語ならではの言語仕様を利用することで、
外部にうまく変数のようにメソッドを公開
内部では、追加処理を書くことができます。


ただ、setter内で複雑な内容を書きすぎるとバグになる・意識しなければいけなくなるため、軽い内容にするべきかもしれません。

# 最後に

プロパティについてまとめてみました！
初心者のため、間違っている部分が多いかと思います。
ぜひ、アドバイス・ご意見をいただけると嬉しいです！