读Flask源代码学习Python--config原理

时间:2023-12-12 22:39:56

读Flask源代码学习Python--config原理

个人学习笔记,水平有限。如果理解错误的地方,请大家指出来,谢谢!第一次写文章,发现好累--!。

起因

  莫名其妙在第一份工作中使用了从来没有接触过的Python,从那之后就对Python有了莫名其妙的好感。前段时间用了Flask做了几个不大不小的项目,项目过程中学到不少以前没注意到的知识点。于是就有了这个系列,希望最后能坚持完成。

Flask是什么?

  根据Flask官网介绍: Flask 是一个用于 Python 的微型网络开发框架。

Flask, HelloWorld!

from flask import Flask
app = Flask(__name__) @app.route("/")
def index():
return "Flask, HelloWorld!" if __name__ == "__main__":
app.run(host="localhost", port=5000, debug=True)

这个系列我会怎样读Flask源码?

  我会根据一个问题的回答顺序进行来进行这个系列的文章。该问题地址

  1. config原理。
  2. import原理。
  3. WSGI接口调用。
  4. 路由原理。
  5. 理解session。
  6. 理解threading.local。
  7. 理解flask自己封装的thread local。
  8. 理解g和request。
  9. 理解app context和request context。

  后续如果个人水平提高了,会从更高的层面继续这个系列。

根据官方教程, Flask中常见Config用法有以下几种。

  1. 直接赋值。
  2. 通过config的update方法一次更新多个属性值。
  3. 部分配置值可以通过属性赋值。
  4. 通过文件读取初始化config信息。

直接赋值

app = Flask(__name__)
app.config['DEBUG'] = True

  从中可以猜测config可能是一个字典或者至少提供一个通过key获取对应值的方法。

class Flask(_PackageBoundObject):
#: The class that is used for the ``config`` attribute of this app.
#: Defaults to :class:`~flask.Config`.
#:
#: Example use cases for a custom class:
#:
#: 1. Default values for certain config options.
#: 2. Access to config values through attributes in addition to keys.
#:
#: .. versionadded:: 1.0
config_class = Config def __init__(self, import_name, static_path=None, static_url_path=None,
static_folder='static', template_folder='templates',
instance_path=None, instance_relative_config=False,
root_path=None):
self.config = self.make_config(instance_relative_config) def make_config(self, instance_relative=False):
"""Used to create the config attribute by the Flask constructor.
The `instance_relative` parameter is passed in from the constructor
of Flask (there named `instance_relative_config`) and indicates if
the config should be relative to the instance path or the root path
of the application. .. versionadded:: 0.8
"""
root_path = self.root_path
if instance_relative:
root_path = self.instance_path
return self.config_class(root_path, self.default_config) def from_envvar(self, variable_name, silent=False):
pass def from_pyfile(self, filename, silent=False):
pass def from_object(self, obj):
pass def from_json(self, filename, silent=False):
pass def from_mapping(self, *mapping, **kwargs):
pass def get_namespace(self, namespace, lowercase=True, trim_namespace=True):
pass

  这是最新的Flask源代码,它出现在app.py文件中的Flask的类定义中。config_class默认值为Config类,Config类又是什么?

  在config.py文件中,有下面这样的代码块。

class Config(dict):
"""Works exactly like a dict but provides ways to fill it from files
or special dictionaries.
""" def __init__(self, root_path, defaults=None):
dict.__init__(self, defaults or {})
self.root_path = root_path

  从Config的构造函数和make_config方法中,可以看到之前猜测config是一个字典的结论确实是正确的。

通过config的update方法一次更新多个属性值。

  有了app的config属性是一个字典这个事实,通过update方法更新多个属性值就很好理解了。

app.config.update(
DEBUG=True,
SECRET_KEY='maeorwmarfomferw')
知识点

  类属性(class attribute)和对象属性(object attribute,有时候也称为实例属性)

  注意,这里config_class是一个Flask的类属性,但是却好像“被当作对象属性”使用。

