手动创建连接并将其传递给 Redis 可能很困难。我们不仅需要反复参考配置信息,而且如果我们使用上一节中的配置管理功能,我们仍然需要获取配置、连接到 Redis,并以某种方式处理连接。为了简化所有这些连接的管理,我们将编写一个装饰器,它将负责连接到我们所有的 Redis 服务器(除了配置服务器)。
装饰器在 Python 中,有一种语法可以将函数 X 传递到另一个函数 Y 中。这个函数 Y 称为装饰器。装饰器有机会改变函数 X 的行为。一些装饰器验证参数,其他装饰器注册回调,甚至其他装饰器管理连接,就像我们打算的那样。
我们的装饰器将以命名配置作为参数,这将生成一个包装器,当在实际函数上调用时,将包装该函数,以便以后的调用将自动连接到适当的 Redis 服务器,并且该连接将传递给包装的函数,以及所有其他后来提供的参数。下一个列表包含我们的 redis_connection() 函数的源代码。
REDIS_CONNECTIONS = {}
def redis_connection(component, wait=1):
我们将应用程序组件的名称传递给装饰器。
key = 'config:redis:' + component
我们缓存配置键,因为每次调用该函数时都会获取它。
def wrapper(function):
我们的包装器接受一个函数,它用另一个函数包装该函数。
@functools.wraps(function)
将一些有用的元数据从原始函数复制到配置处理程序。
def call(*args, **kwargs):
创建实际的函数,它将管理连接信息。
old_config = CONFIGS.get(key, object())
获取旧的配置(如果有)。
_config = get_config( config_connection, 'redis', component, wait)
获取新的配置(如果有)。
config = {}
for k, v in _config.iteritems(): config[k.encode('utf-8')] = v
使配置可用于创建 Redis 连接。
if config != old_config: REDIS_CONNECTIONS[key] = redis.Redis(**config)
如果新旧配置不匹配,则创建一个新连接。
return function( REDIS_CONNECTIONS.get(key), *args, **kwargs)
调用并返回包装函数的结果,记住传递连接和其他匹配的参数。
return call
返回完全包装的函数。
return wrapper
返回一个可以包装我们的 Redis 函数的函数。
组合 *args 和 **kwargs早在第 1 章中,我们首先了解了 Python 中的默认参数。但是在这里,我们结合了两种不同的参数传递形式。如果您难以理解正在发生的事情(这本质上是在函数定义中捕获 args 和 kwargs 变量中的所有位置和命名参数,并将所有位置和命名参数传递给被调用的函数),那么您应该花一些时间通过此缩短的 URL 阅读 Python 语言教程:http://mng.bz/KM5x。
我知道这组嵌套函数一开始可能会令人困惑,但实际上并没有那么糟糕。我们有一个函数 redis_connection(),它接受命名的应用程序组件并返回一个包装器函数。然后使用我们想要传递连接的函数(包装的函数)调用该包装器函数,然后返回函数调用者。此调用者处理获取配置信息、连接到 Redis 和调用我们的包装函数的所有工作。虽然描述起来很麻烦,但实际使用起来很方便,您可以通过在下一个列表中将其应用于第 5.1.1 节中的 log_recent() 函数来看到这一点。
@redis_connection('logs')
redis_connection() 装饰器非常易于使用。
def log_recent(conn, app, message):
函数定义没有改变。
'the old log_recent() code'
log_recent('main', 'User 235 logged in')
我们不再需要在调用 log_recent() 时担心传递日志服务器连接。
装饰器除了列表 5.16 中使用 *args 和 **kwargs 进行的奇怪的参数传递之外,我们还使用语法来“装饰”日志函数。也就是说,我们将一个函数传递给一个装饰器,该装饰器对该函数执行一些操作,然后返回原始函数或其他内容。您可以在 https://pythonlang.cn/dev/peps/pep-0318/ 上阅读有关正在发生的事情以及原因的详细信息。
既然您已经了解了如何在 log_recent() 上使用 redis_connection() 装饰器,它看起来是不是没那么糟糕了?通过这种更好的连接和配置处理方法,我们刚刚从几乎所有要调用的函数中删除了几行代码。作为练习,尝试将此装饰器添加到第 5.2.3 节中的 access_time() 上下文管理器中,这样我们就不需要传递连接了。您可以随意在本书中的所有其他示例中重复使用此装饰器。