个人学习笔记,水平有限。如果理解错误的地方,请大家指出来,谢谢!第一次写文章,发现好累--!。
莫名其妙在第一份工作中使用了从来没有接触过的Python,从那之后就对Python有了莫名其妙的好感。前段时间用了Flask做了几个不大不小的项目,项目过程中学到不少以前没注意到的知识点。于是就有了这个系列,希望最后能坚持完成。
根据Flask官网介绍: Flask 是一个用于 Python 的微型网络开发框架。
from flask import Flaskapp = Flask(__name__)@app.route("/")def index(): return "Flask, HelloWorld!"if __name__ == "__main__": app.run(host="localhost", port=5000, debug=True)
我会根据一个问题的回答顺序进行来进行这个系列的文章。该问题地址。
后续如果个人水平提高了,会从更高的层面继续这个系列。
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是一个字典的结论确实是正确的。
有了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_001None=== one ==={'object_attribute': 'object_attribute_002'}class_attribute_001Traceback (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的描述符说起。
什么是描述符?官方给的定义是:
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) --> valuedescriptor.__set__(self, obj, value) --> Nonedescriptor.__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): passclass 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 DataDescriptorTraceback (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
),** 发现要获取的属性是描述符时 **,它的搜索流程会有所改变,转而调用描述的方法。注意,描述符只适用于新式的类和对象。
objectname.__class__.__dict__
查找attributename,如果找到并且attributename是一个** 数据描述符 **,那么就返回数据描述符的执行结果。(objectname.__class__.__dict__["attributename"].__get__(objectname, type(objectname))
)。在objectname.__class__
全部基类中进行同样的搜索。objectname.__dict__
)查找,找到就返回,不管是否为数据描述符。唯一的区别是,如果是数据描述符就返回描述符(__get__
逻辑的返回值)。objectname.__class__.__dict__
)中查找。objectname.__class__.__bases__.__dict__
查找,找到就返回,不管是否为数据描述符。唯一的区别是,如果是非数据描述符就返回描述符(__get__
逻辑的返回值)。PS:这里不用考虑搜索到数据描述符的情况,因为第二步已经把所有数据描述符的情况考虑在内了。__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) 感兴趣的自行查找相关文章研究下。
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
推荐一篇相关文章
段落缩进使用.
  
类似__set__, __delete__, __init__ 双下划线的文本,而且又不想放在代码块的情况。可以把在下划线前面加上/,这样就不会被吃了。
新闻热点
疑难解答