self.config_class(root_path, self.default_config)

  这又是怎么一回事?首先,先来看看下面这段示例代码。

def attribute_step_001():

    class ClassAttribute(object):

        class_attribute = "class_attribute_001"

        def __init__(self):
super(ClassAttribute, self).__init__()
self.object_attribute = "object_attribute_002" class ClassAttributeWithOverrideGetAttr(object): class_attribute = "class_attribute_001" def __init__(self):
super(ClassAttributeWithOverrideGetAttr, self).__init__()
self.class_attribute = "object_attribute_001"
self.object_attribute = "object_attribute_002" def __getattr__(self, attributename):
pass print("=== two ===")
two = ClassAttributeWithOverrideGetAttr()
print(two.__dict__)
print(two.class_attribute)
print(two.class_attribute_no_exist) print("=== one ===")
one = ClassAttribute()
print(one.__dict__)
print(one.class_attribute)
print(one.class_attribute_no_exist) #tip001这一行可以注释掉,重新运行一遍这样输出比较清晰。 attribute_step_001()

  执行之后输出的结果是:

=== two ===
{'class_attribute': 'object_attribute_001', 'object_attribute': 'object_attribute_002'}
object_attribute_001
None === one ===
{'object_attribute': 'object_attribute_002'}
class_attribute_001
Traceback (most recent call last):
File "D:\work_space\Dev_For_Python\flask_hello_world\application.py", line 128, in <module>
attribute_step_001()
File "D:\work_space\Dev_For_Python\flask_hello_world\application.py", line 125, in attribute_step_001
print(one.class_attribute_no_exist)
AttributeError: 'ClassAttribute' object has no attribute 'class_attribute_no_exist'

  从结果可以发现,当获取一个对象属性时,如果它没有进行设置的话,默认返回的是这个这个对象的类型的同名类属性的值(如果同名类属性存在的话)。

实际上Python获取一个属性值(objectname.attributename)的搜索流程是(新式类):

1:如果attributename对于对象来说是一个特殊的(比如是Python提供的)属性,直接返回它。

2:从对象的__dict__(obj.__dict__)查找。

3:从对象的类型的__dict__(obj.__class__.__dict__)中查找。

4:从对象类型的基类中obj.__class__.__bases__.__dict__查找,基类的基类,直到object。如果__bases__中有多个值,最后的结果依赖于Python的方法解析顺序(MRO)。

5:如果上面都没有找到并且对象的类定义中重写__getattr__(self, attributename)方法,那么会得到对应的返回值。如果没有重写__getattr__(self, attributename)方法,Python会抛出异常AttributeError。

部分配置值可以通过属性赋值

app = Flask(__name__)
app.debug = True

  这些配置值为什么能直接通过属性赋值?答案还是在Flask类和ConfigAttribute类的定义中。

#Flask类定义代码片段
class Flask(_PackageBoundObject):
debug = ConfigAttribute('DEBUG')
testing = ConfigAttribute('TESTING')
session_cookie_name = ConfigAttribute('SESSION_COOKIE_NAME')
send_file_max_age_default = ConfigAttribute('SEND_FILE_MAX_AGE_DEFAULT',
get_converter=_make_timedelta) def __init__(self, import_name, static_path=None, static_url_path=None,
static_folder='static', template_folder='templates',
instance_path=None, instance_relative_config=False,
root_path=None):
pass #ConfigAttribute类定义代码片段
class ConfigAttribute(object):
"""Makes an attribute forward to the config""" def __init__(self, name, get_converter=None):
self.__name__ = name
self.get_converter = get_converter def __get__(self, obj, type=None):
if obj is None:
return self
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

  在Flask类的类定义中可以看到debug被定义成一个ConfigAttribute对象。ConfigAttribute是什么东西?为什么通过app.config["debug"]=false和app.debug=false得到的效果是一样的?这得从Python的描述符说起。

知识点

  • Python描述符(也有文章称为描述器)

  什么是描述符?官方给的定义是:

