可移植代码:__import__ Python 2 和 Python 3 之间的参数字符串类型



在一个默认情况下所有文本文本都是Unicode的世界里,我应该怎么做才能使__import__在Python 2和3中同时工作?

我正在慢慢学习如何制作可以在Python 2(版本2.6或更高版本(和Python 3(版本3.2或更高版本(下运行的Python代码。

我相信,这需要确保文本文字默认为 Unicode 的告诫:

from __future__ import unicode_literals

并在需要时使用 b'wibble' 显式指定字节文字。

不过,__import__内置功能正在跳闸。

人为的、琐碎的项目布局:

$ mkdir fooproject/
$ cd fooproject/
$ mkdir foo/
$ printf "" > foo/__init__.py
$ mkdir foo/bar/
$ printf "" > foo/bar/__init__.py

以下是该项目的简单fooproject/setup.py

from __future__ import unicode_literals
main_module_name = 'foo'
main_module = __import__(main_module_name, fromlist=['bar'])
assert main_module.bar

这在Python 2下失败,但在Python 3下运行良好:

$ python2 ./setup.py
Traceback (most recent call last):
  File "./setup.py", line 4, in <module>
    main_module = __import__(main_module_name, fromlist=['bar'])
TypeError: Item in ``from list'' not a string
$ python3 ./setup.py

我们故意将未经修饰的字符串设为默认的 Unicode。通过"不是字符串",我认为 Python 2 的意思是"不是'字节'对象"。

好的,因此我们将显式设置为bytes文字:

from __future__ import unicode_literals
main_module_name = 'foo'
main_module = __import__(main_module_name, fromlist=[b'bar'])
assert main_module.bar

现在 Python 2 很满意,但 Python 3 抱怨:

$ python2 ./setup.py
$ python3 ./setup.py
Traceback (most recent call last):
  File "./setup.py", line 4, in <module>
    main_module = __import__(main_module_name, fromlist=[b'bar'])
  File "<frozen importlib._bootstrap>", line 2281, in
    _handle_fromlist
TypeError: hasattr(): attribute name must be string

因此,我故意将未经修饰的字符串默认设置为Unicode,就像我应该做的那样;但这显然打破了Python 2和Python 3之间__import__的期望。

如何让该__import__调用及其fromlist参数在 Python 2 和 Python 3 下正常工作,并保持unicode_literals设置?

回想一下str python2 和 python3 的工作方式不同(参见@BrenBarn的评论(,因此:

main_module = __import__(main_module_name, fromlist=[str('bar')])

或更一般

main_module = __import__(main_module_name, fromlist=list(map(str, ['bar'])))

到目前为止,我能想到的最好的是一个包装器函数,用于根据 Python 版本转换类型:

from __future__ import unicode_literals
import sys
fromlist_expects_type = str
if sys.version_info < (3, 0):
    fromlist_expects_type = bytes
def import_module(
        name, globals=None, locals=None, fromlist=(), level=0):
    """ Import specified module, together with options used by __import__.
        :param module_name: Text string of the module name to import.
        :param fromlist: List of names of attributes in the module to
            also import.
        :return: The module object.
        The built-in ``__import__`` function accepts a ``fromlist``
        parameter, but expects different string types between Python 2
        and Python 3. Python 2 only allows text string items; Python 3
        only allows byte string items.
        This function's ``fromlist`` parameter must have items of text
        string (Unicode) type only; the items are converted depending
        on the running Python version.
        """
    module_fromlist = ()
    if fromlist:
        module_fromlist = [
                fromlist_expects_type(attr_name) for attr_name in fromlist]
    module = __import__(
            name=name,
            globals=globals,
            locals=locals,
            fromlist=module_fromlist,
            level=level)
    return module
main_module_name = 'foo'
main_module = import_module(main_module_name, fromlist=['bar'])

这很笨拙,我可能犯了几个错误。这肯定是 Python 中的一个错误,我需要这样做吗?

最新更新