0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Pythonでデコレートする関数の引数情報を伝搬させる

Last updated at Posted at 2021-01-31

対象読者

  • pythonでデコレータを作成したことがある方
  • pythonで型注釈を活用したコードを書いている方
  • python3.5以上(多分3.5以上で有効なコードです)

はじめに

pythonのデコレータはよく利用すると思いますが、デコレートしてしまうと、元関数の引数情報が失われてしまったり、一体何が返っているのか分からないというデメリットがあります。

最近では型注釈が発達してきて、この問題を解決できるようになってきているので、実装例を紹介します。

確認はVSCode(Pylance + mypy)でやりましたが、IDEや環境次第では型情報の認識の仕方が異なるかもしれません。

実装例

まず、基本形から。
次のように、型変数を定義して、それを戻り値の型にしてあげれば関数の情報を伝搬できます。

from typing import Callable, TypeVar
F = TypeVar("F", bound=Callable)

def decorator(func: F) -> F:
  ...
  return func

@decorator
def func(num1, num2):
  return num1 + num2

次に、クラスでデコレータを作成する方法を紹介します。
ただし、この方法は一般的な方法じゃないので、こういうことができるよって知識レベルに留めておきましょう。

ご利用は、自己責任でお願いします。

from functools import update_wrapper
from typing import Any, Callable, Generic, TypeVar

F = TypeVar("F", bound=Callable)


class FuncWrapper(Generic[F]):
    def __init__(self, func: F):
        update_wrapper(self, func)  # 元の関数を__wrapped__に保持する。など、ラッパー作成のヘルパー関数
        # update_wrapperだけでは不十分な情報を保持する
        self.__code__ = func.__code__
        self.__defaults__ = func.__defaults__
        self.__kwdefaults__ = func.__kwdefaults__

    def __getattr__(self, name):
        return getattr(self.__wrapped__, name)  # 未定義の属性は元の関数を参照しにいく

    @property
    def __call__(self) -> F:
        return self.__wrapped__

    @property
    def __class__(self):
        return self.__wrapped__.__class__  # とりあえず、返すクラスも擬態する

@FuncWrapper
def func(num1, num2):
  return num1 + num2

Genericで型変数を保持し、それをどこかで利用します。

キモは、__call__をメソッドじゃなくて、プロパティにしてしまったことですね。
関数の引数と戻り値情報を取り出すのは至難の業ですし、取り出したとしても__docstring__が失われてしまうので、これが一番簡単だと思います。

なお、FuncWrapperをinspectモジュールで一通り分析しており、確認した範囲では元関数と同じ判定結果となっています。
もちろん、擬態しているだけなので、何かしら副作用が生じる可能性はあります。

pep612 Parameter Specification Variables

デコレータの型注釈でみんな悪戦苦闘しているようで、引数情報をもっと簡単に扱うための機能導入が検討されています。

さいごに

結果だけ見れば簡単なことでしたが、__call__をプロパティにするという発想が中々できず、結構時間を費やしました。。。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?