In general, a descriptor is an object attribute with “binding behavior”, one whose attribute access has been overridden by methods in the descriptor protocol. Those methods are__get__(), __set__(), and __delete__(). If any of those methods are defined for an object, it is said to be a descriptor.

  简单的说,只要一个对象实现了描述符协议中的任一方法,那么就可以称之为描述符。描述符协议有哪些方法?

"""
描述符中定义的方法
"""
descriptor.__get__(self, obj, type=None) --> value
descriptor.__set__(self, obj, value) --> None
descriptor.__delete__(self, obj) --> None

  这是描述符协议的所有方法。一个对象只要重写了上面任意一个方法就可以称之为描述符。这里还有几个概念需要提的是,如果一个对象同时定义了__get__()__set__(),它叫做数据描述符(data descriptor)。仅定义了__get__()的描述符叫非数据描述符(non-data descriptor)。

"""
这是描述符的示例代码,通过代码了解下描述符。
"""
class DataDescriptor(object):
"""
这是一个数据描述符
""" def __init__(self):
pass def __get__(self, obj, objtype):
return "value from DataDescriptor" def __set__(self, obj, val):
pass def __delete__(self, obj):
pass class NoDataDescriptor(object):
"""
这是一个非数据描述符
""" def __init__(self):
pass def __get__(self, obj, objtype):
return "value from DataDescriptor" def attribute_test_001(): class ClassAttributeWithOverrideGetAttr(object): class_attribute = "class_attribute_001"
class_attribute2 = NoDataDescriptor() def __init__(self):
super(ClassAttributeWithOverrideGetAttr, self).__init__()
self.class_attribute = "object_attribute_001"
self.object_attribute = "object_attribute_002"
self.class_attribute2 = "object_attribute_003" def __getattr__(self, attributename):
return "value from __getattr__" class ClassAttribute(object): class_attribute = "class_attribute_001"
class_attribute2 = DataDescriptor() def __init__(self):
super(ClassAttribute, self).__init__()
self.object_attribute = "object_attribute_001"
self.class_attribute2 = "object_attribute_002" print("=== ClassAttributeWithOverrideGetAttr ===")
a = ClassAttributeWithOverrideGetAttr()
print("[a01]: a.__dict__ = ", a.__dict__)
print("[a02]: a.__class__.__dict__ = ", a.__class__.__dict__)
print("[a03]: a.class_attribute = ", a.class_attribute)
print("[a04]: a.class_attribute2 = ", a.class_attribute2)
print("[a05]: a.class_attribute_no_exist = ", a.class_attribute_no_exist) print("\r\n=== ClassAttribute ===")
b = ClassAttribute()
print("[b01]: b.__dict__ = ", b.__dict__)
print("[b02]: b.__class__.__dict__ = ", b.__class__.__dict__)
print("[b03]: b.class_attribute = ", b.class_attribute)
print("[b04]: b.class_attribute2 = ", b.class_attribute2)
print("[b05]: b.class_attribute_no_exist = ", b.class_attribute_no_exist)
attribute_test_001()

  代码的输出结果是:

