デコレータは 別の関数を返す関数 でしか御座いませんが、 with
文で使用するコンテキストマネージャの実体は __enter__
及び __exit__
という 二つの dunder methods を持つオブジェクト です。関数を @contextlib.contextmanager
で wrap するのは、あくまでこのコンテキストマネージャ型オブジェクトを 関数から生成することができるデコレータ によって生成している (正確には、それを生成するファクトリ関数を生成している) に過ぎません。
そのため、どうしても文脈によって使われ方を判断したいのであれば、あるオブジェクトが デコレータとして使われているのか、それともコンテキストマネージャとして使われているのかは、呼び出される dunder method によって区別することが可能 かと存じます。具体的には、 @contextlib.contextmanager
を使わず、 Callable 扱いで __call__
が呼ばれた場合はデコレータとして、コンテキストマネージャ扱いで __enter__
が呼ばれた場合はコンテキストマネージャとして動作するようなオブジェクトを作ればよいわけですから、次のような実装が求めるものに近しいのではないかと思われます。
python
1import time
2from functools import wraps
3from typing import Callable
4
5
6class stopwatch:
7 def __init__(self, label=None):
8 self._label = label
9
10 def __call__(self, func: Callable) -> Callable:
11 @wraps(func)
12 def wrapper(*args, **kwargs):
13 start = time.time()
14 result = func(*args, **kwargs)
15 elapsed_time = time.time() - start
16 print(f"{func.__name__}: {elapsed_time}")
17 return result
18
19 return wrapper
20
21 def __enter__(self):
22 self._start = time.time()
23
24 def __exit__(self, exc_type, exc_value, traceback):
25 elapsed_time = time.time() - self._start
26 print(f"{self._label}: {elapsed_time}")
27
28
29@stopwatch()
30def sleep():
31 time.sleep(0.1)
32
33
34def main():
35 sleep()
36
37 with stopwatch("sleep_with"):
38 time.sleep(0.1)
39
40
41if __name__ == "__main__":
42 main()
但し、上記コードで @stopwatch()
としている通り、 あくまで区別が可能となるのはオブジェクトを生成した後 なので、デコレータとして使うときも新規のオブジェクトを生成する必要性が出て参ります。もしもデコレータは毎回生成せずに静的な Callable として、コンテキストマネージャは label
付で毎回生成する型として実装することも求めるとなると、その場合はデコレータ呼出しとコンテキストマネージャ生成がどちらも関数呼び出し (またはオブジェクト生成) の文脈となってしまい、区別することは出来ないのではないかと思われます。その意味では、厳密には御質問の「関数 を文脈から両方に対応させる」という要件自体は (リフレクションを用いて頑張るのでなければ) 不可能と言えるかもしれません。
そのことを考えれば、文脈を踏まえて、といった考え方では無く、単純に どちらも関数の呼び出しであり、引数が異なるのだから、引数で判別すればよい と考えた方が、実装は単純で分かりやすくなるかもしれません。デコレータとして使われる場合、引数に Callable が渡されますから、その場合はデコレータとして、そうでない場合はコンテキストマネージャのファクトリとして振舞うような分岐を入れれば、これでも概ね求める要件は満たせるように思います。
python
1import time
2from contextlib import contextmanager
3from functools import wraps
4from typing import Callable
5
6
7def stopwatch(func_or_label):
8 def decorator_stopwatch(func: Callable) -> Callable:
9 @wraps(func)
10 def wrapper(*args, **kwargs):
11 start = time.time()
12 result = func(*args, **kwargs)
13 elapsed_time = time.time() - start
14 print(f"{func.__name__}: {elapsed_time}")
15 return result
16
17 return wrapper
18
19 @contextmanager
20 def contextmanager_stopwatch(label: str):
21 start = time.time()
22 yield
23 elapsed_time = time.time() - start
24 print(f"{label}: {elapsed_time}")
25
26 if callable(func_or_label):
27 return decorator_stopwatch(func_or_label)
28 else:
29 return contextmanager_stopwatch(func_or_label)
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
退会済みユーザー
2020/03/14 01:18