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 种配管管理方式. 本文通过代码实例演示每种方式的使用方法, 还深度剖析了源码, 总结思路, 相信你不进可以知其然还可以知其所以然.
来源: https://www.cnblogs.com/chenhuabin/p/11346776.html