=== ClassAttributeWithOverrideGetAttr ===
[a01]: a.__dict__ = {'class_attribute2': 'object_attribute_003', 'class_attribute': 'object_attribute_001', 'object_attribute': 'object_attribute_002'}
[a02]: a.__class__.__dict__ = {'__dict__': <attribute '__dict__' of 'ClassAttributeWithOverrideGetAttr' objects>, '__weakref__': <attribute '__weakref__' of 'ClassAttributeWithOverrideGetAttr' objects>, '__getattr__': <function attribute_test_001.<locals>.ClassAttributeWithOverrideGetAttr.__getattr__ at 0x01929F60>, '__module__': '__main__', '__doc__': None, 'class_attribute2': <__main__.NoDataDescriptor object at 0x0192ED70>, 'class_attribute': 'class_attribute_001', '__init__': <function attribute_test_001.<locals>.ClassAttributeWithOverrideGetAttr.__init__ at 0x01929ED0>}
[a03]: a.class_attribute = object_attribute_001
[a04]: a.class_attribute2 = object_attribute_003
[a05]: a.class_attribute_no_exist = value from __getattr__ === ClassAttribute ===
[b01]: b.__dict__ = {'object_attribute': 'object_attribute_001'}
[b02]: b.__class__.__dict__ = {'__dict__': <attribute '__dict__' of 'ClassAttribute' objects>, '__weakref__': <attribute '__weakref__' of 'ClassAttribute' objects>, '__module__': '__main__', '__doc__': None, 'class_attribute2': <__main__.DataDescriptor object at 0x0192EDD0>, 'class_attribute': 'class_attribute_001', '__init__': <function attribute_test_001.<locals>.ClassAttribute.__init__ at 0x01929FA8>}
[b03]: b.class_attribute = class_attribute_001
[b04]: b.class_attribute2 = value from DataDescriptor
Traceback (most recent call last):
File "D:\work_space\Dev_For_Python\flask_hello_world\hello.py", line 104, in <module>
attribute_test_001()
File "D:\work_space\Dev_For_Python\flask_hello_world\hello.py", line 101, in attribute_test_001
print("[b05]: b.class_attribute_no_exist = ", b.class_attribute_no_exist)
AttributeError: 'ClassAttribute' object has no attribute 'class_attribute_no_exist'
[Finished in 0.1s]

从两组输出我们可以得出的结论有:

1: 对比a01, a02, a03 ===> 实例字典和类属性中都存在同样key的时候,实例字典(obj.__dict__) > 类属性(obj.__class__.__dict__)

2: 对比b01, b02, b03 ===> 实例字典不存在key的时候,会返回同名key的类属性的值。

3: 对比a05, b05 ===> 实例字典和类属性都不存在key的时候,会返回重写的(__getattr__)函数的返回值,如果没有重写的话,会抛出异常AttributeError。

4: 对比a04, a04 ===> 实例字典和类属性都存在key的时候,数据描述符 > 实例字典(obj.__dict__) > 非数据描述符。

  描述符的调用逻辑。

  当Python获取一个属性时(objectname.attributename),** 发现要获取的属性是描述符时 **,它的搜索流程会有所改变,转而调用描述的方法。注意,描述符只适用于新式的类和对象。

  1. 如果attributename对于对象来说是一个特殊的(比如是Python提供的)属性,直接返回它。
  2. objectname.__class__.__dict__查找attributename,如果找到并且attributename是一个** 数据描述符 **,那么就返回数据描述符的执行结果。(objectname.__class__.__dict__["attributename"].__get__(objectname, type(objectname)))。在objectname.__class__全部基类中进行同样的搜索。
  3. 从对象的__dict__(objectname.__dict__)查找,找到就返回,不管是否为数据描述符。唯一的区别是,如果是数据描述符就返回描述符(__get__逻辑的返回值)。
  4. 从对象的类型的__dict__(objectname.__class__.__dict__)中查找。
  5. 从对象类型的基类中objectname.__class__.__bases__.__dict__查找,找到就返回,不管是否为数据描述符。唯一的区别是,如果是非数据描述符就返回描述符(__get__逻辑的返回值)。PS:这里不用考虑搜索到数据描述符的情况,因为第二步已经把所有数据描述符的情况考虑在内了。
  6. 如果上面都没有找到并且对象的类定义中重写__getattr__(self, attributename)方法,那么会得到对应的返回值。如果没有重写__getattr__(self, attributename)方法,Python会抛出异常AttributeError。

  通过上面的知识点,可以清楚的知道,首先app.debug是一个数据描述符,其次:当通过app.debug = True对配置值就行修改的时候,实际上调用的是描述符的逻辑type(app).__dict__["debug"].__get__(app, type(app)),最后通过ConfigAttribute中重写的__get__逻辑,可以看出还是修改了app.config字典中key为debug的值。

  最后,对Python涉及到的几点进行总结。

1:在没有描述符出现的的情况下,实例字典(obj.__dict__) > 类属性(obj.__class__.__dict__) > __getattr__()方法 > 抛出异常AttributeError

