【问题标题】:Why python finds module instead of package if they have the same name?如果它们具有相同的名称,为什么 python 会找到模块而不是包?
【发布时间】:2013-01-06 15:21:05
【问题描述】:

这是我的目录结构:

/home/dmugtasimov/tmp/name-res
    root
        tests
            __init__.py
            test_1.py
        __init__.py
        classes.py
        extra.py
        root.py

文件内容: root/tests/_init_.py

import os, sys
ROOT_DIRECTORY = os.path.abspath(os.path.join(os.path.dirname(__file__),
                                             '../..'))
if not sys.path or ROOT_DIRECTORY not in sys.path:
    sys.path.insert(0, ROOT_DIRECTORY)
# These imports are required for unittest to find test modules in package properly
from root.tests import test_1

root/tests/test_1.py

import unittest
from root.classes import Class1
class Tests(unittest.TestCase):
    pass

root/_init_.py - 空
root/classes.py

import os, sys
ROOT_DIRECTORY = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
if not sys.path or ROOT_DIRECTORY not in sys.path:
    sys.path.insert(0, ROOT_DIRECTORY)

print 'sys.path:', sys.path
print 'BEFORE: import root.extra'
import root.extra
print 'AFTER: import root.extra'

class Class1(object):
    pass

class Class2(object):
    pass

root/extra.py

class Class3(object):
    pass

root/root.py

import os
import sys
ROOT_DIRECTORY = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
if not sys.path or ROOT_DIRECTORY not in sys.path:
    sys.path.insert(0, ROOT_DIRECTORY)
from classes import Class2

我得到以下输出:

$ python -m unittest tests.test_1
sys.path: ['/home/dmugtasimov/tmp/name-res', '', '/usr/local/lib/python2.7/dist-packages/tornado-2.3-py2.7.egg', '/usr/lib/python2.7', '/usr/lib/python2.7/plat-linux2', '/usr/lib/python2.7/lib-tk', '/usr/lib/python2.7/lib-old', '/usr/lib/python2.7/lib-dynload', '/usr/local/lib/python2.7/dist-packages', '/usr/local/lib/python2.7/dist-packages/setuptools-0.6c11-py2.7.egg-info', '/usr/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages/PIL', '/usr/lib/python2.7/dist-packages/gst-0.10', '/usr/lib/python2.7/dist-packages/gtk-2.0', '/usr/lib/python2.7/dist-packages/ubuntu-sso-client']
BEFORE: import root.extra
sys.path: ['/home/dmugtasimov/tmp/name-res', '', '/usr/local/lib/python2.7/dist-packages/tornado-2.3-py2.7.egg', '/usr/lib/python2.7', '/usr/lib/python2.7/plat-linux2', '/usr/lib/python2.7/lib-tk', '/usr/lib/python2.7/lib-old', '/usr/lib/python2.7/lib-dynload', '/usr/local/lib/python2.7/dist-packages', '/usr/local/lib/python2.7/dist-packages/setuptools-0.6c11-py2.7.egg-info', '/usr/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages/PIL', '/usr/lib/python2.7/dist-packages/gst-0.10', '/usr/lib/python2.7/dist-packages/gtk-2.0', '/usr/lib/python2.7/dist-packages/ubuntu-sso-client']
BEFORE: import root.extra
Traceback (most recent call last):
 File "/usr/lib/python2.7/runpy.py", line 162, in _run_module_as_main
    "__main__", fname, loader, pkg_name)
 File "/usr/lib/python2.7/runpy.py", line 72, in _run_code
    exec code in run_globals
 File "/usr/lib/python2.7/unittest/__main__.py", line 12, in <module>
    main(module=None)
 File "/usr/lib/python2.7/unittest/main.py", line 94, in __init__
    self.parseArgs(argv)
 File "/usr/lib/python2.7/unittest/main.py", line 149, in parseArgs
    self.createTests()
 File "/usr/lib/python2.7/unittest/main.py", line 158, in createTests
    self.module)
 File "/usr/lib/python2.7/unittest/loader.py", line 128, in loadTestsFromNames
    suites = [self.loadTestsFromName(name, module) for name in names]
 File "/usr/lib/python2.7/unittest/loader.py", line 91, in loadTestsFromName
    module = __import__('.'.join(parts_copy))
 File "tests/__init__.py", line 9, in <module>
    from root.tests import test_1
 File "/home/dmugtasimov/tmp/name-res/root/tests/__init__.py", line 9, in <module>
    from root.tests import test_1
 File "/home/dmugtasimov/tmp/name-res/root/tests/test_1.py", line 3, in <module>
    from root.classes import Class1
 File "/home/dmugtasimov/tmp/name-res/root/classes.py", line 9, in <module>
    import root.extra
 File "/home/dmugtasimov/tmp/name-res/root/root.py", line 6, in <module>
    from classes import Class2
