1 引言

Flask作为Python语言web开发的三大顶梁柱框架之一,对于配置的管理当然必不可少。一个应用从开发到测试到最后的产品发布,往往都需要多种不同的配置,例如是否开启调试模式、使用哪个数据库等等,这些配置都可能因开发阶段和环境而异。

2 Flask配置类:Config

为了达到对配置方便快捷而又灵活管理的目的,Flask提供了一个名为“config的”属性,这个属性在Flask应用实例化时创建,所以,只要创建了Flask应用,就可以使用这个config属性进行配置管理。我们先创建一个Flask应用,去看一看这个config属性:

from flask import Flask

app = Flask(__name__)
print(type(app.config))

输出结果:

<class 'flask.config.Config'>

可以看出,app.config是一个类,一个定义在flask.config模块中的类。既然是一个类,我们就可以推测,Flask在实例化应用时,也实例化了这个Config类,我们通过这个类提供的各种属性、方法来进行配置管理。如果你用的IDE是pycharm,按住Ctrl鼠标左键点击app.config中的config就可以定位到Flask类中定义config属性的源码,这一行源码如下:

self.config = self.make_config(instance_relative_config)

在一行代码在Flask构造方法__init__()中,正如刚才所说,确实是在实例化Flask应用时创建了config属性。不过在值是Flask类中的make_config()方法的返回值,参数instance_relative_config是__init__()方法的参数,默认为False,具体功能我们在下文解析时用到再说。现在,我们去看一下make_config()方法的源码:

def make_config(self, instance_relative=False):
root_path = self.root_path # root_path是主模块所在绝对路径
# 下面这个instance_relative就是Flask构造方法里面的instance_relative_config
if instance_relative: # 如果实例化Flask传入的instance_relative_config为True
root_path = self.instance_path # instance_path也是Flask构造方法中的参数,是一个路径,如果实例化Flask时没有为instance_path传参则默认路径为Flask实例同级目录下的instance目录
defaults = dict(self.default_config) # 读取Flask初始化时的默认配置
defaults["ENV"] = get_env() # 判断环境类型:production或development,即生产环境或开发环境,设置这个值是因为有些应用需要根据这个值来改变行为
defaults["DEBUG"] = get_debug_flag() # 是否开启调试模式
return self.config_class(root_path, defaults) # 实例化一个Config类

make_config()方法执行可以分为4个步骤:读取配置文件路径、读取默认配置、设置环境和模式、创建Config配置类。可以说,前面3个步骤都是为创建Config类做准备,里面的细节大家看上面代码注释就明白了,重点在于创建Config类,继续往下查看config_class:

config_class = Config  # 将Config赋值给config_class

config_class就是Config类,这里只不过做了一个赋值。继续查看Config:

class Config(dict):
def __init__(self, root_path, defaults=None):
dict.__init__(self, defaults or {})
self.root_path = root_path

当看到这个Config类代码时,仿佛一切都恍然大悟——一切配置操作都在这里。从源码中我们可以看到,Config类继承了dict,也即是说,Config类就是一个字典,一切字典所拥有的使用方法,在Config类上也行得通。

大概浏览config.py文件,可以看到,在Config类中还提供了几个名称很相似的方法:

from_object(self, obj)

from_pyfile(self, filename, silent=False)

from_envvar(self, variable_name, silent=False)

from_json(self, filename, silent=False)

from_mapping(self, *mapping, **kwargs)

阅读方法文档获知,这几个方法是读取配置用的,只不过读取的目标不一样,也就是说,Flask通过提供这几个方法为用户提供了多种配置管理方式。

接下来,我们来捋一捋Flask的配置管理方式。

3 配置方式1:直接赋值

通过上面的分析我们知道,Config类继承类字典类,所以我们可以用字典的方式进行配置管理,例如是config['key'] = value的方式赋值,通过config.get(key)方式取值:

from flask import Flask

app = Flask(__name__)
app.config['DEBUG'] = True
print('是否开始调试模式:', app.config.get('DEBUG'))

