【问题标题】:python import path: packages with the same name in different folderspython导入路径:不同文件夹中的同名包
【发布时间】:2012-02-14 18:01:56
【问题描述】:

我正在同时为多个客户开发多个 Python 项目。我的项目文件夹结构的简化版本如下所示:

/path/
  to/
    projects/
      cust1/
        proj1/
          pack1/
            __init__.py
            mod1.py
        proj2/
          pack2/
            __init__.py
            mod2.py
      cust2/
        proj3/
          pack3/
            __init__.py
            mod3.py

例如,当我想使用来自proj1 的功能时,我将sys.path 扩展为/path/to/projects/cust1/proj1(例如通过设置PYTHONPATH 或将.pth 文件添加到site_packages 文件夹,甚至修改@987654329 @直接),然后像这样导入模块:

>>> from pack1.mod1 import something

随着我从事的项目越来越多,碰巧不同的项目具有相同的包名称:

/path/
  to/
    projects/
      cust3/
        proj4/
          pack1/    <-- same package name as in cust1/proj1 above
            __init__.py
            mod4.py

如果我现在简单地将sys.path 扩展为/path/to/projects/cust3/proj4,我仍然可以从proj1 导入,但不能从proj4 导入:

>>> from pack1.mod1 import something
>>> from pack1.mod4 import something_else
ImportError: No module named mod4

我认为第二次导入失败的原因是 Python 仅搜索 sys.path 中的第一个文件夹,在那里它找到了 pack1 包,如果在其中找不到 mod4 模块则放弃。我在之前的问题中问过这个问题,请参阅import python modules with the same name,但我仍然不清楚内部细节。

无论如何,显而易见的解决方案是通过将项目目录转换为超级包来添加另一层命名空间限定:将__init__.py 文件添加到每个proj* 文件夹并从扩展sys.path 的行中删除这些文件夹,例如

$ export PYTHONPATH=/path/to/projects/cust1:/path/to/projects/cust3
$ touch /path/to/projects/cust1/proj1/__init__.py
$ touch /path/to/projects/cust3/proj4/__init__.py
$ python
>>> from proj1.pack1.mod1 import something
>>> from proj4.pack1.mod4 import something_else

现在我遇到了不同客户的不同项目具有相同名称的情况,例如

/path/
  to/
    projects/
      cust3/
        proj1/    <-- same project name as for cust1 above
          __init__.py
          pack4/
            __init__.py
            mod4.py

由于与以前相同的原因,尝试从 mod4 导入不再起作用:

>>> from proj1.pack4.mod4 import yet_something_else
ImportError: No module named pack4.mod4

按照之前解决此问题的相同方法,我将添加另一个包/命名空间层并将客户文件夹转换为超级超级包。

但是,这与我对项目文件夹结构的其他要求相冲突,例如

  • 开发/发布结构以维护多个代码行
  • 其他类型的源代码,例如JavaScript、SQL 等
  • 源文件以外的其他文件,例如文件或数据。

对一些项目文件夹的简化、更真实的描述如下所示:

/path/
  to/
    projects/
      cust1/
        proj1/
          Development/
            code/
              javascript/
                ...
              python/
                pack1/
                  __init__.py
                  mod1.py
            doc/
              ...
          Release/
            ...
        proj2/
          Development/
            code/
              python/
                pack2/
                  __init__.py
                  mod2.py

我不明白如何才能满足 python 解释器对文件夹结构的要求以及我同时拥有的要求。也许我可以创建一个带有一些符号链接的额外文件夹结构并在sys.path 中使用它,但是看看我已经做出的努力,我觉得我的整个方法存在根本性的问题。在旁注中,我也很难相信 python 真的限制了我选择源代码文件夹名称,就像在所描述的情况下那样。

如何设置我的项目文件夹和sys.path,以便在存在名称相同的项目和包的情况下以一致的方式从所有项目中导入?

