【问题标题】:Python import from submodule of package exported by another filePython 从另一个文件导出的包的子模块导入
【发布时间】:2021-04-20 12:13:58
【问题描述】:

我有 imports.py 包含:

import os as exported_os

foo.py 包含:

from imports import exported_os
print(exported_os.path.devnull)    # works

from imports.exported_os.path import devnull    # doesn't

有没有办法使第二次导入工作?我尝试将__path__ 添加到imports.py 并摆弄它,但什么也得不到。

实际用例:ossome_library_version_nexported_ossome_library_version(我试图避免在不同文件中出现多个 some_library_version_n 实例)。

【问题讨论】:

  • path 自己的模块(即有一个名为path.py 的文件),还是path 是具有devnull 属性的对象?您的错误看起来可能是后者。
  • @Arne os.path 是一个模块
  • 哦,我不知何故没有意识到你在谈论 stdlibs os module.strange。
  • 我要放弃了。我试图通过动态更新sys.modules 来找到修复程序,但os 特别是一个奇怪的模块,它为了可导入而做一些疯狂的事情。接下来我会尝试将自定义MetaPathFinder 添加到sys.meta_path,将import imports.something.x.y.z 重定向到__import__("something_else.x.y.z"),但是阅读有关python-importing 的文档会融化我的大脑,所以我将把它留在这个模糊的评论中.
  • 我什至认为您不需要单独的文件来观察这种行为,对吧?在imports.py 中,您也不能使用from exported_os.path import devnull,我认为这是等效的行为。

标签: python python-3.x python-import python-packaging


【解决方案1】:

一种方法

目录结构:

__init__.py
foo.py
imports/
 ├ __init__.py
 └ exported_os/
    ├ __init__.py
    └ path.py

imports/exported_os/__init__.py:

from . import path
from os import *  # not necessary for the question 
                  # but it makes `exported_os` more like `os`
                  # e.g., `exported_os.listdir` will be callable

imports/exported_os/path.py:

from os.path import *

这样,您可以像使用os 一样使用exported_os 和子模块path。与import 不同,from 采用模块和类。

另一种方法

imports.py:

import os
import sys

ms = []
for m in sys.modules:
    if m.startswith('os'):
        ms.append(m)

for m in ms:
    sys.modules['imports.exported_os' + m[2:]] = sys.modules[m]

或者,通过显式扩展sys.modules,您可以像使用os 一样使用exported_os 及其子模块。

为什么不能简单地更改os 的名称

如果你打开.../lib/python3.9/os.py,你会发现下面一行:

sys.modules['os.path'] = path

所以即使你把.../lib/python3.9/os.py复制到.../lib/python3.9/exported_os.py,下面的也行不通:

from exported_os.path import devnull

但如果你将sys.modules['os.path'] 更改为sys.modules['exported_os.path'] 就可以了。

【讨论】:

  • 谢谢,我真的想要一些不需要我指定 os 的所有子模块的东西。
  • @rlms 我添加了另一种方法和更多解释。如果不能接受请查看并留言。
  • @GyuHyeonChoi 我认为 import * 必须谨慎使用,因为如果不小心可能会出现命名冲突。
  • @mgross 应该小心。 : ) 在这种情况下,exported_os 只包含两行代码到import。会好的。
【解决方案2】:

你得到的错误是这样的:

ModuleNotFoundError:没有名为“imports.exported_os”的模块; 'imports' 不是一个包

当您编码from imports import exported_os 时,imports 可以引用文件imports.py 实现的模块。但是,当名称 importsfrom imports.exported_os.path import devnull 中的层次结构的一部分时,imports 必须是一个包,实现为目录结构中的目录,如下所示:

__init__.py
imports
    __init__.py
    exported_os
        __init__.py
        path.py

其中包含最顶层__init__.py 的目录必须位于sys.path 搜索路径中。

因此,除非您想将目录结构重新排列为上述内容,否则您要使用的语法(和选择性导入)在不深入 Python 模块系统内部的情况下实际上是无法使用的。

虽然由于您独特的版本控制问题,这不是您希望能够执行from ... import ... 的解决方案,但让我建议另一种执行此版本控制的方法。在您的情况下,您可以执行以下操作。创建一个包,my_imports(任意命名):

my_imports
    __init__.py

__init__.py的内容是:

import some_library_version_n as some_library_version

然后在foo.py 和任何其他需要此模块的文件中:

from my_imports import *

这是将版本控制依赖项放在一个文件中的另一种方法。如果您有其他类似的依赖项,您当然可以将它们添加到此文件中,并且您可以从 my_imports 导入您感兴趣的名称。您仍然有导入整个模块 some_library_version 的问题。

但是,我们可以更进一步。假设您的库的各个版本具有组件ABC,您可能有兴趣单独或一起导入它们。然后您可以执行以下操作。让我们将包命名为 some_library_version,因为它只会处理这个版本控制问题:

some_library_version/init.py

from some_library_version_n import A
from some_library_version_n import B
from some_library_version_n import C

foo.py

from some_library_version import A, C

【讨论】:

    【解决方案3】:

    添加的大多数答案都是准确的,但没有添加为什么以这种方式工作的上下文,GyuHyeon 解释得很好,但它只是将它恢复到import 是一个花哨的文件包含系统,它可以检查到 std 库,然后安装的并最终进入提供的上下文中,在调用的位置和给定的from 上添加上下文。

    这个例子给出了导入特定函数dirname()的各种方法,lib os只是一个文件夹,如果你想象os在你的工作文件夹中,那么导入路径应该是相同的或'./os'和因为python,一切都是一个类,所以导入将搜索.os/__init__.py,所以如果你的库没有导入子目录,它将没有效果。

    from os.path import dirname as my_fucntion  # (A_2)
    from os import path as my_lib  # (B_2)
    
    from os.path import dirname  # (C_1)
    from os import path  # (B_1)
    import os  # (A_1)
    
    if __name__ == '__main__':
        print(os.path.dirname(__file__))  # (A_1)
        print(path.dirname(__file__))  # (B_1)
        print(dirname(__file__))  # (C_1)
    
        print(my_lib.dirname(__file__))  # (B_2)
        print(my_fucntion(__file__))  # (A_2)
    
    

    【讨论】:

      【解决方案4】:

      您可以尝试使用sys.path.append(...),例如:

      import sys
      sys.path.append(<your path to devnull goes here>)
      

      也许不太好,但您可以使用 pathlib 库和路径连接来构造路径(但如果文件位于单独的文件夹结构中,那么不幸的是必须对文件结构做出一些假设):

      from pathlib import Path
      from os import path
      sys.path.append(path.join(str(Path(__file__).parents[<integer that tells how many folders to go up>]), <path to devnull>))
      

      除了pathlib,您还可以使用os.path 中的dirname 函数。 附加到系统路径后,您可以使用:

      import devnull
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-04-30
        • 1970-01-01
        • 1970-01-01
        • 2021-01-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-05-18
        相关资源
        最近更新 更多