输出:

是否开始调试模式: True

注意:Flask中所有配置名称都是大写。上面DEBUG配置中,如果写成了debug,那就会在app.config中添加一个debug的配置,而不是修改DEBUG,开启调试模式就会失败。

也可以调用字典类中的一些方法,例如调用update方法一次性设置多个值:

from flask import Flask

app = Flask(__name__)
app.config.update(
DEBUG=True,
TESTING=True
)
print('debug:', app.config.get('DEBUG'))
print('testing:', app.config.get('TESTING'))

甚至可以将一些默认配置中没有的值存入配置中:

from flask import Flask

app = Flask(__name__)
app.config['aaaaa'] = '我是aaaaa'
print(app.config['aaaaa'])

输出:

我是aaaaa

对于一些小应用来说,这种确实很是简单方便,但是对于更为复杂的应用,可能需要针对不同的环境使用不同的配置,配置的内容又多,这种方法就显得麻烦了。这时候就需要用到Config类中实现的几个方法了。

4 配置方式2-对象中配置:from_object(推荐)

先来看看from_object()方法的源码:

def from_object(self, obj):
if isinstance(obj, string_types): # 判断obj是否是str类型
obj = import_string(obj) # 如果是str类型,就根据这个字符串导入对象
for key in dir(obj): # 遍历obj的所有值
if key.isupper():
self[key] = getattr(obj, key) # self指的就是config实例本身,通过getattr取出对应的值进行

从源码可以看出,from_object()方法说接收的参数obj可以使str类型,可以是一个模块,甚至是一个类。

我们先尝试一下是一个模块的情况,创建一个settings.py模块,内容如下:

DEBUG = False
TESTING = False

这里只写了两个配置,你可以写更多,无所谓。怎么使用呢?

from flask import Flask
import settings app = Flask(__name__)
app.config.from_object(settings)
print('DEBUG:', app.config.get('DEBUG'))
print('TESTING:', app.config.get('TESTING'))
print('A:', app.config.get('A'))

输出:

DEBUG: True

TESTING: True

A: 123

当obj是一个字符串时:

from flask import Flask

app = Flask(__name__)
app.config.from_object('settings')
print('DEBUG:', app.config.get('DEBUG'))
print('TESTING:', app.config.get('TESTING'))
print('A:', app.config.get('A'))

输出:

DEBUG: True

TESTING: True

A: 123

看出来了吗?无论是使用app.config.from_object(settings)还是app.config.from_object('settings')使用的都是使用settings.py文件中的配置,至于原因,如果不明白就回去看看上面的源码。

如果obj是一个类时,我们修改一下settings.py,如下:

class Config(object):
DEBUG = False
TESTING = False
DATABASE_URI = 'sqlite://memory:' class ProductionConfig(Config):
DATABASE_URI = 'mysql://user@localhost/foo' class DevelopmentConfig(Config):
DEBUG = True class TestingConfig(Config):
TESTING = True

在settings.py模块中,我们定义了多个类,首先是Config类,这个类定义的是默认配置,其他类都继承Config类,每一个之类代表一种配置,如果需要子类中可以覆写Config,如果不覆写则使用Config中的默认配置。怎么使用呢?

from flask import Flask
import settings app = Flask(__name__)
app.config.from_object(settings.ProductionConfig)
print('DEBUG:', app.config.get('DEBUG'))
print('TESTING:', app.config.get('TESTING'))
print('DATABASE_URI:', app.config.get('DATABASE_URI'))

输出:

DEBUG: False

TESTING: False

DATABASE_URI: mysql://user@localhost/foo

使用这种方法的好处是可以充分利用面向对象中继承等的优良特性共享配置,设置多套配置,使用时,只需要针对实际需要修改app.config.from_object(settings.ProductionConfig)中传入的类即可。这种方法在实际开发中也是使用最多的。

5 配置方式3-py文件:from_pyfile

继续解析源码:

