配置参数¶
初始化流程主要解决的是加载配置参数或文件,重点部分在load_config()方法中。
配置参数的处理主要是在config.py文件中,有三个主要的类:
- Config
- Setting
- SettingMeta
先挑有趣的代码开始阅读。
class Setting(object):
__metaclass__ = SettingMeta
class SettingMeta(type):
def __new__(cls, name, bases, attrs):
super_new = super(SettingMeta, cls).__new__
元类编程¶
SettingMeta和Setting继承的方式,是完全不同的,尽管他们都是用class来定义。
__metaclass__ = SettingMeta
metaclass是元类,它是一个类的类(型)。相对于类可以在运行时动态构造对象而言,元类也可以在运行时动态生成类。
解释type¶
示例:
>>> type('abc')
<type 'str'>
>>> type('abc')('def')
'def'
>>> type(type(1))
<type 'type'>
>>> type(type(1))(1)
<type 'int'>
>>> Calculator = type('Calculator', (), {'add': lambda self, x, y: x+y, 'sub': lambda sef, x, y: x-y })
>>> calc = Calculator()
>>> type(calc)
<class '__main__.Calculator'>
>>>
>>> print calc.add(1, 2)
3
>>> print calc.sub(1, 2)
-1
type实际上是接收3个参数,第一个参数是类名,第二个是父类(由于允许多重继承,所以是个元组,空元组表示父类为object),第三个参数为类的成员字典。它会返回一个新风格的type对象,这个对象实际上就是一个动态生成的类。
跟踪子类¶
下面的内容是摘自pro django一书中的内容,是自己在2009-03-31翻译的,放在博客上,从中可以很清楚的知道元类的使用方式。
考虑一个应用,在任何时候,访问一个特定类的所有子类列表。metaclass是一个非常好的处理手段,但是存在一个问题。记住,每一个带有__metaclass__属性的类都要处理,包括新的基类,他们是不需要被注册的(只有它的子类要被注册)。
要处理好这个问题,就要作些额外的处理,但这样作也是很直接了当的,同时也是很有益处的。
示例:
>>> class SubclassTracker(type):
... def __init__(cls, name, bases, attrs):
... try:
... if TrackedClass not in bases:
... return
... except NameError:
... return
... TrackedClass._registry.append(cls)
...
>>> class TrackedClass(object):
... __metaclass__ = SubclassTracker
... _registry = []
...
>>> class ClassOne(TrackedClass):
... pass
...
>>> TrackedClass._registry
[<class '__main__.ClassOne'>]
>>> class ClassTwo(TrackedClass):
... pass
...
>>> TrackedClass._registry
[<class '__main__.ClassOne'>, <class '__main__.ClassTwo'>]
这个metaclass执行了两个功能。首先,try块确保父类,TrackedClass,已经定义好了。如果没有的话,就抛出NameError异常, 这个过程就表明metaclass当前正处理TrackedClass。TrackedClass那还能处理更多的东西,但是这个例子为了简单,忽略掉了,只要通过注册就行了。
...
所有TrackedClass的子类能在任何时候从注册表中提取。 TrackedClass的任何子类都将出现在这个注册表中,不管子类在哪里定义的。执行这个类定义的过程就开始注册它,应用程序能导入任何有这些类和 metaclass的模块。
gunicorn配置怎么处理¶
Setting、SettingMeta与TrackedClass、SubclassTracker主体上是一致的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class SettingMeta(type):
def __new__(cls, name, bases, attrs):
super_new = super(SettingMeta, cls).__new__
parents = [b for b in bases if isinstance(b, SettingMeta)]
if not parents:
return super_new(cls, name, bases, attrs)
attrs["order"] = len(KNOWN_SETTINGS)
attrs["validator"] = wrap_method(attrs["validator"])
new_class = super_new(cls, name, bases, attrs)
new_class.fmt_desc(attrs.get("desc", ""))
KNOWN_SETTINGS.append(new_class)
return new_class
def fmt_desc(cls, desc):
desc = textwrap.dedent(desc).strip()
setattr(cls, "desc", desc)
setattr(cls, "short", desc.splitlines()[0])
|
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 | class Setting(object):
__metaclass__ = SettingMeta
name = None
value = None
section = None
cli = None
validator = None
type = None
meta = None
action = None
default = None
short = None
desc = None
def __init__(self):
if self.default is not None:
self.set(self.default)
def add_option(self, parser):
if not self.cli:
return
args = tuple(self.cli)
kwargs = {
"dest": self.name,
"metavar": self.meta or None,
"action": self.action or "store",
"type": self.type or "string",
"default": None,
"help": "%s [%s]" % (self.short, self.default)
}
if kwargs["action"] != "store":
kwargs.pop("type")
parser.add_option(*args, **kwargs)
def copy(self):
return copy.copy(self)
def get(self):
return self.value
def set(self, val):
assert callable(self.validator), "Invalid validator: %s" % self.name
self.value = self.validator(val)
|
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 | class Bind(Setting):
name = "bind"
section = "Server Socket"
cli = ["-b", "--bind"]
meta = "ADDRESS"
validator = validate_string
default = "127.0.0.1:8000"
desc = """\
The socket to bind.
A string of the form: 'HOST', 'HOST:PORT', 'unix:PATH'. An IP is a valid
HOST.
"""
class Workers(Setting):
name = "workers"
section = "Worker Processes"
cli = ["-w", "--workers"]
meta = "INT"
validator = validate_pos_int
type = "int"
default = 1
desc = """\
The number of worker process for handling requests.
A positive integer generally in the 2-4 x $(NUM_CORES) range. You'll
want to vary this a bit to find the best for your particular
application's work load.
"""
|
注意在SettingMeta中的KNOWN_SETTINGS.append(new_class),和TrackedClass._registry,Bind、Workers在声明时,就已经执行SettingMeta的__new__(),留意__new__()与__init__()的区别
>>> from gunicorn import config
>>> b = config.Bind()
>>> b.default
'127.0.0.1:8000'
>>> b.cli
['-b', '--bind']
>>> b.desc
"The socket to bind.\n\nA string of the form: 'HOST', 'HOST:PORT', 'unix:PATH'. An IP is a valid\nHOST."
>>> b.name
'bind'
>>> b.value
'127.0.0.1:8000'
>>> b.validator
<bound method Bind._wrapped of <gunicorn.config.Bind object at 0x8eb4eec>>
>>> b.order
1
SettingMeta中第8-9行,动态生成的有:validator()方法与order属性,在导入config时就已完成。
1 2 3 4 | def wrap_method(func):
def _wrapped(instance, *args, **kwargs):
return func(*args, **kwargs)
return _wrapped
|
这里使用了python decorator的特性,wrap_method(‘validate_string’)通过这样处理后,把validate_string替换成新的名称validator。
>>> from gunicorn import config
>>> c = config.Config()
>>> c.address
('127.0.0.1', 8000)
>>> c.workers
1
>>> c.worker_class
<class 'gunicorn.workers.sync.SyncWorker'>
由Config中的parser方法,来解析命令行的参数