0xe3aad

0xe3aad

Pythonデコレータ

導入#

関数の実行前後に何かしらの処理を行いたい場合、以下のように書くことができます:

def foo():
	print('foo()!!!')

def bar():
	print('Before')
	foo()
	print('After')

bar()

デコレータの実装#

しかし、これは少し不自然に見えます。以下のように書くこともできます:

def foo():
	print('foo()!!!')

def bar(func):
	def inner():
		print(f'Before {func.__name__}')
		func()
		print(f'After {func.__name__}')
	return inner

bar(foo)()

Python では、関数が第一級オブジェクトであるため、関数を引数として渡すことができます。

これにより、シンプルなデコレータが実装されました。しかし、呼び出し時に少し手間がかかることに気付くかもしれません。Python では、次のようなシンタックスシュガーも提供されています:

def bar(func):
	def inner():
		print(f'In Bar Before {func.__name__}')
		func()
		print(f'In Bar After {func.__name__}')
	return inner

@bar
def foo():
	print('foo()!!!')

foo()

さらに、Python のデコレータは複数持つことができ、一種のラッピングのようなものです:

def bar(func):
	def inner():
		print(f'In Bar Before {func.__name__}')
		func()
		print(f'In Bar After {func.__name__}')
	return inner

def baz(func):
	def inner():
		print(f'In Baz Before {func.__name__}')
		func()
		print(f'In Baz After {func.__name__}')
	return inner


@bar
@baz
def foo():
	print('foo()!!!')

foo()

上記のコードの出力は次のとおりです:

In Bar Before inner
In Baz Before foo
foo()!!!
In Baz After foo
In Bar After inner

一つの応用例#

次のような関数があるとします:

import time

def slow_method():
	time.sleep(2)
	print('Done!')

この関数の実行時間を知りたい場合、デコレータを使用して実装することができます:

import time

def timeit(func):
	def inner():
		s = time.time()
		func()
		e = time.time()
		print(f'{func.__name__} Finished in {e - s}s.')
	return inner

@timeit
def slow_method():
	time.sleep(2)
	print('Done!')

slow_method()

実行結果:

Done!
slow_method Finished in 2.0159008502960205s.

これにより、要件が満たされました。

デコレートされた関数に引数がある場合も同様に動作します:

import time

def timeit(func):
	def inner(*args):
		s = time.time()
		func(*args)
		e = time.time()
		print(f'{func.__name__} Finished in {e - s}s.')
	return inner

@timeit
def slow_method(a, b):
	time.sleep(2)
	print(f'{a} + {b} = {a+b}')
	print('Done!')

slow_method(1, 2)

実行結果:

1 + 2 = 3
Done!
slow_method Finished in 2.0069477558135986s.

メモ化再帰を実現するためのデコレータの巧妙な使用#

まず、素朴なフィボナッチ関数があるとします:

def fib(n):
	if n <= 1: return 1
	return fib(n - 1) + fib(n - 2)

明らかに、この関数は非常に効率が悪いです。メモ化再帰を使用してこの関数を最適化することができます。

cache = {}

def fib(n):
	if n in cache: return cache[n]
	if n <= 1: return 1
	cache[n] = fib(n - 1) + fib(n - 2)
	return cache[n]

print(fib(333))

これにより、fib 関数がはるかに高速になります。ただし、実装が少し複雑になるかもしれません。デコレータを使用してコードを簡素化することができます:

class MyCache(object):
	def __init__(self, func):
		self.func = func
		self.cache = {}

	def __call__(self, *args):
		if args not in self.cache:
			self.cache[args] = self.func(*args)
		return self.cache[args]

@MyCache
def fib(n):
	if n <= 1: return 1
	return fib(n - 1) + fib(n - 2)


print(fib(333))

これにより、fib 関数が非常にエレガントに実装されます。

参考#

装饰器 Decorator - Python Weekly EP3

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。