def from_pyfile(self, filename, silent=False):
filename = os.path.join(self.root_path, filename) # 拼接路径
d = types.ModuleType("config") # 创建一个模块对象
d.__file__ = filename
try:
with open(filename, mode="rb") as config_file: #将文件内容解析到d
exec(compile(config_file.read(), filename, "exec"), d.__dict__)
except IOError as e:
if silent and e.errno in (errno.ENOENT, errno.EISDIR, errno.ENOTDIR):
return False
e.strerror = "Unable to load configuration file (%s)" % e.strerror
raise
self.from_object(d) # 调用上面说到过的from_object()方法
return True

在上一章节分析from_object()方法时,我们说到,from_object()方法可以接受一个模块作为参数,from_pyfile()方法接受的就是一个py文件作为参数,在Python中一个py文件就是一个模块,那from_object()方法与from_pyfile()方法有什么区别呢?从源码汇总我们可以看出,from_pyfile()方法接受一个文件名作为参数,我们可以认为,使用from_pyfile()方法读取配置时,我们只能直接将配置写在py文件中,而不能是写在py文件中定义的类。from_pyfile()方法思路就是传入一个py文件名,然后对文件进行解析,转为模块对象,调用from_object()方法对解析到的模块对象读取配置。

分析完了我们就来使用一下吧,settings.py文件内容如下:

DEBUG = True
TESTING = True
A = 123

读取配置:

from flask import Flask

app = Flask(__name__)
app.config.from_pyfile('settings.py')
print('DEBUG:', app.config.get('DEBUG'))
print('TESTING:', app.config.get('TESTING'))
print('A:', app.config.get('A'))

输出:

DEBUG: True

TESTING: True

A: 123

6 配置方式4-字典元组:from_mapping

这种方式是以元组或者字典的形式来管理配置,先来看看源码:

def from_mapping(self, *mapping, **kwargs):
mappings = [] # 用于存放待会儿解析出来的数据
if len(mapping) == 1: # 只能接受一个位置参数
if hasattr(mapping[0], "items"): # 如果是字典
mappings.append(mapping[0].items()) # 以(key, value)的形式放到mappings列表中
else:
mappings.append(mapping[0]) # 如果不是字典,直接放到mappings列表中
elif len(mapping) > 1: # 如果位置参数数量多于1个就会抛出异常
raise TypeError(
"expected at most 1 positional argument, got %d" % len(mapping)
)
mappings.append(kwargs.items()) # 对于关键字参数,则直接以(key, vlaue)形式放到mappings列表中
for mapping in mappings:
for (key, value) in mapping:
if key.isupper(): # 如果key是大写的,才会修改配置
self[key] = value
return True

就算看完了上面的代码解析,你也许知道了代码做了什么,但是却还不知道为什么这么做,来,我们尝试使用一下也许你就明白了:

from flask import Flask

app = Flask(__name__)
tuple_config = (
('DEBUG', True),
('TESTING', False)
)
dict_config = {
'DEBUG': True,
'TESTING': False
}
# app.config.from_mapping(tuple_config, A=123, B=456) # 使用元组
app.config.from_mapping(dict_config, A=123, B=456) # 使用字典
print('DEBUG:', app.config.get('DEBUG'))
print('DEBUG:', app.config.get('DEBUG'))
print('TESTING:', app.config.get('TESTING'))
print('A:', app.config.get('A'))
print('B:', app.config.get('B'))

上面代码中,我们定义了元组和字典(实际开发中最好在一个专门的模块中定义),使用元组进行配置的方法我注释掉了,运行效果都是一样的,你可以调试一下,加深理解源码。输入如下:

DEBUG: True

TESTING: False

A: 123

B: 456

7 配置方式5-json文件:from_json

如果你喜欢用json文件的方式来管理配置,那么,from_json()方法刚好适合你,我们来了看看这个方法的实现:

