面向切面编程在 Python 中一部分体现为装饰器。
由于 Python 中一切皆对象,装饰器的使用方法也因此多种多样,下文介绍装饰器的四种写法。
用函数装饰函数
def retry(func: Optional[Callable] = None, duration: timedelta = timedelta(seconds=2), limit: int = 10) -> Callable:
if not func:
return functools.partial(retry, duration=duration, limit=limit)
@functools.wraps(func)
def _func(*args, **kwargs):
duration_seconds = duration.total_seconds()
count = 1
while count <= limit:
try:
result = func(*args, **kwargs)
return result
except:
count += 1
time.sleep(duration_seconds)
continue
return _func
class TestRetry(unittest.TestCase):
@staticmethod
def generate_test_function(limit, retry_decorator):
retry_count = [0]
@retry_decorator(limit=5, duration=timedelta(seconds=0))
def my_test():
retry_count[0] += 1
if retry_count[0] < limit:
raise Exception("Test failed")
else:
return retry_count
return my_test
def test_function_decoration(self):
for i in range(100):
exception_count = random.randint(1, 10)
retry_count = self.generate_test_function(exception_count, retry)()
if exception_count <= 5:
self.assertEqual(retry_count[0], exception_count)
else:
self.assertIsNone(retry_count)
这是最常见的一种写法,单元测试中的语法糖拆解过程如下:
$$mytest() = retry\_decorator(limit=5, duration=timedelta(seconds=0))(my\_test)()$$
用类装饰函数
class Retry:
"""
Decorator
"""
def __init__(self, duration: timedelta = timedelta(seconds=2), limit: int = 10):
self.duration = duration
self.limit = limit
def __call__(self, func: Callable):
@functools.wraps(func)
def _func(*args, **kwargs):
duration_seconds = self.duration.total_seconds()
count = 1
while count <= self.limit:
try:
result = func(*args, **kwargs)
return result
except:
count += 1
time.sleep(duration_seconds)
continue
return _func
class TestRetry(unittest.TestCase):
"""
unittest
"""
@staticmethod
def generate_test_function(limit, retry_decorator):
retry_count = [0]
@retry_decorator(limit=5, duration=timedelta(seconds=0))
def my_test():
retry_count[0] += 1
if retry_count[0] < limit:
raise Exception("Test failed")
else:
return retry_count
return my_test
def test_class_decoration(self):
for i in range(100):
exception_count = random.randint(1, 10)
retry_count = self.generate_test_function(exception_count, Retry)()
if exception_count <= 5:
self.assertEqual(retry_count[0], exception_count)
else:
self.assertIsNone(retry_count)
用类装饰函数并不常见,这里主要运用 class 的 __call__ 特性,语法糖分解后
$$my\texttt{_}test() = TestRetry(limit=5, duration=timedelta(seconds=0)).\texttt{__call__}(self, my\_test)$$
用函数装饰类
def single_instance(clazz: type) -> type:
"""
Decorator to make a class a singleton.
"""
class Singleton:
delegate_clazz = clazz
instance = None
delegate_instance = None
def __init__(self, *args, **kwargs):
self.delegate_instance.__init__(*args, **kwargs)
def __new__(cls, *args, **kwargs):
if cls.instance is None:
cls.instance = super().__new__(cls)
cls.delegate_instance = cls.delegate_clazz.__new__(cls.delegate_clazz, *args, **kwargs)
return cls.instance
def __getattr__(self, item):
return getattr(self.delegate_instance, item)
return Singleton
class TestClass(unittest.TestCase):
def test_single_instance_func(self):
@single_instance
class Instance:
def __init__(self, num):
self.num = num
def value(self):
return self.num
self.assertEqual(Instance(1), Instance(1))
self.assertEqual(Instance(1).num, 1)
self.assertEqual(Instance(1).value(), 1)
这里用函数装饰器实现了单例模式,但生产场景中很少如此实现,大多采用元类的方式。语法糖拆解如下
$$ Instance(1) = single\_instance(Instance).\texttt{__new__}(Singleton).\texttt{__init__}(self, 1) $$
用类装饰函数
class SingleInstance:
"""
Decorator to make a class a singleton.
"""
def __init__(self, clazz: type):
self.clazz = clazz
self.instance = None
def __call__(self, *args, **kwargs):
if self.instance is None:
self.instance = self.clazz(*args, **kwargs)
else:
self.instance.__init__(*args, **kwargs)
return self.instance
class TestClass(unittest.TestCase):
def test_single_instance_func(self):
@SingleInstance
class Instance:
def __init__(self, num):
self.num = num
def value(self):
return self.num
self.assertEqual(Instance(1), Instance(1))
self.assertEqual(Instance(1).num, 1)
self.assertEqual(Instance(1).value(), 1)
类似用类装饰器装饰函数,语法糖拆解如下
$$Instance(1) = SingleInstance(Instance)(1) = SingleInstance(Instance).\texttt{__call__}(self, 1) $$