本文为英文书籍 Clean Code in Python Chapter 5 Using Decorators to Improve Our Code 学习笔记,建议直接看原书html
虽然通常见到装饰器装饰的是方法和函数,但实际容许装饰任何类型的对象,所以咱们将探索应用于函数、方法、生成器和类的装饰器。python
还要注意,不要将装饰器与装饰器设计模式(Decorator Pattern)混为一谈。设计模式
函数多是能够被装饰的Python对象中最简单的表示形式。咱们能够在函数上使用装饰器来达成各类逻辑——能够验证参数、检查前提条件、彻底改变行为、修改签名、缓存结果(建立原始函数的存储版本)等等。缓存
做为示例,咱们将建立实现重试机制的基本装饰器,控制特定的域级异常(domain-level exception)并重试必定次数:bash
# decorator_function_1.py
import logging
from functools import wraps
logger = logging.getLogger(__name__)
class ControlledException(Exception):
"""A generic exception on the program's domain."""
pass
def retry(operation):
@wraps(operation)
def wrapped(*args, **kwargs):
last_raised = None
RETRIES_LIMIT = 3
for _ in range(RETRIES_LIMIT):
try:
return operation(*args, **kwargs)
except ControlledException as e:
logger.info("retrying %s", operation.__qualname__)
last_raised = e
raise last_raised
return wrapped
复制代码
能够暂时忽略@wraps,以后再介绍
retry装饰器使用例子:app
@retry
def run_operation(task):
"""Run a particular task, simulating some failures on its execution."""
return task.run()
复制代码
由于装饰器只是提供的一种语法糖,实际上等于run_operation = retry(run_operation)
比较经常使用的超时重试,即可以这样实现。dom
咱们用一个例子详细阐述下接受参数的处理过程。 假设你想写一个装饰器,给函数添加日志功能,同时容许用户指定日志的级别和其余的选项。 下面是这个装饰器的定义和使用示例:ide
from functools import wraps
import logging
def logged(level, name=None, message=None):
""" Add logging to a function. level is the logging level, name is the logger name, and message is the log message. If name and message aren't specified, they default to the function's module and name. """
def decorate(func):
logname = name if name else func.__module__
log = logging.getLogger(logname)
logmsg = message if message else func.__name__
@wraps(func)
def wrapper(*args, **kwargs):
log.log(level, logmsg)
return func(*args, **kwargs)
return wrapper
return decorate
# Example use
@logged(logging.DEBUG)
def add(x, y):
return x + y
@logged(logging.CRITICAL, 'example')
def spam():
print('Spam!')
复制代码
初看起来,这种实现看上去很复杂,可是核心思想很简单。 最外层的函数 logged()
接受参数并将它们做用在内部的装饰器函数上面。 内层的函数 decorate()
接受一个函数做为参数,而后在函数上面放置一个包装器。 这里的关键点是包装器是可使用传递给 logged()
的参数的。函数
定义一个接受参数的包装器看上去比较复杂主要是由于底层的调用序列。特别的,若是你有下面这个代码:学习
@decorator(x, y, z)
def func(a, b):
pass
复制代码
装饰器处理过程跟下面的调用是等效的;
def func(a, b):
pass
func = decorator(x, y, z)(func)
decorator(x, y, z) 的返回结果必须是一个可调用对象,它接受一个函数做为参数并包装它
复制代码
有些人认为,装饰类是比较复杂的事情,并且这样的方案可能危及可读性。由于咱们在类中声明一些属性和方法,可是装饰器可能会改变它们的行为,呈现出彻底不一样的类。
在这种技术被严重滥用的状况下,这种评价是正确的。客观地说,这与装饰函数没有什么不一样;毕竟,类只是Python生态系统中的另外一种类型的对象,就像函数同样。咱们将在标题为“装饰器和关注点分离”的章节中一块儿回顾这个问题的利弊,可是如今,咱们将探讨类的装饰器的好处:
回顾监视平台的事件系统,咱们如今须要转换每一个事件的数据并将其发送到外部系统。 可是,在选择如何发送数据时,每种类型的事件可能都有本身的特殊性。
特别是,登陆的事件可能包含敏感信息,如登陆信息须要隐藏, 时间戳等其余字段也可能须要特定的格式显示。
class LoginEventSerializer:
def __init__(self, event):
self.event = event
def serialize(self) -> dict:
return {
"username": self.event.username,
"password": "**redacted**",
"ip": self.event.ip,
"timestamp": self.event.timestamp.strftime("%Y-%m-%d% H: % M"),}
class LoginEvent:
SERIALIZER = LoginEventSerializer
def __init__(self, username, password, ip, timestamp):
self.username = username
self.password = password
self.ip = ip
self.timestamp = timestamp
def serialize(self) -> dict:
return self.SERIALIZER(self).serialize()
复制代码
在这里,咱们声明一个类,该类将直接映射到登陆事件,包含其逻辑——隐藏密码字段,并根据须要格式化时间戳。
虽然这种方法可行,并且看起来是个不错的选择,可是随着时间的推移,想要扩展咱们的系统,就会发现一些问题:
另外一种解决方案是,给定一组过滤器(转换函数)和一个事件实例,可以动态构造对象,该对象可以经过滤器对其字段序列化。而后,咱们只须要定义转换每种类型的字段的函数,而且经过组合这些函数中的许多函数来建立序列化程序。
一旦有了这个对象,咱们就能够装饰类,以便添加serialize()方法,该方法将只调用这些Serialization对象自己:
def hide_field(field) -> str:
return "**redacted**"
def format_time(field_timestamp: datetime) -> str:
return field_timestamp.strftime("%Y-%m-%d %H:%M")
def show_original(event_field):
return event_field
class EventSerializer:
def __init__(self, serialization_fields: dict) -> None:
self.serialization_fields = serialization_fields
def serialize(self, event) -> dict:
return {
field: transformation(getattr(event, field))
for field, transformation in self.serialization_fields.items()
}
class Serialization:
def __init__(self, **transformations):
self.serializer = EventSerializer(transformations)
def __call__(self, event_class):
def serialize_method(event_instance):
return self.serializer.serialize(event_instance)
event_class.serialize = serialize_method
return event_class
@Serialization(
username=show_original,
password=hide_field,
ip=show_original,
timestamp=format_time,
)
class LoginEvent:
def __init__(self, username, password, ip, timestamp):
self.username = username
self.password = password
self.ip = ip
self.timestamp = timestamp
复制代码
待续。。。