ImportError: cannot import name Class2

原来问题是python解释器用来搜索包或模块的顺序:

$ python -vv -m unittest tests.test_1
…skipped...
import root.classes # precompiled from /home/dmugtasimov/tmp/name-res/root/classes.pyc
sys.path: ['/home/dmugtasimov/tmp/name-res', '', '/usr/local/lib/python2.7/dist-packages/tornado-2.3-py2.7.egg', '/usr/lib/python2.7', '/usr/lib/python2.7/plat-linux2', '/usr/lib/python2.7/lib-tk', '/usr/lib/python2.7/lib-old', '/usr/lib/python2.7/lib-dynload', '/usr/local/lib/python2.7/dist-packages', '/usr/local/lib/python2.7/dist-packages/setuptools-0.6c11-py2.7.egg-info', '/usr/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages/PIL', '/usr/lib/python2.7/dist-packages/gst-0.10', '/usr/lib/python2.7/dist-packages/gtk-2.0', '/usr/lib/python2.7/dist-packages/ubuntu-sso-client']
BEFORE: import root.extra
# trying /home/dmugtasimov/tmp/name-res/root/root.so
# trying /home/dmugtasimov/tmp/name-res/root/rootmodule.so
# trying /home/dmugtasimov/tmp/name-res/root/root.py
# /home/dmugtasimov/tmp/name-res/root/root.pyc matches /home/dmugtasimov/tmp/name-res/root/root.py
…skipped...

根据python文档http://docs.python.org/2/tutorial/modules.html#the-module-search-path: “当导入名为 spam 的模块时,解释器首先搜索具有该名称的内置模块。如果没有找到,它会在变量 sys.path 给出的目录列表中搜索名为 spam.py 的文件。”

这意味着python应该查看sys.path index 0条目,获取路径'/home/dmugtasimov/tmp/name-res'并找到名为root的包,然后在此包中搜索名为extra的模块。但它会在 /home/dmugtasimov/tmp/name-res/root/ 目录中搜索模块根目录,然后尝试在其中找到名为 extra 的内容。它会发生什么?它不与官方文件相矛盾吗?还是搜索包的规则与搜索模块的规则不同?如果有,这些规则是否包含在文档的某个地方?

更新

我把它放在这里是为了更好的格式。
为了进一步调查,请执行以下操作:

  1. 删除root.pyc
  2. 将root.py重命名为root2.py
  3. 运行 python -vv -m unittest tests.test_1
# trying /home/dmugtasimov/tmp/name-res/root/root.so
# trying /home/dmugtasimov/tmp/name-res/root/rootmodule.so
# trying /home/dmugtasimov/tmp/name-res/root/root.py
# trying /home/dmugtasimov/tmp/name-res/root/root.pyc
# trying /home/dmugtasimov/tmp/name-res/root/extra.so
# trying /home/dmugtasimov/tmp/name-res/root/extramodule.so
# trying /home/dmugtasimov/tmp/name-res/root/extra.py

python 似乎只在最初的 4 次尝试中忽略 sys.path。

更新 2

简化版:

/home/dmugtasimov/tmp/name-res3/xyz
    __init__.py
    a.py
    b.py
    t.py
    xyz.py

文件 init.py、b.py 和 xyz.py 为空
文件 a.py:

import os, sys
ROOT_DIRECTORY = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
if not sys.path or ROOT_DIRECTORY not in sys.path:
    print 'sys.path is modified in a.py'
    sys.path.insert(0, ROOT_DIRECTORY)
else:
    print 'sys.path is NOT modified in a.py'

print 'sys.path:', sys.path
print 'BEFORE import xyz.b'
import xyz.b
print 'AFTER import xyz.b'

文件 t.py:

import os, sys
ROOT_DIRECTORY = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
if not sys.path or ROOT_DIRECTORY not in sys.path:
    print 'sys.path is modified in t.py'
    sys.path.insert(0, ROOT_DIRECTORY)
