Pythonの3大ビルトインデコレータ

Pythonのビルトイン関数のうち、デコレータは3つです。それは@staticmethod、@classmethod、@propertyです。

[Built-in Functions — Python 3.9.2 documentation](https://docs.python.org/3/library/functions.html)

ちなみにPythonの標準ライブラリに含まれるデコレータに関しては、[有志の方がまとめているレポジトリ](https://github.com/lord63/awesome-python-decorator)があります。

## [@staticmethod](https://docs.python.org/3/library/functions.html#staticmethod)、 [@classmethod](https://docs.python.org/3/library/functions.html#classmethod)

[@staticmethod](https://docs.python.org/3/library/functions.html#staticmethod)はインスタンスメソッドをスタティックメソッドに変えてくれるデコレータです。通常インスタンスメソッドは第一引数にselfが入りますが、スタティックメソッドでは第一引数にselfが入りません。

スタティックメソッドはselfにアクセスしなくてもいい場合に用います。

[@classmethod](https://docs.python.org/3/library/functions.html#classmethod)はインスタンスメソッドをクラスメソッドに変えてくれるデコレータです。クラスメソッドは第一引数にクラスオブジェクトが入ります。

クラス変数や継承先のクラスの情報を使いたい場合に用います。

インスタンスメソッド、スタティックメソッド、クラスメソッドの違いの例をコードとして以下に書かせていただきました。

```Python
class C:
    class_variable = "CV"

    def __init__(self):
        self.instance_variable = "IV"

    def instance_method(self):
        print("An instance method was called.")
        print("instance_variable is {}".format(self.instance_variable))

    @staticmethod
    def static_method():
        print("A static method was called.")

    @classmethod
    def class_method(cls):
        print("A class method was called")
        print("class_variable is {}".format(cls.class_variable))
```

実行例は以下のようになります。

```Python
instance = C()

print("=====instance method=====")
instance.instance_method()
print()

print("=====static method=====")
C.static_method()
print()

instance.static_method()
print()

print("=====class method=====")
C.class_method()
print()

instance.class_method()
```

実行結果は以下のようになります。

```
=====instance method=====
An instance method was called.
instance_variable is IV

=====static method=====
A static method was called.

A static method was called.

=====class method=====
A class method was called
class_variable is CV

A class method was called
class_variable is CV
```

## [@property](https://docs.python.org/3/library/functions.html#property)

Pythonではインスタンス変数に直接アクセスできてしまいます。そのためインスタンスが望まない変数を持ってしまうことがあります。この問題に対処できるのが[@property](https://docs.python.org/3/library/functions.html#property)です。

例えばpriceというインスタンス変数は基本的に0より大きい数字であるべきです。しかしpriceが負の数であっても例外が投げられないという問題を考えることができます。

```Python
product.price = -200  # 例外を投げたい
```

例として以下のようなコードを書かせていただきました。

```Python
class Product:
    def __init__(self, price):
        self._price = price

    @property
    def price(self):
        print("Getter is called.")
        return self._price

    @price.setter
    def price(self, price):
        print("Setter is called.")
        if price < 0:
            raise ValueError("price should be 0 or more than 0")
        self._price = price

    @price.deleter
    def price(self):
        print("Deleter is called.")
        self._price = None
```

実行例は以下のようになります。

```Python
product = Product(100)

print("=====getter=====")
product.price
print()

print("=====setter=====")
product.price = 200
print()

print("=====deleter=====")
del product.price
print()

print("=====invalid argument=====")
product.price = -200
```

実行結果は以下のようになります。

```
=====getter=====
Getter is called.

=====setter=====
Setter is called.

=====deleter=====
Deleter is called.

=====invalid argument=====
Setter is called.
Traceback (most recent call last):
  File "property.py", line 38, in <module>
    product.price = -200
  File "property.py", line 14, in price
    raise ValueError("price should be 0 or more than 0")
ValueError: price should be 0 or more than 0
```

## 参考

https://qiita.com/msrks/items/fdc9afd12effc2cba1bc