Pythonのdecoratorについて

## 著者紹介など

初めまして! 
sai-haiと申します．よろしくお願いします！

この記事では，Python の decorator について調べたことをまとめました．はじめての記事で至らない点が多いかと思います．間違っている点はコメントなどで指摘していただけると嬉しいです．

## 実行環境
Python3.11.2

## decorator のお気持ち理解
decorator とは @ を用いた表現です。後ろに続いて定義した関数・メソッド・クラスを装飾することができます。
~~~python
@a # decorator
@b # decorator
def c:
    pass

# c = a(b(c))

@D # decorator
class E:

# E = D(E)
~~~
この時、a, b, A, B はどれも呼び出し可能 (callable) なオブジェクトであるとします。decorator は上のように任意の個数で続けて記述することが可能です。その場合、入れ子になります。( decorator 同士の間には必ず改行が必要です。)

## python における decorator の位置付け・動機
Python 2.4 における[PEP318 - Decorators for Functions and Methods](https://peps.python.org/pep-0318/)を参照します。以下 [Abstract](https://peps.python.org/pep-0318/#abstract) の和訳です。
>関数やメソッドを変換する現在の方法（例えば、クラスや静的メソッドとして宣言する）は厄介で、理解しにくいコードにつながる可能性があります。理想的には、これらの変換は、宣言自体が行われるコード内の同じ箇所で行われるべきです。このPEPでは、関数またはメソッド宣言の変換のための新しい構文を導入します。

具体的なdecoratorの使用意図は、
~~~python
def foo(cls):
    pass
foo = synchronized(lock)(foo)
foo = classmethod(foo)
~~~
のように、関数 foo が３回宣言されている pythonic でないコードを、
~~~python
@classmethod
@synchronized(lock)
def foo(cls):
    pass
~~~
のように簡潔に表現することです。(出典：[Motivation](https://peps.python.org/pep-0318/#motivation)) 

Python 2.4 では、decorator は関数とメソッドにのみ適用可能でしたが、[PEP3129 – Class Decorators](https://peps.python.org/pep-3129/) にある通り、Python 3.0 において、クラスについても適用可能になりました。

## 文法について
Python 3.11.2 Documentation における、[Function definitions](https://docs.python.org/3.11/reference/compound_stmts.html#function-definitions), [Class definitions](https://docs.python.org/3/reference/compound_stmts.html#class) を参照すると、関数・クラスの定義はそれぞれ

>funcdef                   ::=  [decorators] "def" funcname "(" [parameter_list] ")" ["->" expression] ":" suite
>classdef    ::=  [decorators] "class" classname [inheritance] ":" suite

です。このうち decorators は decorator が一つ以上続いたものです。

> decorators                ::=  decorator+
decorator                 ::=  "@" assignment_expression NEWLINE

## decorator の使用法
### 関数・メソッドの装飾
#### 基本の例
~~~python  
def decorate(func):
    def wrapper(*arg,**kwarg):
        print("===1")
        func(*arg,**kwarg)
        print("===2")
    return wrapper

@decorate # 直後にdefされたものを引数にとって装飾
def greet():
    print("Hello Qiita!")
# greet = decorate(greet)

@decorate # 直後にdefされたものを引数にとって装飾. 再利用可能!!
def greet2():
    print("Hi Qiita!")
# greet2 = decorate(greet2)

greet()
# 出力
# ===1
# Hello Qiita!
# ===2

greet2()
# 出力
# ===1
# Hi Qiita!
# ===2
~~~  
#### decorator をネストした場合
ネストした場合は、次のように入れ子になります。
~~~python
def deco1(func):
    def wrapper1(*arg,**kwarg):
        print("===1")
        func(*arg,**kwarg)
        print("===2")
    return wrapper1

def deco2(func):
    def wrapper2(*arg,**kwarg):
        print("===3")
        func(*arg,**kwarg)
        print("===4")
    return wrapper2

@deco1
@deco2
def greet():
    print("Hello Qiita!")
# greet = deco1(deco2(greet))

greet() 
# 出力
# ===1
# ===3
# Hello Qiita!
# ===4
# ===2
~~~  
#### decorator によって装飾された関数が引数を持つ場合
続いて，decorator によって装飾された関数が引数を持つ場合は次のようになります。  
~~~python  
def decorate1(func):
    def wrapper1(*arg,**kwarg):
        print("===1")
        func(*arg,**kwarg)
        print("===2")
    return wrapper1

@decorate1
def greet(value):
    print(value)
# greet = decorate1(greet)

greet("Hello World!") # wrapper1("Hello World!")
# 出力
# ===1
# Hello World!
# ===2
~~~  

#### クラスによって関数・メソッドを装飾する場合
装飾した定義の時点で、装飾元のクラスの \_\_init\_\_ 関数が呼ばれていることに留意してください。

~~~python  
import functools

class CountCalls:
    def __init__(self, func):
        functools.update_wrapper(self, func)
        self.func = func
        self.num_calls = 0

    def __call__(self, *args, **kwargs):
        self.num_calls += 1
        print(f"Call {self.num_calls} of {self.func.__name__!r}")
        return self.func(*args, **kwargs)

@CountCalls
def say_whee(*arg):
    print("Whee!",*args)
# say_whee = CountCalls(say_whee)
# この時点で、say_whee は クラス CountCalls のインスタンス
# すでに CountCalls.__init__ が呼ばれている

say_whee(1,2) # __call__ が呼ばれる
# 出力
# Call 1 of 'say_whee'
# Whee! 1 2

say_whee(3,4,5) # __call__ が呼ばれる
# 出力
# Call 2 of 'say_whee'
# Whee! 3 4 5
~~~  
(コードの出典:[Primer on Python Decorators](https://realpython.com/primer-on-python-decorators/#first-class-objects)から改変)  


#### decorator 使用時の注意点！！
上記のコードたちは不親切になっている点がありました．2つ上のコードの場合について，
~~~python  
print(greet.__name__)
# 出力
# wrapper1
~~~
つまり，装飾された greet 関数の名前が decorator の関数の戻り値の関数の名前になってしまいます．そこでfunctoolsをインポートして，以下のようにすると，この点は解決されます。    
~~~python  
import functools

def decorate1(func):
    @functools.wraps(func) # wrapper1 の名前を 引数の func の func.__name__ に変更
    def wrapper1(*arg,**kwarg):
        print("===1")
        func(*arg,**kwarg)
        print("===2")
    # wrapper1 = functools.wraps(func)(wrapper1)
    return wrapper1

@decorate1
def greet(value):
    print(value)

print(greet.__name__) # 出力 greet
~~~
ここでもまた decorator を使っています．

## クラスの装飾 
### 基本の使用例
関数・メソッドの装飾の場合と大きな違いはありません。いくつか具体例を挙げます。

#### dataclass の利用
dataclasses の [dataclass](https://docs.python.org/3/library/dataclasses.html) を利用すると、クラスに使いやすい \_\_init\_\_ , \_\_repr\_\_ , \_\_eq\_\_ が自動生成されます。

~~~python
from dataclasses import dataclass

@dataclass
class Student:
    name: str
    age: int
# Student = dataclass(Student)

student1 = Student("Alice", 19)
student2 = Student("Bob", 21)

print(student1)
print(student2)
print(student1 == student2)

# Student(name='Alice', age=19)
# Student(name='Bob', age=21)
# False
~~~

そのほかにも、classmethod, staticmethod, property などが主に利用されます。

#### シングルトンの作成（実用的ではない）  
シングルトンとは，全く同じインスタンスしか生成できないクラスのことです．  

~~~python  
import functools

def singleton(cls):
    """Make a class a Singleton class (only one instance)"""
    @functools.wraps(cls)  # singletonで返すwrapper_singletonを．引数cls
    def wrapper_singleton(*args, **kwargs):
        if not wrapper_singleton.instance:
            wrapper_singleton.instance = cls(*args, **kwargs)
        return wrapper_singleton.instance
    wrapper_singleton.instance = None
    return wrapper_singleton

@singleton
class TheOne:
    pass
# TheOne = singleton(TheOne)

first_one = TheOne()
another_one = TheOne()

print(id(first_one))
# 出力
# 140094218762280

print(id(another_one))
# 出力
# 140094218762280

print(first_one is another_one)
# 出力
# True
~~~
(コードの出典:[Primer on Python Decorators](https://realpython.com/primer-on-python-decorators/#first-class-objects)から一部改変)   

このように，適当なクラス (TheOne) を，その状態を持つためのクラス (singleton) でデコレートすることで，簡単にシングルトンにすることができました．もちろんこの decorator は再利用可能です．

#### Django の実装の一部
[admin.py](https://github.com/django/django/blob/5fcfe5361e5b8c9738b1ee4c1e9a6f293a7dda40/django/contrib/auth/admin.py#L25) などにおいて、decorator が利用されています。
~~~python
'''
code from:https://github.com/django/django/blob/5fcfe5361e5b8c9738b1ee4c1e9a6f293a7dda40/django/contrib/auth/admin.py#L25
'''
@admin.register(Group)
class GroupAdmin(admin.ModelAdmin):
    # 中略
    def formfield_for_manytomany(self, db_field, request=None, **kwargs):
        # 中略
        return super().formfield_for_manytomany(db_field, request=request, **kwargs)
~~~
## decorator のまとめ    
- decorator とは @ を用いた表現で、後ろに続いて定義した関数・メソッド・クラスを装飾するもの
- @a NEWLINE def b: は b = a(b)　
- クラスで装飾する場合は、装飾されるものの定義のすぐ後ろで \_\_init\_\_ が呼ばれることに注意
- クラスの装飾には、dataclass などが用いられる