else:
    print 'sys.path is NOT modified in t.py'

import xyz.a

运行:

python a.py

输出:

sys.path is modified in a.py
sys.path: ['/home/dmugtasimov/tmp/name-res3', '/home/dmugtasimov/tmp/name-res3/xyz',
 '/usr/local/lib/python2.7/dist-packages/tornado-2.3-py2.7.egg',
 '/home/dmugtasimov/tmp/name-res3/xyz', '/usr/lib/python2.7',
 '/usr/lib/python2.7/plat-linux2', '/usr/lib/python2.7/lib-tk',
 '/usr/lib/python2.7/lib-old', '/usr/lib/python2.7/lib-dynload',
 '/usr/local/lib/python2.7/dist-packages',
 '/usr/local/lib/python2.7/dist-packages/setuptools-0.6c11-py2.7.egg-info',
 '/usr/lib/python2.7/dist-packages',
 '/usr/lib/python2.7/dist-packages/PIL',
 '/usr/lib/python2.7/dist-packages/gst-0.10',
 '/usr/lib/python2.7/dist-packages/gtk-2.0',
 '/usr/lib/python2.7/dist-packages/ubuntu-sso-client']
BEFORE import xyz.b
AFTER import xyz.b

运行:

python -vv a.py

输出:

import xyz # directory /home/dmugtasimov/tmp/name-res3/xyz
# trying /home/dmugtasimov/tmp/name-res3/xyz/__init__.so
# trying /home/dmugtasimov/tmp/name-res3/xyz/__init__module.so
# trying /home/dmugtasimov/tmp/name-res3/xyz/__init__.py
# /home/dmugtasimov/tmp/name-res3/xyz/__init__.pyc matches /home/dmugtasimov/tmp/name-res3/xyz/__init__.py
import xyz # precompiled from /home/dmugtasimov/tmp/name-res3/xyz/__init__.pyc
# trying /home/dmugtasimov/tmp/name-res3/xyz/b.so
# trying /home/dmugtasimov/tmp/name-res3/xyz/bmodule.so
# trying /home/dmugtasimov/tmp/name-res3/xyz/b.py
# /home/dmugtasimov/tmp/name-res3/xyz/b.pyc matches /home/dmugtasimov/tmp/name-res3/xyz/b.py
import xyz.b # precompiled from /home/dmugtasimov/tmp/name-res3/xyz/b.pyc

运行:

python t.py

输出:

sys.path is modified in t.py
sys.path is NOT modified in a.py
sys.path: ['/home/dmugtasimov/tmp/name-res3', '/home/dmugtasimov/tmp/name-res3/xyz',
 '/usr/local/lib/python2.7/dist-packages/tornado-2.3-py2.7.egg',
 '/home/dmugtasimov/tmp/name-res3/xyz', '/usr/lib/python2.7',
 '/usr/lib/python2.7/plat-linux2', '/usr/lib/python2.7/lib-tk',
 '/usr/lib/python2.7/lib-old', '/usr/lib/python2.7/lib-dynload',
 '/usr/local/lib/python2.7/dist-packages',
 '/usr/local/lib/python2.7/dist-packages/setuptools-0.6c11-py2.7.egg-info',
 '/usr/lib/python2.7/dist-packages',
 '/usr/lib/python2.7/dist-packages/PIL',
 '/usr/lib/python2.7/dist-packages/gst-0.10',
 '/usr/lib/python2.7/dist-packages/gtk-2.0',
 '/usr/lib/python2.7/dist-packages/ubuntu-sso-client']
BEFORE import xyz.b
Traceback (most recent call last):
  File "t.py", line 9, in <module>
    import xyz.a
  File "/home/dmugtasimov/tmp/name-res3/xyz/a.py", line 11, in <module>
    import xyz.b
ImportError: No module named b

运行:

python -vv t.py

输出:

