個人學習筆記,水平有限。如果理解錯誤的地方,請大家指出來,謝謝!第一次寫文章,發現好累--!。
??莫名其妙在第一份工作中使用了從來沒有接觸過的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__ 雙下劃線的文本,而且又不想放在代碼塊的情況。可以把在下劃線前面加上/,這樣就不會被吃了。
新聞熱點
疑難解答