0%

tornado.util.Configurable

tornado.util 模块中的 Configurable 是一个抽象类,该类通过继承机制为实现它的类型提供了一个简单工厂的功能。具体来说就是 Configurable 通过重写 __new__ 方法来自定义类的创建,把类的构造函数变成了一个简单工厂,只要一个类继承了 Configurable,那么这个类在实例化时,构造函数就能像简单工厂一样选择这个类的一个子类来实例化。

一般在其他 OO 语言中我们都是通过提供静态工厂方法来实现该功能,有赖于 python 的实例化机制,这里通过重写 __new__ 方法而把类的构造函数变成了简单工厂,使用时更直观一些,通过调用类型的构造函数就完成了选择实例化。

Configurable 类有两个抽象方法:configurable_base(cls)configurable_default(cls),前者通常返回直接继承自 Configurable 的基类类型,后者返回实例化时默认使用的子类型。

目前在 tornado 中直接继承自 Configurable 的基类类型是 AsyncHttpClient, IOLoopResolve。也就是说我们可以通过 AsyncHTTPClient(), IOLoop()Resolver() 直接完成从这些基类类型的子类型中选择一个配置好的类型来实例化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
class Configurable(object):

__impl_class = None
__impl_kwargs = None

def __new__(cls, **kwargs):
base = cls.configurable_base()
args = {}
# 做一个类型判断,以便不影响直接实例化具体子类
if cls is base:
impl = cls.configured_class()
if base.__impl_kwargs:
args.update(base.__impl_kwargs)
else:
impl = cls
args.update(kwargs)
instance = super(Configurable, cls).__new__(impl)
# initialize vs __init__ chosen for compatiblity with AsyncHTTPClient
# singleton magic. If we get rid of that we can switch to __init__
# here too.
#
# AsyncHTTPClient 中默认为每一个 IOLoop 实例对应一个 AsyncHTTPClient 实例。
# 其重写了 __new__ 方法指定了两个命名参数:io_loop,force_instance,为了兼容
# 这个初始化过程,这里选择使用 initialize 而不是 __init__ 来初始Configurable
# 实例。如果能摆脱这个约束的话,我们便可以在这里使用 __init__ 方法。
instance.initialize(**args)
return instance

@classmethod
def configurable_base(cls):
"""返回直接继承自 configurable 类的基类类型。
一般情况下,返回定义(实现)这个方法的类型,但是这个不是必须的。
"""
raise NotImplementedError()


@classmethod
def configurable_default(cls):
"""返回该类型实例化时默认(__impl_class = None时)的子类类型。"""
raise NotImplementedError()

def initialize(self):
"""初始化 `Configurable` 子类实例。
注意一下,初始化 Configurable 类型需要使用 `initialize` 代替 ``__init__``。
"""

@classmethod
def configure(cls, impl, **kwargs):
"""设置实例化基类类型时使用的子类类型。

关键字参数会保存在类字段中,在实例化类型时传递给类型的构造函数。这样便可以为该类型
的实例设置一些全局的初始化参数。
"""
base = cls.configurable_base()
if isinstance(impl, (unicode_type, bytes_type)):
impl = import_object(impl)
if impl is not None and not issubclass(impl, cls):
raise ValueError("Invalid subclass of %s" % cls)
base.__impl_class = impl
base.__impl_kwargs = kwargs

@classmethod
def configured_class(cls):
"""返回当前配置的子类类型,如果有通过 configure 方法配置则返回对应配置的类型,
则返回默认的子类类型。

该方法在 __new__ 方法中有调用,用于确定类型需要实例化的具体子类类型。
"""
base = cls.configurable_base()
if cls.__impl_class is None:
base.__impl_class = cls.configurable_default()
return base.__impl_class

综上所述,我们通过 tornado.ioloop.IOLoop.instance() 便可以根据不同的平台获取 IOLoop 的子类实例。在 《tornado:IOLoop模块解析》 中已经有过分析,这里我们再来结合 Configurable 看看其代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@staticmethod
def instance():
if not hasattr(IOLoop, "_instance"):
with IOLoop._instance_lock:
if not hasattr(IOLoop, "_instance"):
# New instance after double check
# 实例化一个 IOLoop 类型,由于这里构造函数实际是工厂,所以实例化的是
# IOLoop 某个平台的具体类型。
IOLoop._instance = IOLoop()
return IOLoop._instance

@classmethod
def configurable_base(cls):
return IOLoop

@classmethod
def configurable_default(cls):
"""根据不同平台类选择具体的 IOLoop 类型。"""
if hasattr(select, "epoll"):
from tornado.platform.epoll import EPollIOLoop
return EPollIOLoop
if hasattr(select, "kqueue"):
# Python 2.6+ on BSD or Mac
from tornado.platform.kqueue import KQueueIOLoop
return KQueueIOLoop
from tornado.platform.select import SelectIOLoop
return SelectIOLoop