import xyz # directory /home/dmugtasimov/tmp/name-res3/xyz
# trying /home/dmugtasimov/tmp/name-res3/xyz/__init__.so
# trying /home/dmugtasimov/tmp/name-res3/xyz/__init__module.so
# trying /home/dmugtasimov/tmp/name-res3/xyz/__init__.py
# /home/dmugtasimov/tmp/name-res3/xyz/__init__.pyc matches /home/dmugtasimov/tmp/name-res3/xyz/__init__.py
import xyz # precompiled from /home/dmugtasimov/tmp/name-res3/xyz/__init__.pyc
# trying /home/dmugtasimov/tmp/name-res3/xyz/a.so
# trying /home/dmugtasimov/tmp/name-res3/xyz/amodule.so
# trying /home/dmugtasimov/tmp/name-res3/xyz/a.py
# /home/dmugtasimov/tmp/name-res3/xyz/a.pyc matches /home/dmugtasimov/tmp/name-res3/xyz/a.py
import xyz.a # precompiled from /home/dmugtasimov/tmp/name-res3/xyz/a.pyc
# trying /home/dmugtasimov/tmp/name-res3/xyz/os.so
# trying /home/dmugtasimov/tmp/name-res3/xyz/osmodule.so
# trying /home/dmugtasimov/tmp/name-res3/xyz/os.py
# trying /home/dmugtasimov/tmp/name-res3/xyz/os.pyc
# trying /home/dmugtasimov/tmp/name-res3/xyz/sys.so
# trying /home/dmugtasimov/tmp/name-res3/xyz/sysmodule.so
# trying /home/dmugtasimov/tmp/name-res3/xyz/sys.py
# trying /home/dmugtasimov/tmp/name-res3/xyz/sys.pyc
# trying /home/dmugtasimov/tmp/name-res3/xyz/xyz.so
# trying /home/dmugtasimov/tmp/name-res3/xyz/xyzmodule.so
# trying /home/dmugtasimov/tmp/name-res3/xyz/xyz.py
# /home/dmugtasimov/tmp/name-res3/xyz/xyz.pyc matches /home/dmugtasimov/tmp/name-res3/xyz/xyz.py
import xyz.xyz # precompiled from /home/dmugtasimov/tmp/name-res3/xyz/xyz.pyc
#   clear[2] __file__
#   clear[2] __package__
#   clear[2] sys
#   clear[2] ROOT_DIRECTORY
#   clear[2] __name__
#   clear[2] os
sys.path is modified in t.py
sys.path is NOT modified in a.py
sys.path: ['/home/dmugtasimov/tmp/name-res3', '/home/dmugtasimov/tmp/name-res3/xyz',
 '/usr/local/lib/python2.7/dist-packages/tornado-2.3-py2.7.egg',
 '/home/dmugtasimov/tmp/name-res3/xyz', '/usr/lib/python2.7',
 '/usr/lib/python2.7/plat-linux2', '/usr/lib/python2.7/lib-tk',
 '/usr/lib/python2.7/lib-old', '/usr/lib/python2.7/lib-dynload',
 '/usr/local/lib/python2.7/dist-packages',
 '/usr/local/lib/python2.7/dist-packages/setuptools-0.6c11-py2.7.egg-info',
 '/usr/lib/python2.7/dist-packages',
 '/usr/lib/python2.7/dist-packages/PIL',
 '/usr/lib/python2.7/dist-packages/gst-0.10',
 '/usr/lib/python2.7/dist-packages/gtk-2.0',
 '/usr/lib/python2.7/dist-packages/ubuntu-sso-client']
BEFORE import xyz.b
Traceback (most recent call last):
  File "t.py", line 9, in <module>
    import xyz.a
  File "/home/dmugtasimov/tmp/name-res3/xyz/a.py", line 11, in <module>
    import xyz.b
ImportError: No module named b

如您所见,两种情况下 sys.path 是相同的:

sys.path: ['/home/dmugtasimov/tmp/name-res3', '/home/dmugtasimov/tmp/name-res3/xyz', '/usr/local/lib/python2.7/dist-packages/tornado-2.3-py2.7.egg', '/home/dmugtasimov/tmp/name-res3/xyz', '/usr/lib/python2.7', '/usr/lib/python2.7/plat-linux2', '/usr/lib/python2.7/lib-tk', '/usr/lib/python2.7/lib-old', '/usr/lib/python2.7/lib-dynload', '/usr/local/lib/python2.7/dist-packages', '/usr/local/lib/python2.7/dist-packages/setuptools-0.6c11-py2.7.egg-info', '/usr/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages/PIL', '/usr/lib/python2.7/dist-packages/gst-0.10', '/usr/lib/python2.7/dist-packages/gtk-2.0', '/usr/lib/python2.7/dist-packages/ubuntu-sso-client']

