在一个默认情况下所有文本文本都是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 中的一个错误,我需要这样做吗?