def from_json(self, filename, silent=False):
filename = os.path.join(self.root_path, filename) # 拼接路径
try:
with open(filename) as json_file: # 读取文件
obj = json.loads(json_file.read()) # 对文件内容字符串反序列化成字典
except IOError as e:
if silent and e.errno in (errno.ENOENT, errno.EISDIR):
return False
e.strerror = "Unable to load configuration file (%s)" % e.strerror
raise
return self.from_mapping(obj) # 调用上面介绍过的from_mapping方法

如果你理解了上面from_mapping()方法,那么,对于这个from_json()方法也很好理解了,因为from_json()方法只是读取json文件成字符串后反序列化成字段传入from_mapping()。

在使用from_json()方法之前,我们得先创建一个json文件来写入配置,假设文件名为settings.json,内容如下:

{
"DEBUG": true,
"TESTING": false,
"A": 123
}

使用方法:

from flask import Flask

app = Flask(__name__)

app.config.from_json('settings.json') #传入json文件
print('DEBUG:', app.config.get('DEBUG'))
print('TESTING:', app.config.get('TESTING'))
print('A:', app.config.get('A'))

输出:

DEBUG: True

TESTING: False

A: 123

8 配置方式6-系统环境变量:from_envvar

from_envvar()是从系统环境变量中读取配置,源码如下:

def from_envvar(self, variable_name, silent=False):
rv = os.environ.get(variable_name) # 读取指定的系统环境变量
if not rv: # 如果系统环境中并没有配置这一变量
if silent:
return False
raise RuntimeError(
"The environment variable %r is not set "
"and as such configuration could not be "
"loaded. Set this variable and make it "
"point to a configuration file" % variable_name
)
return self.from_pyfile(rv, silent=silent) # 调用from_pyfile方法

这个方法的源码应该是上面介绍过的这么多方法中最好理解的了。从源码中可以看出,这个方法的功能就是根据传入的variable_name,去系统环境中读取变量名为variable_name的环境变量,而这个变量的值必须是一个py文件的完整路径,因为在最后是调用from_pyfile()方法出导入配置的,我相信,只要你会使用from_pyfile()方法,就会使用这个方法,毕竟搞IT的,配置个环境变量应该都会。

9 总结

本文结合对Flask源码的分析总结分析了Flask配置管理的使用方法。Flask通过Config配置类中的6个方法,对应得提供了6种配管管理方式。本文通过代码实例演示每种方式的使用方法,还深度剖析了源码,总结思路,相信你不进可以知其然还可以知其所以然。

 
 

从源码看Flask框架配置管理的更多相关文章

  1. 从源码看JDK提供的线程池(ThreadPoolExecutor)

    一丶什么是线程池 (1)博主在听到线程池三个字的时候第一个想法就是数据库连接池,回忆一下,我们在学JavaWeb的时候怎么理解数据库连接池的,数据库创建连接和关闭连接是一个比较耗费资源的事情,对于那些 ...

  2. Spring5源码解析-Spring框架中的单例和原型bean

    Spring5源码解析-Spring框架中的单例和原型bean 最近一直有问我单例和原型bean的一些原理性问题,这里就开一篇来说说的 通过Spring中的依赖注入极大方便了我们的开发.在xml通过& ...

  3. 从微信小程序开发者工具源码看实现原理(一)- - 小程序架构设计

    使用微信小程序开发已经很长时间了,对小程序开发已经相当熟练了:但是作为一名对技术有追求的前端开发,仅仅熟练掌握小程序的开发感觉还是不够的,我们应该更进一步的去理解其背后实现的原理以及对应的考量,这可能 ...

  4. Alink漫谈(二) : 从源码看机器学习平台Alink设计和架构

    Alink漫谈(二) : 从源码看机器学习平台Alink设计和架构 目录 Alink漫谈(二) : 从源码看机器学习平台Alink设计和架构 0x00 摘要 0x01 Alink设计原则 0x02 A ...

  5. 从linux源码看socket的阻塞和非阻塞

    从linux源码看socket的阻塞和非阻塞 笔者一直觉得如果能知道从应用到框架再到操作系统的每一处代码,是一件Exciting的事情. 大部分高性能网络框架采用的是非阻塞模式.笔者这次就从linux ...

  6. 从Linux源码看Socket(TCP)Client端的Connect

    从Linux源码看Socket(TCP)Client端的Connect 前言 笔者一直觉得如果能知道从应用到框架再到操作系统的每一处代码,是一件Exciting的事情. 今天笔者就来从Linux源码的 ...

  7. 从Linux源码看Socket(TCP)的bind

    从Linux源码看Socket(TCP)的bind 前言 笔者一直觉得如果能知道从应用到框架再到操作系统的每一处代码,是一件Exciting的事情. 今天笔者就来从Linux源码的角度看下Server ...

  8. 从Linux源码看Socket(TCP)的listen及连接队列

    从Linux源码看Socket(TCP)的listen及连接队列 前言 笔者一直觉得如果能知道从应用到框架再到操作系统的每一处代码,是一件Exciting的事情. 今天笔者就来从Linux源码的角度看 ...

  9. 从Linux源码看Socket(TCP)的accept

    从Linux源码看Socket(TCP)的accept 前言 笔者一直觉得如果能知道从应用到框架再到操作系统的每一处代码,是一件Exciting的事情. 今天笔者就从Linux源码的角度看下Serve ...

