【问题标题】:Portable code: __import__ parameter string type between Python 2 and Python 3可移植代码:Python 2 和 Python 3 之间的 __import__ 参数字符串类型
【发布时间】:2014-12-15 05:27:00
【问题描述】:

在默认情况下所有文本文字都是 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,就像我应该做的那样;但这显然打破了__import__ 在 Python 2 和 Python 3 之间的期望。

我怎样才能获得 __import__ 调用,完成其 fromlist 参数,在 Python 2 和 Python 3 下正常工作,保持 unicode_literals 设置?

【问题讨论】:

  • 您可以编写一个将字符串传递给的函数,它会检查版本并返回 unicode 或 bytestring 版本。
  • @BrenBarn,这正是str() 的行为方式!

标签: python migration portability python-unicode


【解决方案1】:

recall str 对于 python2 和 python3 的工作方式不同(请参阅@BrenBarn 的评论),所以:

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

或更一般地

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

【讨论】:

  • 为不需要的东西拼凑起来;但比我以前的要整洁得多。谢谢。
【解决方案2】:

到目前为止,我能想到的最好的方法是根据 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 中的一个错误,我需要这样做吗?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-05-03
    • 1970-01-01
    • 2015-06-09
    • 2020-10-22
    • 1970-01-01
    • 1970-01-01
    • 2019-12-12
    • 2019-09-25
    相关资源
    最近更新 更多