玉面玲珑颜如玉 脚本之家

揭开six库神秘的面纱_Java

揭开six库神秘的面纱_Java_02

揭开six库神秘的面纱_Java_03

作者 | 玉面玲珑颜如玉

出品 | 脚本之家(ID:jb51net)

如有好文章投稿,请点击 → 这里了解详情

01前言


不得不说距离Python2.7退役的日子还剩下不到2个月的时间了,面对堆积成三的Django项目遗留代码还是少动为妙。

真心要感谢six库的作者让迁移的工作减少了些困难,虽然实现的代码写的挺啰嗦的,几年前吓退了初学的我。而今天,打开揭开six库看似神秘的兼容过程。


02神秘six前传


为什么将这个库命名为six,在官方上有这样一段说明,因为要从2过渡3,其中2+3=5,而这个名字已经被zope项目的five库占了先机。而2 * 3=6,乘法看似来更强大,于是就命名为six了。

揭开six库神秘的面纱_Java_04

呵呵,我真没能领悟作者的幽默。


03神秘six正传


six库1个常见的用法类似如下:

from six.queue import Queuefrom six import html_parser

很明显,我们是想导入队列Queue类及urlparse函数。

如果要手动进行兼容的话,那么就会写出类似如下一大段代码:

try:   import HTMLParser   from Queue import Queueexcept:   mport html.parser   from queue import Queue

通过使用six库可以简化上述的操作。

如果你去查看了six库的源码,就会上述实现过程非常简单,完全没必要花上上千行的代码。


04six库迷雾


在six库中,有这样一段代码:

  1. _moved_attributes = [

  2.    MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),

  3.    MovedModule("queue", "Queue"),

  4.    MovedModule("html_parser","HTMLParser","html.parser")

  5. ]

  6. for attr in _moved_attributes:

  7.    setattr(_MovedItems, attr.name, attr)

  8. del attr


  9. _MovedItems._moved_attributes = _moved_attributes

  10. moves = _MovedItems(__name__ + ".moves")

这里略去了大部分的内容,只挑重点来说说。

可以看到,我们定义了1个 _moved_attributes的变量,它是1个列表,存放着MovedModule及MovedAttribute类实例。我们遍历这个列表,之后调用setattr方法对 _MovedItems类设置其对应的属性。

而six的秘密就藏在上述不到10行的代码中。首先来看 _MovedItems类,其源代码为:

  1. import types



  2. class _LazyModule(types.ModuleType):

  3.    def __init__(self, name):

  4.        super(_LazyModule, self).__init__(name)

  5.        self.__doc__ = self.__class__.__doc__


  6.    def __dir__(self):

  7.        attrs = ["__doc__", "__name__"]

  8.        attrs += [attr.name for attr in self._moved_attributes]

  9.        return attrs

  10.    # Subclasses should override this

  11.    _moved_attributes = []


  12. class _MovedItems(_LazyModule):

  13.    """Lazy loading of moved objects"""

  14.    __path__ = []  # mark as package

基本没有什么干货可说,唯一有用的就是 __dir__方法中根据将其属性遍历出来。如果不存在对应的属性,则调用变量 _moved_attributes中的类。

接着来看MovedModule类,其源代码为:

  1. import sys

  2. PY3 = sys.version_info[0] == 3


  3. def _import_module(name):

  4.    __import__(name)

  5.    return sys.modules[name]


  6. class _LazyDescr(object):

  7.    def __init__(self, name):

  8.        self.name = name


  9.    def __get__(self, obj, tp):

  10.        result = self._resolve()

  11.        setattr(obj, self.name, result)  # Invokes __set__.

  12.        try:

  13.            delattr(obj.__class__, self.name)

  14.        except AttributeError:

  15.            pass

  16.        return result



  17. class MovedModule(_LazyDescr):

  18.    def __init__(self, name, old, new=None):

  19.        super(MovedModule, self).__init__(name)

  20.        if PY3:

  21.            if new is None:

  22.                new = name

  23.            self.mod = new

  24.        else:

  25.            self.mod = old


  26.    def _resolve(self):

  27.        return _import_module(self.mod)


  28.    def __getattr__(self, attr):

  29.        _module = self._resolve()      

  30.        value = getattr(_module, attr)    

  31.        return value

这个类是唯一稍微有点内容可以说说的。在该类 __getattr__方法中,会调用其自身的 _resolve方法解决对应模块导入的问题。实际上还是如下的老套路:

name = '想要导入的模块'__import__(name)sys.modules[name]

解决了模块的导入问题外,之后调用getattr方法获取到模块对应的属性,并将其返回。

揭开six库神秘的面纱_Java_05

就是这样简单,简单的不能再简单。

下面我们再回过头对之前的代码再进行推演一遍,例如:

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++等编程语言,以编程作为乐趣。

声明:本文为 脚本之家专栏作者 投稿,未经允许请勿转载。