在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的代码是这样的:defmake_config(self,instance_relative=False):root_path=self.root_pathifinstance_relative:root_path=self.instance_path#默认配置defaults=dict(self.default_config)defaults['ENV']=get_env()defaults['DEBUG']=get_debug_flag()returnself.config_class(root_path,defaults)make_config方法获取flask中的默认配置,以及ENV和DEBUG这两个配置,然后返回self.config_class对象,类中定义如下:config_class=ConfigclassConfig(dict):def__init__(self,root_path,defaults=None):#dict.__init__让Config实例有字典行为config['ENV']dict.__init__(self,defaultsor{})self.root_path=root_pathconfig_class本质上就是Config类,注意初始化mConfig类的方法,dict。__init__(self,defaultsor{})这行代码使Config类可以作为字典使用,例如app.config['TESTING']=True。当然,您也可以使用__getitem__和__setitem__内置方法使类的行为类似于字典。那么像app.testing=True这样的配置是如何工作的呢?在Flask类中可以看到,这些类变量都是ConfigAttribute对象。testing=ConfigAttribute('TESTING')secret_key=ConfigAttribute('SECRET_KEY')ConfigAttribute类如下:#obj是一个托管类实例def__get__(self,obj,type=None):#如果托管实例不存在,则返回描述符本身ifobjisNone:returnself#返回Flask的config[name]instancerv=obj.config[self.__name__]如果self.get_converter不是None:rv=self.get_converter(rv)returnrvdef__set__(self,obj,value):obj.config[self.__name__]=valueConfigAttributeis描述符号的描述符类是什么?描述符是一种将相同的访问逻辑应用于多个属性的方法。——《流畅的python》描述符实现了具体的内置方法,__get__、__set__和__delete__,常见的如Django中ORM中的实现就是使用的描述符:classPerson(models.Model):#models.CharFieldisa描述符first_name=models.CharField(max_length=30)last_name=models.CharField(max_length=30)和python内置的@property@classmethodstaticmethod装饰器都是使用描述符实现的。说了这么多,我们来看看描述符是如何使用的。首先看ConfigAttribute类中的__get__方法:#obj是一个托管类实例def__get__(self,obj,type=None):#如果托管实例不存在,则返回描述符本身ifobjisNone:returnself#返回Flask实例的config[name]rv=obj.config[self.__name__]ifself.get_converterisnotNone:rv=self.get_converter(rv)returnrv__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变量中接收(保证配置的一致性)。我们自己写一个访问这些变量的方法是不是很麻烦?使用描述符可以简化流程,对外封装具体的访问细节,减少代码量。当然我们也可以通过构建特征工厂的方式使用函数来实现,比如:defconfig_attribute(name):defgetter(instance):returninstance.config[name]defsetter(instance,value):instance。config[name]=value#property其实就是@property装饰器returnproperty(getter,setter)#Flask中的调用方式相同testing=config_attribute('TESTING')更多关于描述符的细节可以在《流畅的python》中找到书中第20章的内容,有详细的介绍。我的博客:https://blog.shiniao.fun/
