Requests源码阅读

库结构

.
├── adapters.py
├── api.py
├── auth.py
├── certs.py
├── compat.py
├── cookies.py
├── exceptions.py
├── help.py
├── hooks.py
├── __init__.py
├── _internal_utils.py
├── models.py
├── packages.py
├── sessions.py
├── status_codes.py
├── structures.py
├── utils.py
└── __version__.py

整体结构

这是我第一次看源码,版本为requests-2.18.4.说实话很吃力,有这几个原因

  1. 英文水平不够,有些注释理解起来吃力
  2. 引入了很多底层库和模块,不知道其作用
  3. 不知道从何看起,各个模块互相引用
  4. 有一些兼容py2,py3的代码
  5. 对HTTP协议传输时的各种内容不了解(即使我懂计网)

因为无法从细节处理解,所以我决定从宏观层面了解requests是如何运作的。首先需要找到requests的入口,不过入口很多,比如get,post,head等等,但其实他们最终都是调用了requests.request()方法,只是在调用时传入了不同的HTTP请求方法名。因为模块很多,且相互之间互相引用,很容易看了后面,忘了前面,所以我在草稿纸上从入口开始,画了一个大概的框架图,一直到返回response,但是不好上传图片,用文字描述一下。

  1. 首先入口函数是requests.request(),接受一个method, url, **kwargs,从而可以发送http的各种请求,相关配置信息比如cookie, 请求体data,身份认证等也在这时传入

  2. request()方法调用了sessions.py中的Session类的request()方法,接下来提到的request()方法都是Session类的

  3. 该request()方法先生成一个models.py中Request对象,然后用这个对象作为参数生成一个PreparedRequest对象,然后request()方法调用Session的send()方法得到响应,并返回。

  4. 第三步中的send方法,先生成了一个HTTPAdapter对象,然后调用了HTTPAdapter.send()取得响应,并返回。

框架大概就是这样,对所有的细节都没有考虑。

挑几个我看的懂的模块讲讲

init.py

  1. 检查了urllib3和chardet的版本,使用assert来保证版本正确
  2. 引入了requests对外开放的接口,基本信息

version.py

保存版本,作者等信息

api.py

定义了库的对外接口,包括request,get,options,head,post,put,patch,delete这些接口,除了request,其他方法最终都还是调用request方法,类似于request('get', url', **kwargs),而request是调用了sessions.py中的Session类的request方法,抛开这个类的其他功能不谈,这个类是一个上下文管理器,保证请求后连接的关闭操作。

sessions.py

这个模块最重要的就是Session类,继承了SessionRedirectMixin类。初始化Session类的时候,为请求的一些选项赋值默认信息。前面说过了Session类是一个上下文管理器,因为其定义了__enter____exit__两个方法。

Session类与api中一样,也定义了get,post,head这些方法,最终也都是调用了Session类的request方法。request方法接受api中接口传入的参数,并用这些参数创建一个Request对象,但是这个对象在我看来似乎没做什么事,只是用来创建PreparedRequest()对象,那为什么不直接创建PrepardRequest对象呢?PreparedRequest对象对传入的参数进行了一些处理。之后把这个对象和一些其他参数作为参数调用Session的send方法,send方法调用了HTTPAdapter的send方法,获得最终对象,展开了讲太多了,就介绍的简单了写。

compat.py

因为py2,py3的标准库和内置的编码方式有变化,这个模块是根据python版本不同引入不同的标准库,并修改一定的标准库名字。。

structures.py

定义了两个继承自dict的类,分别为CaseInsensitiveDict和LookupDict。第一个类的存储方式很奇特,内部定义了一个OrderedDict数据对象_store,所有的操作都是对这个对象操作,_store以传入的Key的小写作为key,(key,value)这个元祖作为值。是为了将所有形式的key,无论大小写,只要字母是一样的,就只存一遍。举个例子

header = CaseInsensitiveDict()
header['accEPT'] = 'json'
# _store['accept'] = ('accEPT', 'json')
header['Accept'] = 'json'
# _store['accept'] = ('Accept', 'json')

其他模块

还有很多模块,就不一一介绍了,全都介绍篇幅会太大了。

收获

Python最佳实践

  1. 引入同一个包内的其他py文件
from . import a, b
from .a import function
  1. 同一个包引入大量模块,函数
from xxx import (
    xxxx, xxxx, xxxx,
    xxxx, xxxx, xxxx
)
  1. 函数,方法说明
def request(method, url, **kwargs):
    """函数的功能,总体概括

    :param method: xxxxx.
    :param url: xxxxx.
    :param xxx: xxxx.
    :return: xxxx
    :rtype: xxx

    Usage::

      >>> import requests
      >>> req = requests.request('GET', 'url')
      
    """

最佳实践里存在一个困惑,那就是flake8要求每行字符个数不大于80,就算放宽要求到达100,也有些许注释超过这个数值。

编码问题

编码问题是个很头疼的问题,requests库里充斥着大量的编码转换功能,主要有两个原因

  1. py2, py3的字符串编码方式不同(不了解py2)
  2. http协议的各部分与python字符串编码的冲突

因为计算机保存字符都是0,1串,把同一个我们能看到的抽象的字符标示成这种0,1串的话,根据不同的编码方式,就可以转换成不同的0,1串,但有时候这样不同的编码方式需要在一起使用,就需要统一成一种方式。

在Python3中,str默认是unicode编码,而unicode这种编码方式支持的字符个数比较多,所以可以作为一种中间编码,在两种编码方式中转换。即假设有A,B两种不同编码,一个A类型的串需要编码为B类型的串,则先需要将A类型的串解码成unicode串,在将这个unicode串编码成B类型的串。在Python中decode为解码,encode为编码。

string = '中国人'
print(string.encode('utf-8'))
# b'\xe4\xb8\xad\xe5\x9b\xbd\xe4\xba\xba'
print(string.encode('gbk'))
# b'\xd6\xd0\xb9\xfa\xc8\xcb'
temp = b'\xd6\xd0\xb9\xfa\xc8\xcb'
print(temp.decode('gbk'))
# '中国人'
print(temp.decode('utf-8'))
# UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd6 in position 0: invalid continuation byte

异常捕获

库中有一个模块专门定义了一些异常,代码中也经常出现try...except...的组合,甚至是嵌套组合,捕捉的异常类型非常的细,防御性编程是让代码稳健运行必不可少的部分,但捕捉的太粗太泛就会导致错误定位不精准。

魔法方法

Python中有很多魔法方法,那些在类中以__开头和结尾的方法,在代码中出现很多次,几乎每一个模块都有,而我平时很少用到,在日常写代码的时候很少去继承Python内置的对象,除了object,在解决问题的方法上也有一些差距。

getattr, hasattr

这些也是代码中高频语法,但是还不能理解相对于直接使用Obj.value有什么优势。

总结

差不多是把源代码都看了一遍,但很多地方都是一眼扫过去,没有关注它的作用,更多是关注他的代码风格。可以说差距真的是非常大,差距来自对问题的解决方法上以及对python语言本身的理解上。

第一次读源码肯定是比较难的,所以应该找一些小一点的,容易理清脉络的库或项目,然后从最外层的框架入手,从宏观上了解每一部分的作用,按照这个框架去深入,而不能一个模块一个模块独立开来阅读。看不懂的地方,有的多看几遍后会明了一些,有的可能还是不懂,那么就不要纠结,继续看下去,不能奢求读完源码就学会了作者的所有技术,但对自己肯定是有潜移默化的影响。

你可能感兴趣的:(Requests源码阅读)