[源码分析]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章的内容,有详细的介绍。


我的blog:https://blog.shiniao.fun/

https://juejin.im/post/5e37ee0af265da3e2170ae17

「点点赞赏,手留余香」

    还没有人赞赏,快来当第一个赞赏的人吧!
JAVA
JVM
0 条回复 A 作者 M 管理员
    所有的伟大,都源于一个勇敢的开始!
欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论