报错提示如下
报错解释
警告:在ssl已经导入之后再对ssl进行猴补丁可能会导致错误,包括Python 3.6中的递归错误。它还可能悄无声息地导致Python 3.7上的不正确行为。请早些时候monkey-patch。见github.com/gevent/geve…。直接导入的模块(没有打补丁)
于是乎上GitHub查看相关的issues
这里说明了要导入其他模块之前必须要导入猴子补丁
下面来描述以下猴子补丁的相关信息
猴子补丁的由来
猴子补丁的这个叫法起源于Zope框架,大家在修正Zope的Bug的时候经常在程序后面追加更新部分,这些被称作是“杂牌军补丁(guerillapatch)”,后来guerilla就渐渐的写成了gorllia(猩猩),再后来就写了monkey(猴子),所以猴子补丁的叫法是这么莫名其妙的得来的。后来在动态语言中,不改变源代码而对功能进行追加和变更,统称为“猴子补丁”。所以猴子补丁并不是Python中专有的。猴子补丁这种东西充分利用了动态语言的灵活性,可以对现有的语言Api进行追加,替换,修改Bug,甚至性能优化等等。
什么时候用到?
如果都是自己写的代码,Monkey Patch 就毫无意义了,直接改源码就可以。Monkey Patch 的主要用途,在于 源码不宜直接修改。 比如你要修正一个第三方 module 的 bug 或者进行特定的修正、扩展,通常来说有下面几种做法:
- 1、直接把源 package/module,复制一份到当前项目中,再改源码;但不推荐,因为会导致当前的项目代码管理上的混乱。
- 2、向源作者提 pull request,以修正 bug 或者其他;应该如此,但未必能联系到对方,联系到了对方未必修改,修改了未必很快能用上。这个时候 Monkey Patch 的价值就出来了,不用改原始的 module 源码,就能达到自己期望的效果。
如何打补丁
一个实际例子更帮助理解,看代码之前,我们再回顾一下,之前反复强调** 『 module 也是一个变量』**。
import re
old_match = re.match # 保留旧函数,后面可以直接用
def new_match(*args, **kwargs):
print('re.match is running') # 增加自己的逻辑
return old_match(*args, **kwargs) # 调用回旧函数
# 一个全新的函数
def hello(*args, **kwargs):
print(args, kwargs)
re.match = new_match # 替换掉原来的旧属性(函数)
re.hello = hello # 赋予一个新属性(函数)
直接运行的效果:
>>> re.match('.*', 'abc')
re.match is running # 如果没有 patch,此行不会输出
<re.Match object; span=(0, 3), match='abc'>
>>> re.hello(123) # 如果没有 patch,调用这个函数会报错
(123,) {}
前面的问题使用猴子补丁的方式
gevent能够修改标准库里面大部分的阻塞式系统调用,包括socket、ssl、threading和 select等模块,而变为协作式运行。也就是通过猴子补丁的monkey.patch_xxx()来将python标准库中模块或函数改成gevent中的响应的具有协程的协作式对象。这样在不改变原有代码的情况下,将应用的阻塞式方法,变成协程式的,要是先加载其他的模块,而不先打猴子补丁的话,其他模块就先使用的是阻塞式的系统调用,而不是协作式运行。
猴子补丁使用时的注意事项
猴子补丁的功能很强大,但是也带来了很多的风险,尤其是像gevent这种直接进行API替换的补丁,整个Python进程所使用的模块都会被替换,可能自己的代码能hold住,但是其它第三方库,有时候问题并不好排查,即使排查出来也是很棘手,所以,就像松本建议的那样,如果要使用猴子补丁,那么只是做功能追加,尽量避免大规模的API覆盖。