随机推荐

  1. 前端从零开始学习Graphql

    学习本姿势需要电脑装有node,vue-cli相关环境,以及要有node,express,koa,vue相关基础 本文相关demo的github地址: node服务:https://github.co ...

  2. PCB 板边倒圆角的实现方法(基本算法一)

    PCB外形是直角时外形时,通常工程制作时,外是直角或尖角的地方倒圆角,主要是为了防止板边容易划伤板且容易扎伤人 所以当客户没有特殊要求时,PCB外形是直角时一般会默认倒角0.5mm圆角(如下图所示) ...

  3. Impala集成C3P0的连接方式

    1. 概述 Impala是Cloudera公司主导开发的新型查询系统,它提供SQL语义,能查询存储在Hadoop的HDFS和HBase中的PB级大数据.已有的Hive系统虽然也提供了SQL语义,但由于 ...

  4. MySQL 8.0 information_schema系统库的改进

    目录 information_schema有何用? mysql8.0 之前的查询方式 mysql8.0 开始的查询方式 测试5.7和8.0不同版本访问I_S库的性能 结论 information_sc ...

  5. shell脚本常见错误一二三

    1.$'\r': 未找到命令的解决 2.: 不是有效的标识符h: 3.cd "$path"/webapps/ROOT 不能正常进入ROOT文件夹,$path并未与后面的字符结合起来 ...

  6. wincc C脚本如何调用第三方动态链接库dll

    就Wincc本身脚本功能而言并不强大,但是wincc 脚本提供了第三方接口,如通用的Kernel32.dll,User32.dll,Gdi32.dll,大家如果对这些API接口感兴趣,可网上查找关于w ...

  7. STM32F0_HAL库驱动描述——基于F1的USART串口IT中断实现解析

    从原子F103 HAL库基础串口例程来看HAL程序结构: 从main函数开始,首先是HAL库两个函数的初始化: HAL_Init(): Stm32_Clock_Init(RCC_PLL_MUL9); ...

  8. Netty-Pipeline深度解析

    首先我们知道,在NIO网络编程模型中,IO操作直接和channel相关,比如客户端的请求连接,或者向服务端发送数据, 服务端都要从客户端的channel获取这个数据 那么channelPipeline ...

  9. Scala数据结构

    Scala数据结构 主要的集合特质 Scala同时支持可变集合和不可变集合,优先采用不可变集合.集合主要分为三大类:序列(List),集(set),映射(map).所有的集合都扩展自Iterable特 ...

  10. spark 源码分析之十八 -- Spark存储体系剖析

    本篇文章主要剖析BlockManager相关的类以及总结Spark底层存储体系. 总述 先看 BlockManager相关类之间的关系如下: 我们从NettyRpcEnv 开始,做一下简单说明. Ne ...