PEP-0328 中文翻译 By Lance Van

最近在做微信公众平台的后台开发,找到了一个Python的微信SDKwechat-python-sdk,使用之后却发现不支持Python3,于是决定移植到Python3上面。

在移植的过程中,发现原来的代码在import不能正确导入同一package下的module,在网上搜寻方案无果,怒翻译Python官方的PEP 0328

感谢chickenjohn桑在翻译过程中给予的帮助。也希望各位多给予指导。

目录

摘要
Timeline
使用括号的原因
使用绝对导入的原因
使用相对导入的原因
Guido的选择
相对导入与__name__属性
相对导入与sys.modules中的间接寻址入口
参考资料
版权所有

摘要

import声明存在两个问题:

  • 过长的import声明很难写,可能为了符合Python编码规范出现很多换行。
  • 存在很多包的情况下,import操作可能存在语义含糊不清的情况;例如,在一个包中,import foo操作可能代表导入了包内的foo模块,也可能代表导入了包外的foo模块。(更准确的说,一个本地的模块或包可能直接切断了与外部(即sys.path)的联系。)

对于第一个问题,我们建议用括号将要导入的名称括起来,以满足Python的换行标准。对于第二个问题,我们建议所有的import默认以绝对路径的方式声明,并用特殊语法(即开头的.)表示相对路径。

Timeline

在Python 2.5中,加入以下语句可以应用新的绝对路径标准:

  __future__ import absolute_import

如此就能优雅的使用相对路径导入了。在Python 2.6中,任何出现包内导入的import操作都会引起DeprecationWarning(这对于没有使用相对导入语法的from <> import操作同样适用)。

使用括号的原因

现在,如果要从一个模块或包中导入很多名称时,你不得不从如下不够优雅的方式中做出选择:

  • 利用反斜杠分隔开:

    from Tkinter import Tk, Frame, Button, Entry, Canvas, Text, \\\\
    LEFT, DISABLED, NORMAL, RIDGE, END
    
  • 将一次import操作分为多次import

    from Tkinter import Tk, Frame, Button, Entry, Canvas, Text
    from Tkinter import LEFT, DISABLED, NORMAL, RIDGE, END
    

( 不讨论import *的情况 ;-)
作为替代,应使用Python标准的分组机制进行import操作:

  from Tkinter import (Tk, Frame, Button, Entry, Canvas, Text, 
  LEFT, DISABLED, NORMAL, RIDGE, END)

这个建议从提出开始就被BDFL审批。

Python 2.4版本开始引入这种括号的支持

使用绝对导入的原因

在Python 2.4和更早的版本中,

  import foo

这个语句可能指代一个库中的顶层模块,也可能指代另一个本地包中的模块。作为Python标准库的扩展,越来越多的包内模块无意中与Python标准库的同名模块产生了冲突。这个问题导致了很多时候无法确定它导入了哪一个模块,从而产生歧义。为了消除这个歧义,我们建议此种方式导入的foo必须是在sys.path中可以找到的模块或包。此方式的导入称为绝对导入。

Python-dev社区默认选用绝对导入的原因是这种方式在实际中更常用,而且能提供所有使用相对导入时可提供的功能,尽管这可能导致一系列麻烦:例如修改顶层包名称以及修改包目录结构。

因为这可能导致语义上的变动,在Python 2.5和2.6中,绝对导入不是必须遵守的,加入以下语句可以应用绝对引用标准:

  from __future__ import absolute_import

这个建议从提出开始就被BDFL审批。

使用相对导入的原因

与绝对导入相对的,是否允许相对导入的问题也出现了。示例如下,这些示例的重点在于如何规整大包的结构而不必在大包的子包中做改动。除此之外,不使用相对导入很难单独导入包内的一个模块。

Guido认可相对导入这种导入方式,但是相对导入在语法上可能产生很多歧义。有一个不成文的约定就是相对导入需要列出导入的的具体名称(就是说,形如import foo单独的导入被认为是绝对导入)。

不同的相对导入方式如下:

  • Guido的导入方式

    from .foo import bar
    

以及

  from ...foo import bar

这两种形式有很多不同的解释方式。 一种解释方式是一个.表示文件结构中的一个层级。这样的话有很多人抱怨,数.的个数很麻烦,显然不够优雅。另外一个选择是只允许一个文件层次之间的相对导入。这样又会造成很多功能上的丧失,还有很多人抱怨在这种表示下会忘记.。最终的方案是声明一种相对导入寻找包与模块的算法,而这又违反了“The Zen of Python”中的"Explicit is better than implicit"(外显胜于内隐)。(这个被提议的算法主要思想是“自下而上,直至触顶”)