2:数据描述符 > 实例字典(obj.__dict__) > 非数据描述符。

3:Python中有几个内置的描述符:函数,属性(property), 静态方法(static method) 感兴趣的自行查找相关文章研究下。

通过文件读取初始化config信息。

app = Flask(__name__)
app.config.from_object('yourapplication.default_settings')
app.config.from_envvar('YOURAPPLICATION_SETTINGS')

通过上面这几种方法初始化config信息的源代码都相对简单。个人觉得没有什么好分析的。

"""
这是Flask中通过文件或者对象初始化涉及到的源代码
"""
def from_envvar(self, variable_name, silent=False):
"""Loads a configuration from an environment variable pointing to
a configuration file. This is basically just a shortcut with nicer
error messages for this line of code:: app.config.from_pyfile(os.environ['YOURAPPLICATION_SETTINGS']) :param variable_name: name of the environment variable
:param silent: set to ``True`` if you want silent failure for missing
files.
:return: bool. ``True`` if able to load config, ``False`` otherwise.
"""
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) def from_pyfile(self, filename, silent=False):
"""Updates the values in the config from a Python file. This function
behaves as if the file was imported as module with the
:meth:`from_object` function. :param filename: the filename of the config. This can either be an
absolute filename or a filename relative to the
root path.
:param silent: set to ``True`` if you want silent failure for missing
files. .. versionadded:: 0.7
`silent` parameter.
"""
filename = os.path.join(self.root_path, filename)
d = types.ModuleType('config')
d.__file__ = filename
try:
with open(filename) as config_file:
exec(compile(config_file.read(), filename, 'exec'), d.__dict__)
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
self.from_object(d)
return True def from_object(self, obj):
"""Updates the values from the given object. An object can be of one
of the following two types: - a string: in this case the object with that name will be imported
- an actual object reference: that object is used directly Objects are usually either modules or classes. Just the uppercase variables in that object are stored in the config.
Example usage:: app.config.from_object('yourapplication.default_config')
from yourapplication import default_config
app.config.from_object(default_config) You should not use this function to load the actual configuration but
rather configuration defaults. The actual config should be loaded
with :meth:`from_pyfile` and ideally from a location not within the
package because the package might be installed system wide. :param obj: an import name or object
"""
if isinstance(obj, string_types):
obj = import_string(obj)
for key in dir(obj):
if key.isupper():
self[key] = getattr(obj, key) def from_json(self, filename, silent=False):
"""Updates the values in the config from a JSON file. This function
behaves as if the JSON object was a dictionary and passed to the
:meth:`from_mapping` function. :param filename: the filename of the JSON file. This can either be an
absolute filename or a filename relative to the
root path.
:param silent: set to ``True`` if you want silent failure for missing
files. .. versionadded:: 1.0
"""
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) def from_mapping(self, *mapping, **kwargs):
"""Updates the config like :meth:`update` ignoring items with non-upper
keys. .. versionadded:: 1.0
"""
mappings = []
if len(mapping) == 1:
if hasattr(mapping[0], 'items'):
mappings.append(mapping[0].items())
else:
mappings.append(mapping[0])
elif len(mapping) > 1:
raise TypeError(
'expected at most 1 positional argument, got %d' % len(mapping)
)
mappings.append(kwargs.items())
for mapping in mappings:
for (key, value) in mapping:
if key.isupper():
self[key] = value
return True

在项目中如何使用初始化config?

推荐一篇相关文章

最后分享下写这篇文章学习到的几个Markdown的小技巧。

  1. 段落缩进使用.
&emsp;&emsp;
  1. 类似__set__, __delete__, __init__ 双下划线的文本,而且又不想放在代码块的情况。可以把在下划线前面加上\,这样就不会被吃了。

参考资料

  1. Descriptior HowTo Guide
  2. Python Types and Objects
  3. Python Attributes and Methods
  4. 编写高质量代码 改善Python程序的91个建议 (第58,59,60建议)这里有关于属性拦截和获取更详细的解读,当中涉及到__getattribute__, __getattr__