讀Flask源代碼學習Python--config原理


讀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__


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM