设为首页 收藏本站
查看: 572|回复: 0

[经验分享] 读Flask源代码学习Python

[复制链接]
累计签到:1 天
连续签到:1 天
发表于 2015-11-30 07:37:42 | 显示全部楼层 |阅读模式
读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源码?
    我会根据一个问题的回答顺序进行来进行这个系列的文章。该问题地址。


  • config原理。  

  • import原理。  

  • WSGI接口调用。  

  • 路由原理。  

  • 理解session。  

  • 理解threading.local。  

  • 理解flask自己封装的thread local。  

  • 理解g和request。  

  • 理解app context和request context。
    后续如果个人水平提高了,会从更高的层面继续这个系列。

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


  • 直接赋值。  

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

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

  • 通过文件读取初始化config信息。
直接赋值
  

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

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

class Flask(_PackageBoundObject):
  #: The>  #: Defaults to :class:`~flask.Config`.
  #:

  #: Example use cases for a custom>  #:
  #: 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>  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>  
  class_attribute = "class_attribute_001"
  

  def __init__(self):
  super(ClassAttribute, self).__init__()
  self.object_attribute = "object_attribute_002"
  


  class>  
  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 =>  print(two.__dict__)
  print(two.class_attribute)
  print(two.class_attribute_no_exist)
  

  print("=== one ===")

  one =>  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
  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>  

  
  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>  

  
  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("===>
  a =>  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===>  b =>  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()
  

    代码的输出结果是:
  

===>
[a01]: a.__dict__                 =  {'class_attribute2': 'object_attribute_003', 'class_attribute': 'object_attribute_001', 'object_attribute': 'object_attribute_002'}
  
[a02]: a.__class__.__dict__       =  {'__dict__': , '__weakref__': , '__getattr__': , '__module__': '__main__', '__doc__': None, 'class_attribute2': , 'class_attribute': 'class_attribute_001', '__init__': }
  
[a03]: a.class_attribute          =  object_attribute_001
  
[a04]: a.class_attribute2         =  object_attribute_003
  
[a05]: a.class_attribute_no_exist =  value from __getattr__
  

  


  
===>  
[b01]: b.__dict__                   =  {'object_attribute': 'object_attribute_001'}
  
[b02]: b.__class__.__dict__         =  {'__dict__': , '__weakref__': , '__module__': '__main__', '__doc__': None, 'class_attribute2': , 'class_attribute': 'class_attribute_001', '__init__': }

  
[b03]: b.class_attribute            = >  
[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
  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),** 发现要获取的属性是描述符时 **,它的搜索流程会有所改变,转而调用描述的方法。注意,描述符只适用于新式的类和对象。


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

  • 从objectname.__class__.__dict__查找attributename,如果找到并且attributename是一个** 数据描述符 **,那么就返回数据描述符的执行结果。(objectname.__class__.__dict__["attributename"].__get__(objectname, type(objectname)))。在objectname.__class__全部基类中进行同样的搜索。  

  • 从对象的__dict__(objectname.__dict__)查找,找到就返回,不管是否为数据描述符。唯一的区别是,如果是数据描述符就返回描述符(__get__逻辑的返回值)。  

  • 从对象的类型的__dict__(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) 感兴趣的自行查找相关文章研究下。

通过文件读取初始化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>  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>  

  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>  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>  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的小技巧。


  •   段落缩进使用.
      

      
  •   类似__set__, __delete__, __init__ 双下划线的文本,而且又不想放在代码块的情况。可以把在下划线前面加上\,这样就不会被吃了。

参考资料


  • Descriptior HowTo Guide  

  • Python Types and Objects  

  • Python Attributes and Methods  

  • 编写高质量代码 改善Python程序的91个建议 (第58,59,60建议)这里有关于属性拦截和获取更详细的解读,当中涉及到__getattribute__, __getattr__

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-145082-1-1.html 上篇帖子: Python 写Windows Service服务程序 下篇帖子: python做一个http接口测试框架
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表