但行为不同。对于 a.py,python 首先搜索包 xyz,然后搜索其中的模块 b:

import xyz # directory /home/dmugtasimov/tmp/name-res3/xyz
# trying /home/dmugtasimov/tmp/name-res3/xyz/__init__.so
# trying /home/dmugtasimov/tmp/name-res3/xyz/__init__module.so
# trying /home/dmugtasimov/tmp/name-res3/xyz/__init__.py
# /home/dmugtasimov/tmp/name-res3/xyz/__init__.pyc matches /home/dmugtasimov/tmp/name-res3/xyz/__init__.py
import xyz # precompiled from /home/dmugtasimov/tmp/name-res3/xyz/__init__.pyc
# trying /home/dmugtasimov/tmp/name-res3/xyz/b.so
# trying /home/dmugtasimov/tmp/name-res3/xyz/bmodule.so
# trying /home/dmugtasimov/tmp/name-res3/xyz/b.py
# /home/dmugtasimov/tmp/name-res3/xyz/b.pyc matches /home/dmugtasimov/tmp/name-res3/xyz/b.py
import xyz.b # precompiled from /home/dmugtasimov/tmp/name-res3/xyz/b.pyc

换句话说:

  1. 在目录 sys.path[0] 中搜索 PACKAGE xyz -> 找到
  2. 在包 xyz 中搜索模块 b -> 找到
  3. 继续执行

对于 t.py,它在与 a.py 本身相同的目录中搜索模块 xyz,然后在模块 xyz 中找不到模块 b:

# trying /home/dmugtasimov/tmp/name-res3/xyz/xyz.so
# trying /home/dmugtasimov/tmp/name-res3/xyz/xyzmodule.so
# trying /home/dmugtasimov/tmp/name-res3/xyz/xyz.py
# /home/dmugtasimov/tmp/name-res3/xyz/xyz.pyc matches /home/dmugtasimov/tmp/name-res3/xyz/xyz.py
import xyz.xyz # precompiled from /home/dmugtasimov/tmp/name-res3/xyz/xyz.pyc

换句话说:

  1. 在与 a.py(或 sys.path[1] ?)相同的目录中搜索 MODULE xyz -> 找到
  2. 在 MODULE xyz 中搜索 MODULE b -> 未找到
  3. 导入错误

所以看起来“import xyz.b”是否会有所不同,具体取决于 a.py 最初是如何作为脚本加载或从另一个模块导入的。

更新 3

我提交了文档修复提案:http://bugs.python.org/issue16891

更新 4

我现在完全清楚 UPDATE 2 中描述的行为原因。

http://docs.python.org/2/tutorial/modules.html#intra-package-references

6.4.2。包内引用

子模块经常需要相互引用。例如, 环绕模块可能使用回声模块。事实上,这样的参考 是如此常见,以至于 import 语句首先在包含 在查看标准模块搜索路径之前打包。就这样 环绕模块可以简单地使用 import echo 或 from echo import 回声过滤器。如果在当前包中没有找到导入的模块 (当前模块为子模块的包),导入 语句查找具有给定名称的顶级模块。

对于“python a.py”,“a”不被视为包中的模块,但对于“python t.py”,“a”被视为包“xyz”中的模块。因此,在第一种情况下,它根据 sys.path 搜索,但在第二种情况下,它在同一个包(即“xyz”)中搜索名为“xyz”的模块(换句话说,“xyz.xyz”)

您可以轻松查看是否像这样更改 a.py:

文件 a.py:

import os, sys
ROOT_DIRECTORY = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
if not sys.path or ROOT_DIRECTORY not in sys.path:
    print 'sys.path is modified in a.py'
    sys.path.insert(0, ROOT_DIRECTORY)
else:
    print 'sys.path is NOT modified in a.py'

print 'sys.path:', sys.path
print '__package__', __package__
print 'BEFORE import xyz.b'
import xyz.b
print 'AFTER import xyz.b'

我的输出是:

