pythonのfunctoolsについてまとめてみた

今回のお題
-
今回は、pythonのfunctoolsというモジュールについてまとめます。

元々はfunctools.wrapについて知りたかっただけなのですが、その過程で他の関数についてもいくつか勉強せざるを得なかったので記念に残しておきます。

目次
-

- functoolsモジュールとは
- functools.partial
- functools.update_wrapper
- functools.wrap

functoolsモジュールとは
-
functoolsモジュールとは、「ある関数を操作したり、またそれによって新たな関数を取得したりできる関数をまとめたモジュール」のことです。

つまり、このモジュールをimportすることで関数に対してさまざまな操作を加えることができるようになるのですね。

言葉だけで説明してもわかりづらいと思うので、早速具体例を紹介していきます。

なお、今後の本記事のソースコードにおいては全てfunctoolsモジュールのimportは省略されています。
その点にはご注意ください。

functools.partial
-
functools.partialは、ある関数の引数やキーワード引数の一部を固定して新しい関数を作り出すことができます。

```python:functools.partialの一例
def info(name, age):
  print(f"{name}さんは{age}歳です。")

func_1 = functools.partial(info, "John")

func_1(30)
# 出力：Johnさんは30歳です。
```
上記の例では、partialの第一引数であるinfo関数に対してpartialの第二引数である"John"が渡されています。

これによりinfo関数の第一引数がJohnで固定された新しい関数が作られ、それがfunc_1に代入されています。

なお、上記の例でinfoの第一引数以外（例えばage)に引数を渡したい場合には、キーワード引数を用いることになります。

```python
def info(name, age):
  print(f"{name}さんは{age}歳です。")

func_1 = functools.partial(info, age=30)

func_1("John")
# 出力 Johnさんは30歳です。
```

functools.update_wrapper
-
functools.update_wrapperは、「ある関数の属性を別の関数の属性で上書きする」ためのものです。

属性とは関数名や関数のモジュール名などのことで、クラスやモジュールなどにも属性が設定されています。

```python:属性値の例
print(print.__name__)
# 出力：print
# printという関数の関数名はprint

print(print.__module__)
# 出力：builtins
# printはビルトイン関数

class Hoge:
  pass

print(Hoge.__name__)
# 出力：Hoge
# Hogeクラスのクラス名はHoge
```

では、update_wrapperで属性値を上書きするとどうなるでしょうか。

```python
def hoge():
  pass

def fuga():
  pass

# そのまま出力
print(hoge.__name__) # 出力：hoge

# 上書きして出力
print(update_wrapper(hoge, fuga).__name__) # 出力：fuga
```

ちなみに、上書きされる要素は以下の通りであり、逆にこの部分を引数で指定することでアップデート内容を操作できます。

```python
WRAPPER_ASSIGNMENTS（第３引数)  = ('__module__', '__name__', '__qualname__', '__doc__', '__annotations__')
WRAPPER_UPDATES(第４引数） = ('__dict__',)
```
```python:引数操作の例
def hoge():
  pass

def fuga():
  pass

# そのまま出力
print(hoge.__name__) # 出力：hoge

# 引数を限定した上書き
print(update_wrapper(hoge, fuga, "__doc__").__name__) # 出力：hoge
```

また、update_wrapperで上書きした内容は基本的にその場限りではなく持続します。

```python
def hoge():
  pass

def fuga():
  pass

# そのまま出力
print(hoge.__name__) # ここでは出力：hoge

# 上書き
print(update_wrapper(hoge, fuga).__name__) # ここで出力：fugaに上書き

print(hoge.__name__) # ここでも出力：fugaのまま
```

なお、update_wrapperを用いた上書きは、主にで主にデコレータによる属性値の上書きを防ぐ目的で使用されるようです。

デコレータ、及びデコレータによる属性値の上書きについては今回は省略するので、各自でお調べいただくようにお願いいたします。

functools.wraps
-
この関数については、上記のupdate_wrapperを踏まえた上で公式ドキュメント（の日本語訳）を引用します。

>これはラッパー関数を定義するときに update_wrapper() を関数デコレータとして呼び出す便宜関数です。

つまりつまりあるデコレータを定義する際にこのfunctools.wrapsをデコレータとして用いておくことで、デコレータによる属性値の上書きを防ぐことができるのですね。

```python
import functools

def deco(f):
  @functools.wraps(f, "__name__")
  def wrapper(*args, **kwargs):
    """test"""
    return f(*args, **kwargs)
  return wrapper

@deco
def hoge():
  """sample"""
  pass

print(hoge.__name__) 
# 出力：hoge
# __name__はwrapsで指定しているのでそのまま
print(hoge.__doc__)
# 出力：test
# __doc__はwrapsで指定していないので上書きされる
```
今回は例として属性値を指定する方法を取りましたが、指定しなかった場合の初期値はupdate_wrapperと同じです。

また、公式にはデコレータ定義時に使用、とありましたが、属性値を上書きするデコレータとしても一応使えます。

```python
import functools

@functools.wraps(sum)
def hoge():
  pass

print(hoge.__name__) # 出力：sum
```

終わりに
-
以上でfunctoolsについての解説を終わります。

今回はfunctools.wrapsを理解する上で必要になった箇所をまとめただけですが、functoolsには他にもたくさんの関数がありますので、機会があればそちらについてもまとめたいと思います。