[源码分析] Flask 配置管理与描述符析
在 Flask 中可以通过 app.config[‘NAME’] = what 的形式指定一些配置,比如设置 debug = True :
app.debug = True
# 或者
app.config['DEBUG'] = True
有些配置比如设置 ENV 和 TESTING 还可以直接利用 Flask 对象来设置,像这样:
app.testing = True
除了在程序中指定配置,也可以将配置写在单独的文件中,比如:
app = Flask(__name__)
app.config.from_object('yourapplication.default_settings')
app.config.from_envvar('YOURAPPLICATION_SETTINGS')
应用首先从 yourapplication.default_settings 模块载入配置,然后根据 YOURAPPLICATION_SETTINGS
环境变量所指向的文件的内容重载配置的值。
除了从配置文件加载,也可以定义类类指定配置,具体用法去看看官方文档就知道了。
知道了在 Flask 中如何使用配置,我们来看看它是如何实现的。
首先 config 肯定是个变量,在 Flask 这个类中被定义为:
self.config = self.make_config(instance_relative_config)
然后 make_config 的代码是这样的:
def make_config(self, instance_relative=False):
root_path = self.root_path
if instance_relative:
root_path = self.instance_path
# 默认配置
defaults = dict(self.default_config)
defaults['ENV'] = get_env()
defaults['DEBUG'] = get_debug_flag()
return self.config_class(root_path, defaults)
make_config 方法获取 flask 中默认的配置,以及 ENV 和 DEBUG 这两个配置,之后返回了 self.config_class 对象,它在类中是这样定义的:
config_class = Config
class Config(dict):
def __init__(self, root_path, defaults=None):
# dict.__init__让 Config 实例拥有字典行为 config['ENV']
dict.__init__(self, defaults or {})
self.root_path = root_path
config_class 本质上是 Config 类,注意看 Config 类的初始化方法,
dict.__init__(self, defaults or {})
这一行代码使得 Config 类可以像字典一样使用,比如 app.config['TESTING']=True
。当然你也可以使用 __getitem__
和 __setitem__
内置方法使得类具有字典的行为。
那么像 app.testing = True 这样的配置是如何实现的?
在 Flask 类中可以看到,这些类变量都是 ConfigAttribute 对象。
testing = ConfigAttribute('TESTING')
secret_key = ConfigAttribute('SECRET_KEY')
ConfigAttribute类如下:
class ConfigAttribute(object):
def __init__(self, name, get_converter=None):
self.__name__ = name
self.get_converter = get_converter
# obj 是被托管类实例
def __get__(self, obj, type=None):
# 如果被托管实例不存在,返回描述符自身
if obj is None:
return self
# 返回 Flask 实例的 config[name]
rv = obj.config[self.__name__]
if self.get_converter is not None:
rv = self.get_converter(rv)
return rv
def __set__(self, obj, value):
obj.config[self.__name__] = value
ConfigAttribute 是一个描述符类,描述符是什么?
描述符是对多个属性运用相同存取逻辑的一种方式。——《流畅的 python》
描述符实现了特定的内置方法,__get__
, __set__
和 __delete__
,常见的比如 Django 中 ORM 中的实现就是用的描述符:
class Person(models.Model):
# models.CharField 就是一个描述符
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
还有 python 内置的 @property @classmethod staticmethod 装饰器就是用描述符实现的。
说了这么多,来看看描述符到底怎么用。
首先来看ConfigAttribute类中的 __get__
方法:
# obj 是被托管类实例
def __get__(self, obj, type=None):
# 如果被托管实例不存在,返回描述符自身
if obj is None:
return self
# 返回 Flask 实例的 config[name]
rv = obj.config[self.__name__]
if self.get_converter is not None:
rv = self.get_converter(rv)
return rv
__get__
方法的参数 obj 是被托管类的实例,在这里就是 Flask 类,方法中首先判断被托管类是否存在,不存在就返回描述符本身。之后返回 Flask 类实例中的 config[name],看到没有 env 类变量实际上就是 config[name] 中指定的值。
来看 __set__
方法:
def __set__(self, obj, value):
obj.config[self.__name__] = value
__set__
方法中的 obj 同样是被托管类的实例,然后 value 被存储在被托管类的 config 变量中。所以,我们才可以用 app.testing = True 来指定配置。
说到底,描述符有什么用?我们来看,在 Flask 类中我们要指定类变量 testing, env, secret_key, session_cookie_name 等等,都需要从 config 变量中接收(保证配置的一致性)。我们为这些变量都写一个存取方法是不是很麻烦,使用描述符就可以简化流程,对外封装了具体的存取细节,并且减少代码量。
当然我们也可以使用一个函数,通过构建特性工厂的方式来实现,比如:
def config_attribute(name):
def getter(instance):
return instance.config[name]
def setter(instance, value):
instance.config[name] = value
# property 实际上就是@property 装饰器
return property(getter, setter)
# 在 Flask 中调用方式一样
testing = config_attribute('TESTING')
关于描述符的更多细节可以查看 《流畅的 python》 这本书中第 20 章的内容,有详细的介绍。
© CC BY-NC-SA 4.0 | Chaojie