~/tmp/name-res3/xyz $ python a.py
sys.path is modified in a.py
sys.path: ['/home/dmugtasimov/tmp/name-res3', '/home/dmugtasimov/tmp/name-res3/xyz',
 '/usr/local/lib/python2.7/dist-packages/tornado-2.3-py2.7.egg',
 '/usr/lib/python2.7', '/usr/lib/python2.7/plat-linux2',
 '/usr/lib/python2.7/lib-tk', '/usr/lib/python2.7/lib-old',
 '/usr/lib/python2.7/lib-dynload',
 '/usr/local/lib/python2.7/dist-packages',
 '/usr/local/lib/python2.7/dist-packages/setuptools-0.6c11-py2.7.egg-info',
 '/usr/lib/python2.7/dist-packages',
 '/usr/lib/python2.7/dist-packages/PIL',
 '/usr/lib/python2.7/dist-packages/gst-0.10',
 '/usr/lib/python2.7/dist-packages/gtk-2.0',
 '/usr/lib/python2.7/dist-packages/ubuntu-sso-client']
__package__ None
BEFORE import xyz.b
AFTER import xyz.b
~/tmp/name-res3/xyz $ python t.py
sys.path is modified in t.py
sys.path is NOT modified in a.py
sys.path: ['/home/dmugtasimov/tmp/name-res3', '/home/dmugtasimov/tmp/name-res3/xyz',
 '/usr/local/lib/python2.7/dist-packages/tornado-2.3-py2.7.egg',
 '/usr/lib/python2.7', '/usr/lib/python2.7/plat-linux2',
 '/usr/lib/python2.7/lib-tk', '/usr/lib/python2.7/lib-old',
 '/usr/lib/python2.7/lib-dynload',
 '/usr/local/lib/python2.7/dist-packages',
 '/usr/local/lib/python2.7/dist-packages/setuptools-0.6c11-py2.7.egg-info',
 '/usr/lib/python2.7/dist-packages',
 '/usr/lib/python2.7/dist-packages/PIL',
 '/usr/lib/python2.7/dist-packages/gst-0.10',
 '/usr/lib/python2.7/dist-packages/gtk-2.0',
 '/usr/lib/python2.7/dist-packages/ubuntu-sso-client']
__package__ xyz
BEFORE import xyz.b
Traceback (most recent call last):
  File "t.py", line 9, in <module>
    import xyz.a
  File "/home/dmugtasimov/tmp/name-res3/xyz/a.py", line 12, in <module>
    import xyz.b
ImportError: No module named b

感谢@J.F. Sebastian 指出了正确的文档位置。

更新 5

似乎还有一个问题。有兴趣的请关注这里的cmets:http://bugs.python.org/issue16891

【问题讨论】:

  • 为了进一步调查,请执行以下操作: 1. 删除 root.pyc 2. 将 root.py 重命名为 root2.py 3. 运行 python -vv -m unittest tests.test_1 #正在尝试 /home/dmugtasimov/ tmp/name-res/root/root.so # 尝试 /home/dmugtasimov/tmp/name-res/root/rootmodule.so # 尝试 /home/dmugtasimov/tmp/name-res/root/root.py # 尝试 / home/dmugtasimov/tmp/name-res/root/root.pyc # 尝试 /home/dmugtasimov/tmp/name-res/root/extra.so # 尝试 /home/dmugtasimov/tmp/name-res/root/extramodule。所以 # 尝试 /home/dmugtasimov/tmp/name-res/root/extra.py 似乎 python 忽略 sys.path 仅用于最初的 4 次尝试。
  • 请修正(编辑)您的目录结构列表。文件“root.py”与“root”目录处于同一级别,但您编写的是“root/root.py”。更多文件也是如此。
  • 你真的不应该修改sys.path,尤其是在库代码中。您的root 是一个包,因此当__main__ 脚本在其可导入路径中有根包时,使用绝对或相对导入没有问题。
  • 假设我接受我不应该修改 sys.path。但是我确实修改了它(我有这样做的理由),并且我希望 python 的行为与记录的一样(如果有记录的话)。问题是“是否记录在案,在哪里?”问题不是“我应该修改 sys.path 吗?” ;)
  • 顺便说一句,root/classes.py中的sys.path修改可能会被删除,这不是root问题。

标签: python import module package sys.path


【解决方案1】:

我简化了问题中的示例,以证明只有 四种解决方案是可能的:

  • 显式相对导入from . import some_module 或带有更多逗号from ..
  • 相对导入(如果在包“xyz”中使用,则不带“xyz”)
  • 绝对导入使用from __future__ import absolute_import(或使用Python 3)
  • 切勿在包的任何路径中重复包的顶级可导入名称。

