讀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-2025 CODEPRJ.COM