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. scrapy基础知识之 scrapy 三种模拟登录策略:

    注意:模拟登陆时,必须保证settings.py里的 COOKIES_ENABLED (Cookies中间件) 处于开启状态 COOKIES_ENABLED = True或 # COOKIES_ENA ...

  2. 热度3年猛增20倍,Serverless&云开发的技术架构全解析

    『 作为一个不断发展的新兴技术, Serverless 热度的制高点已然到来.』 或许,Google Trends 所显示的 3 年猛增 20 倍的" Serverless " 搜 ...

  3. 通讯(tarjan缩点)(20190716NOIP模拟测试4)

    B. 通讯   题目类型:传统 评测方式:文本比较  内存限制:256 MiB 时间限制:1000 ms 标准输入输出 题目描述 “这一切都是命运石之门的选择.” 试图研制时间机器的机关SERN截获了 ...

  4. KVM :vnc 远程控制kvm创建虚拟机

    一.vnc远程控制服务器 前期准备: 1.编辑/etc/hosts vi /etc/hosts 10.1.16.32 kvm 2.关闭防火墙 service iptables stop 3.关闭sel ...

  5. MyBatis从入门到精通(2):MyBatis XML方式的基本用法

    本章将通过完成权限管理的常见业务来学习 MyBatis XML方式的基本用法 2.1一个简单的权限控制需求 权限管理的需求: 一个用户拥有若干角色,一个角色拥有若干权限,权限就是对某个模块资源的某种操 ...

  6. Day1 -Python program

    采用python 3.5 用PyCharm编译 第一串代码 print ("hello,world!") 练习1 输入一个用户名和密码,如果输入正确,就欢迎登陆,否则就显示错误. ...

  7. 吐槽下Excel的十大不规范使用问题

    Excel是个老少咸宜的软件工具,这是不争的事实,无论哪个级别的用户,都能在乐在其中.但问题是太多的人群因为不懂得正确的使用姿势,硬生生地把Excel玩得让人啼笑皆非,同样留给接手者一个难堪无比的烂摊 ...

  8. python面向过程编程 - ATM

    前面程序整合加自定义日志 1.文件摆放 ├── xxxx │ ├── src.py │ └── fil_mode.py │ └── data_time.py │ └── loading.py │ └─ ...

  9. python小技巧 将二元列表转为一元列表

    list=[num for row in nums for num in row]

  10. Linux命令大全(简)

    rm--删除文件和目录   -i 删除一个已存在的文件前,提示用户进行确认.   -r 递归的删除目录. mkdir--创建目录 cp--复制文件和目录   -i 在覆盖一个已存在的目录前,提示用户进 ...