什么解决方案是最好的?这取决于个人对 Python 2 或 3 的偏好。只有最后一个对所有 Python 都很好且通用。这真是一个有用的问题。


xyz/tests/__init__.pyimport xyz.tests.t

xyz/tests/t.py

import sys
print('sys.path = %s' % sys.path) # see that the parent of "xyz" is on sys.path
print("importing xyz.tests")
import xyz.a

xyz/a.py:

# solution A: absolute_import by __future__  (or use Python 3)
#from __future__ import absolute_import
print("importing xyz.a")
# solution B: explicit relative import
#from . import b    # and remove "import xyz.b"
# solution C: relative import (not recommended)
#import b           # and remove "import xyz.b"
import xyz.b

xyz/b.pyprint("imported xyz.b")

xyz/xyz.pyprint("Imported xyz.xyz !!!")

xyz/__init__.py:空文件


一切可能都失败,例如

parent_of_xyz=...  # The parent directory of "xyz" - absolute path
cd $parent_of_xyz
python -m xyz.tests.t
PYTHONPATH=$parent_of_xyz/xyz python -m unittest tests
PYTHONPATH=$parent_of_xyz     python xyz/tests/t.py

像这样的消息

Imported xyz.xyz  !!!
...
ImportError...

如果应用任何解决方案(未注释),所有三个示例都有效。

不使用任何子目录可以更简化。

编辑:我昨天尝试了很多测试,但我在不同版本中写的不一致。对不起,答案无法重现。现在它已修复

【讨论】:

  • 1. __main__ 中的显式相对导入失败(作为脚本运行的模块); 2. 根本不应该使用相对导入; 3.from __future__ import absolute_import不是必须的; 4.你不能避免重复一些部分名称(你不知道sys.path上还有哪些其他包)
  • @J.F.Sebastian:在该包中应避免使用与正在编写的包相同的名称。这是一个简单的限制,尽管它不是一个众所周知的限制。即使在 Python 2.7 中,没有 _future_ 的绝对导入也无法正常工作,该功能应该是强制性的。是的,简单的相对导入是一种不好的做法,只是为了完整性而提到没有其他好的或坏的解决方案不存在。
  • 这里是错字吗(最后一个字符)?导入 xyz.tests.t`
  • @hynekcer,看起来你的简化没有正确反映最初的问题,因为一旦我运行“python -m unittest tests.t”,就会得到“ImportError: No module named tests”。还有一点是PYTHONPATH可能没有插入到sys.path的索引0处,所以它改变了搜索顺序。
  • 我修复了我昨天尝试多次测试而导致的错误。对我来说看起来不错,但我在测试期间从不同版本中不一致地复制了我的答案。对不起。
【解决方案2】:

不要修改sys.path,当同一个模块以不同的名称可用时,它会导致问题。见Traps for the Unwary

在代码中使用绝对或显式相对导入,并从项目目录运行脚本。使用全名运行测试:

$ python -munittest root.tests.test_1

某些包确实会在内部修改 sys.path,例如,查看 twisted 如何使用 _preamble.py 或 pypy 的 autopath.py。您可以决定它们的缺点(引入晦涩的导入问题)是否值得(允许更多运行脚本的方法)。避免在用作库的代码中修改 sys.path,即将其限制为测试模块和命令行脚本。

【讨论】:

  • 你是对的,问题中的边缘问题可以改进,但核心是正确的,我也没有回答。在 Python 2 中记录似乎真的不好。请看我的回答。
  • @hynekcer:我的答案是关于问题的核心:“如何解决导入问题”和答案:“在代码中使用绝对或显式相对导入并从项目中运行脚本目录。”
  • 没有 _future_ 指令(或没有 Python 3)并从项目目录(或任何可能的目录)运行的绝对导入不起作用,正如它所展示的那样在简化的例子中。导入包的主项目目录可以完全在外面。
  • @hynekcer:感谢您指出这一点:“但核心是正确的,仍未得到答复”
  • @hynekcer:是的,在您的示例中,绝对导入不起作用。来自文档(Python 2.x):“import 语句首先在包含包中查找,然后再查找标准模块搜索路径”,即 xyz/a.py 中的 import xyz.b 将尝试导入 @首先是 987654333@ 而不是 xyz/b.py。重命名 xyz.py(按照您的建议)可以解决问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-09-17
  • 2013-07-21
  • 2020-01-26
  • 2012-05-17
  • 2015-08-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多