【问题讨论】:

    标签: python path python-import


    【解决方案1】:

    This 是我的问题的解决方案,尽管一开始可能并不明显。

    在我的项目中,我现在引入了每个客户一个命名空间的约定。在每个客户文件夹(cust1cust2 等)中,都有一个带有此代码的 __init__.py 文件:

    import pkgutil
    __path__ = pkgutil.extend_path(__path__, __name__)
    

    我的包中的所有其他 __init__.py 文件都是空的(主要是因为我还没有时间找出如何处理它们)。

    正如here 解释的那样,extend_path 确保 Python 知道一个包中有多个子包,物理上位于其他地方,并且 - 据我了解 - 解释器在它失败后不会停止搜索在sys.path中遇到的第一个包路径下找到一个模块,但搜索__path__中的所有路径。

    我现在可以在所有项目之间以一致的方式访问所有代码,例如

    from cust1.proj1.pack1.mod1 import something
    from cust3.proj4.pack1.mod4 import something_else
    from cust3.proj1.pack4.mod4 import yet_something_else
    

    不利的一面是,我不得不创建一个更深的项目文件夹结构:

    /path/
      to/
        projects/
          cust1/
            proj1/
              Development/
                code/
                  python/
                    cust1/
                      __init__.py   <--- contains code as described above
                      proj1/
                        __init__.py <--- empty
                        pack1/
                        __init__.py <--- empty
                        mod1.py
    

    但这对我来说似乎很可接受,特别是考虑到我需要付出多少努力来维护这个约定。 sys.path/path/to/projects/cust1/proj1/Development/code/python 扩展用于此项目。

    在旁注中,我注意到在同一客户的所有 __init__.py 文件中,无论我从哪个项目导入内容,都会执行路径中出现在 sys.path 中的第一个文件。

    【讨论】:

      【解决方案2】:

      您应该使用出色的 virtualenvvirtualenvwrapper 工具。

      【讨论】:

      • 我正在使用它们,但它们只提供解决方案,只要我在同一客户的项目之间导入。如果我需要从更广泛的范围导入,在系统 python env 中的虚拟环境中也会出现同样的冲突。此外,尽管我很喜欢 virtualenv,但从我的角度来看,它弥补了 python 解释器的一个主要缺点。我真的是唯一一个遇到这个问题的人吗?
      • 恕我直言,您不应该那样在项目之间共享功能。如果他们需要共享一些实现,请考虑将其提取为库并将它们包含在两个项目中
      • hmmm,所以我问该怎么做,而回答是不做?!?那不是我所希望的。巧合的是,我实际上正在按照您的建议进行操作-我将功能提取到库中并在其他项目中使用-这正是出现此导入路径问题的地方。我的印象是,这是一个其他人都在以某种方式解决的问题,我将不得不接受这种进口限制,例如引入一种约定,仅在同一客户的包之间导入(并且还为每个客户维护一个虚拟环境)。
      【解决方案3】:

      如果您不小心将一个客户/项目的代码导入另一个客户/项目而没有注意到,会发生什么?当您交付时,它几乎肯定会失败。我会采用一次为一个项目设置 PYTHONPATH 的约定,而不是试图让你曾经编写的所有内容都可以一次导入。

      您可以为每个项目使用包装脚本来设置 PYTHONPATH 并启动 python,或者在切换项目时使用脚本来切换环境。

      当然,有些项目确实依赖于其他项目(您提到的那些库),但是如果您打算让客户能够一次导入多个项目,那么您必须安排名称不发生冲突。只有当 PYTHONPATH 上有多个项目应该一起使用时,你才会遇到这个问题。

      【讨论】:

      • “意外导入代码”怎么办?导入语句在 Python 中是显式的,因此没有隐式导入,并且使用完全限定的导入语句(如 cust1.proj1.pack1.mod1)的约定,实际上不会有意外导入错误的非完全限定包的危险,可能覆盖以前存在的。在旁注中,如果在我交付之后 出现任何故障,我的测试和部署过程确实需要审查...重新脚本/环境:这正是 virtualenvwrapper 所做的,请参阅其他答案。
      • 对于单个客户,项目名称是唯一的。然而,不同客户的项目之间有很多概念和代码重用;现在,这甚至导致不同客户的(库)项目名称相同——这就是问题的根源。功能沿水平代码集成线迁移,库项目相似,但不完全相同。
      • 我承认并不总是需要在任意项目之间导入纵横交错,但我不愿意接受 Python 解释器的技术限制阻止我这样做,所以我很高兴我找到了这个解决方案。同样,我不愿意接受“无论如何你都不应该这样做”作为答案,因为这对我来说似乎太教条了。
      猜你喜欢
      • 2022-01-21
      • 2018-12-17
      • 2016-06-21
      • 2018-05-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-15
      相关资源
      最近更新 更多