有些人提出使用其他标点符号作为分隔符,比如“-”或者"^"。

有些人提出使用"*"

  from *.foo import bar
  • 还有一个选择是从很多包结构中导入:
    from pkg.pkg import

    from parent.parent import

很多人(包括Guido)认为这种方式看起来很丑,但是这种方式非常明确而且一目了然。 总之,更多人喜欢以__pkg__作为更短的选项。

  • 有人提出只允许同一文件层级之间的引用。 换句话说,用相对导入无法导入更高层级的模块,只能以如下方式导入:
    from .spam import eggs
    或者
    import .spam.eggs

  • 有的人喜欢索引的父目录表示方式
    from -2.spam import eggs
    这种方式下,从当前目录导入只需要如下方式:
    from .spam import eggs

  • 最后,当要导入的包处于比较深的文件结构时,一些人不愿意从import的写法改为from ... import。他们建议重写import的语法:
    from MODULE import NAMES as RENAME searching HOW
    或者
    import NAMES as RENAME from MODULE searching HOW
    [from NAMES] [in WHERE] import ...
    然而,这在Python 2.5中很可能不能实现(改动过大),允许相对导入非常关键,所以()除此之外,这个被建议的语法有很多问题:

  • 准确的语法是什么?(什么条件下应用)
  • searching字句应对应什么?也就是说,应该采用一下哪种方式:
    import foo as bar searching XXX, spam as ham searching XXX
    或者
    import foo as bar, spam as ham searching XXX

Guido的选择

Guido已经宣布[1]相对导入要在导入路径前面加一个.表示当前的包,两个或者更多的.表示在当前包的父包中进行相对导入操作,一个.表示一个层级。以下是一个包的样例:

  package/
     __init__.py
     subpackage1/
         __init__.py
         moduleX.py
         moduleY.py
      subpackage2/
          __init__.py
          moduleZ.py
      moduleA.py

假设所在的文件是moduleX.pysubpackage1/__init__.py, 新语法的正确用法如下:

  from .moduleY import spam
  from .moduleY import spam as ham
  from . import moduleY
  from ..subpackage1 import moduleY
  from ..subpackage2.moduleZ import eggs
  from ..moduleA import foo
  from ...package import bar
  from ...sys import path

注意,虽然最后一个例子中的用法是合法的,但是不推荐这样做。(Guido用疯狂(insane)这个词来形容这种做法)

相对导入必须使用形如from <> import的方式; 形如import <>的均视为绝对导入。当然,绝对导入也可以通过省略.的方式进行from <> import操作。 禁止使用import .foo的原因是:在

  import XXX.YYY.ZZZ

操作后,

  XXX.YYY.ZZZ

可以在表达式中使用,而

 .module

不能在表达式中使用。

相对导入与__name__属性

相对导入使用一个模块的__name__属性确定模块在包层次结构中的位置。如果模块的名字不包含任何包的信息(比如它被设置为__main__),相对导入就会以处理最顶层模块一样处理,无视这个模块的实际位置。

相对导入与sys.modules中的间接寻址入口

当包的概念被引出时,在sys.modules中间接寻址入口的概念应运而生[2]。当一个sys.module中一个包中的模块的入口的值是None的时候,它表示当前模块确实表示顶层。例如,'Sound.Effects.string'sys.modules的值可能是None。这意味着导入对应'Sound.Effects.string'的操作就是导入string模块。

这又引出了一个当绝对导入对应相对导入情况下的的优化方式。但是在PEP对于绝对导入和相对导入有着明确界定的情况下,这种优化方式就不再需要了。当绝对导入或相对导入成为了唯一可用的导入方式时,sys.modules中的间接寻址入口不再支持。

参考资料

了解更多相关背景可以参考以下主题:

  • Re: Christmas Wishlist
  • Re: Python-Dev Digest, Vol 5, Issue 57
  • Relative import
  • Another Strategy for Relative Import
    [1] http://mail.python.org/pipermail/python-dev/2004-March/043739.html
    [2] https://www.python.org/doc/essays/packages/

版权所有

这个文档已被放置在公共域名。
来源:https://hg.python.org/peps/file/tip/pep-0328.txt

你可能感兴趣的:(PEP-0328 中文翻译 By Lance Van)