01前言
不得不说距离Python2.7退役的日子还剩下不到2个月的时间了,面对堆积成三的Django项目遗留代码还是少动为妙。
真心要感谢six库的作者让迁移的工作减少了些困难,虽然实现的代码写的挺啰嗦的,几年前吓退了初学的我。而今天,打开揭开six库看似神秘的兼容过程。
02神秘six前传
为什么将这个库命名为six,在官方上有这样一段说明,因为要从2过渡3,其中2+3=5,而这个名字已经被zope项目的five库占了先机。而2 * 3=6,乘法看似来更强大,于是就命名为six了。
呵呵,我真没能领悟作者的幽默。
03神秘six正传
six库1个常见的用法类似如下:
from six.queue import Queue from six import html_parser
很明显,我们是想导入队列Queue类及urlparse函数。
如果要手动进行兼容的话,那么就会写出类似如下一大段代码:
try: import HTMLParser from Queue import Queue except: mport html.parser from queue import Queue
通过使用six库可以简化上述的操作。
如果你去查看了six库的源码,就会上述实现过程非常简单,完全没必要花上上千行的代码。
04six库迷雾
在six库中,有这样一段代码:
_moved_attributes = [ MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), MovedModule("queue", "Queue"), MovedModule("html_parser","HTMLParser","html.parser") ] for attr in _moved_attributes: setattr(_MovedItems, attr.name, attr) del attr _MovedItems._moved_attributes = _moved_attributes moves = _MovedItems(__name__ + ".moves")
这里略去了大部分的内容,只挑重点来说说。
可以看到,我们定义了1个 _moved_attributes
的变量,它是1个列表,存放着MovedModule及MovedAttribute类实例。我们遍历这个列表,之后调用setattr方法对 _MovedItems
类设置其对应的属性。
而six的秘密就藏在上述不到10行的代码中。首先来看 _MovedItems
类,其源代码为:
import types class _LazyModule(types.ModuleType): def __init__(self, name): super(_LazyModule, self).__init__(name) self.__doc__ = self.__class__.__doc__ def __dir__(self): attrs = ["__doc__", "__name__"] attrs += [attr.name for attr in self._moved_attributes] return attrs # Subclasses should override this _moved_attributes = [] class _MovedItems(_LazyModule): """Lazy loading of moved objects""" __path__ = [] # mark as package
基本没有什么干货可说,唯一有用的就是 __dir__
方法中根据将其属性遍历出来。如果不存在对应的属性,则调用变量 _moved_attributes
中的类。
接着来看MovedModule类,其源代码为:
import sys PY3 = sys.version_info[0] == 3 def _import_module(name): __import__(name) return sys.modules[name] class _LazyDescr(object): def __init__(self, name): self.name = name def __get__(self, obj, tp): result = self._resolve() setattr(obj, self.name, result) # Invokes __set__. try: delattr(obj.__class__, self.name) except AttributeError: pass return result class MovedModule(_LazyDescr): def __init__(self, name, old, new=None): super(MovedModule, self).__init__(name) if PY3: if new is None: new = name self.mod = new else: self.mod = old def _resolve(self): return _import_module(self.mod) def __getattr__(self, attr): _module = self._resolve() value = getattr(_module, attr) return value
这个类是唯一稍微有点内容可以说说的。在该类 __getattr__
方法中,会调用其自身的 _resolve
方法解决对应模块导入的问题。实际上还是如下的老套路:
name = '想要导入的模块' __import__(name) sys.modules[name]
解决了模块的导入问题外,之后调用getattr方法获取到模块对应的属性,并将其返回。
就是这样简单,简单的不能再简单。
下面我们再回过头对之前的代码再进行推演一遍,例如:
MovedModule("queue", "Queue")
在MovedModule类初始化时,如果是在Python3环境下则有:
new = "queue" self.mod = new _import_module(self.mod)
从而导入urllib.parse模块。而对于Python2的情况,则变为:
self.mod = "Queue"
从而在不同版本中成功导入对应的模块。至于MovedAttribute类,其过程与之类似,也是先导入对应的模块再获取到对应的属性。
05结语
实际上six库虽然看起来很不思议,实际上也是基础知识堆积而成,只要花点心思就能看破其手法。
最后还是那句话,写的真心够啰嗦的。
本文作者:本人笔名玉面玲珑颜如玉,1个多年滚打于Web开发的研发工程师。熟悉PHP、Java、C++等编程语言,以编程作为乐趣。
声明:本文为 脚本之家专栏作者 投稿,未经允许请勿转载。