当前位置: 首页 > 面试题库 >

Python 3.5+:如何在给定完整文件路径的情况下动态导入模块(存在隐式同级导入)?

程瑞
2023-03-14
问题内容

标准库清楚地说明了如何直接导入源文件(给定源文件的绝对文件路径),但是如果该源文件使用隐式同级导入(如以下示例中所述),则此方法不起作用。

在隐式同级导入的情况下,该示例如何适应工作?

我已经签出这个和这个其他的话题#1的问题,但他们并没有解决隐同级进口
用手导入的文件。

设置/示例

这是一个说明性的例子

目录结构:

root/
  - directory/
    - app.py
  - folder/
    - implicit_sibling_import.py
    - lib.py

app.py

import os
import importlib.util

# construct absolute paths
root = os.path.abspath(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
isi_path = os.path.join(root, 'folder', 'implicit_sibling_import.py')

def path_import(absolute_path):
   '''implementation taken from https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly'''
   spec = importlib.util.spec_from_file_location(absolute_path, absolute_path)
   module = importlib.util.module_from_spec(spec)
   spec.loader.exec_module(module)
   return module

isi = path_import(isi_path)
print(isi.hello_wrapper())

lib.py

def hello():
    return 'world'

implicit_sibling_import.py

import lib # this is the implicit sibling import. grabs root/folder/lib.py

def hello_wrapper():
    return "ISI says: " + lib.hello()

#if __name__ == '__main__':
#    print(hello_wrapper())

python folder/implicit_sibling_import.py使用该if __name__ == '__main__':块运行注释掉了ISI says: worldPython 3.6中的收益。

但是运行python directory/app.py收益:

Traceback (most recent call last):
  File "directory/app.py", line 10, in <module>
    spec.loader.exec_module(module)
  File "<frozen importlib._bootstrap_external>", line 678, in exec_module
  File "<frozen importlib._bootstrap>", line 205, in _call_with_frames_removed
  File "/Users/pedro/test/folder/implicit_sibling_import.py", line 1, in <module>
    import lib
ModuleNotFoundError: No module named 'lib'

解决方法

如果我想补充import sys; sys.path.insert(0, os.path.dirname(isi_path))app.pypython app.py产量world如预期,但我想避免改写(munging)的sys.path,如果可能的。

回答要求

我想python app.py打印,ISI says: world并希望通过修改path_import功能来完成此操作。

我不确定搞砸的含义sys.path。例如。如果有directory/requests.py并且我将路径添加directory到了sys.path,我不想import requests开始导入directory/requests.py而不是导入我安装的请求pip install requests

解决方案 必须
实现为python函数,该函数接受指向所需模块的绝对文件路径并返回模块对象。

理想情况下,该解决方案不应引入副作用(例如,如果确实进行了修改sys.path,则应返回sys.path其原始状态)。如果解决方案确实带来了副作用,则应说明为什么不引入副作用就无法实现解决方案。

PYTHONPATH

如果我有多个项目正在执行此操作,则无需记住PYTHONPATH每次在它们之间切换时都要进行设置。用户应该只能够pip install运行我的项目,而无需任何其他设置即可运行它。

-m

-m标志是推荐的/
pythonic方法,但是标准库也清楚地说明了如何直接导入源文件。我想知道如何适应这种方法来处理隐式相对导入。显然,Python的内部结构必须执行此操作,因此内部结构与“直接导入源文件”文档有何不同?


问题答案:

我能想到的最简单的解决方案是sys.path在执行导入的函数中进行临时修改:

from contextlib import contextmanager

@contextmanager
def add_to_path(p):
    import sys
    old_path = sys.path
    sys.path = sys.path[:]
    sys.path.insert(0, p)
    try:
        yield
    finally:
        sys.path = old_path

def path_import(absolute_path):
   '''implementation taken from https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly'''
   with add_to_path(os.path.dirname(absolute_path)):
       spec = importlib.util.spec_from_file_location(absolute_path, absolute_path)
       module = importlib.util.module_from_spec(spec)
       spec.loader.exec_module(module)
       return module

除非您同时在另一个线程中进行导入,否则这不会引起任何问题。否则,由于sys.path已还原到其先前的状态,因此不应有有害的副作用。

编辑:

我意识到我的回答有些不尽人意,但是,深入研究代码后发现,该行spec.loader.exec_module(module)基本上导致exec(spec.loader.get_code(module.__name__),module.__dict__)被调用。这spec.loader.get_code(module.__name__)只是lib.py中包含的代码。

因此,对该问题的更好答案将必须找到一种方法import,只需通过exec语句的第二个参数简单地注入一个或多个全局变量,即可使该语句具有不同的行为。但是,“
@所做的任何使导入机制在该文件的文件夹中显示的操作,都必须持续超出初始导入的持续时间,因为调用该文件时该文件中的函数可能会执行进一步的导入”,如@问题评论中的user2357112。

不幸的是,更改import语句行为的唯一方法似乎是更改sys.path或打包__path__module.__dict__已经包含__path__让似乎并没有工作,这叶子sys.path(或者试图找出为什么执行不会代码当作一个包,即使它有__path____package__
-但我不知道从哪里开始-也许它有与没有__init__.py文件有关)。

此外,这个问题似乎并不特定于兄弟姐妹进口,importlib而是一个普遍的问题。

Edit2:
如果您不希望该模块最终出现在sys.modules下面,则应该可以正常工作(请注意,sys.modules导入期间添加的所有模块都将被 删除
):

from contextlib import contextmanager

@contextmanager
def add_to_path(p):
    import sys
    old_path = sys.path
    old_modules = sys.modules
    sys.modules = old_modules.copy()
    sys.path = sys.path[:]
    sys.path.insert(0, p)
    try:
        yield
    finally:
        sys.path = old_path
        sys.modules = old_modules


 类似资料:
  • 问题内容: 给定完整路径,如何在Python 3.4中加载Python模块? 类似的问题如何在给定完整路径的情况下导入模块?涵盖了3.4之前的Python版本,但是得出的结论是,对于给出的答案,Python 3.4的支持已被弃用,因此赞赏所有针对Python 3.4的解决方案。 请注意,此问题与“导入任意python”源文件不是重复的。(Python 3.3+),因为对此的答案也使用了Python

  • 问题内容: 给定完整路径,如何加载Python模块?请注意,该文件可以在文件系统中的任何位置,因为它是配置选项。 问题答案: 对于Python 3.5+,请使用: 对于Python 3.3和3.4,请使用: (尽管在Python 3.4中已弃用此功能。) 对于Python 2,请使用: 编译后的Python文件和DLL具有等效的便捷功能。

  • 问题内容: 给定Python模块的完整路径,如何加载它?注意,该文件可以位于文件系统中的任何位置,因为它是一个配置选项。 问题答案: 对于Python 3.5+,请使用: 对于Python 3.3和3.4,请使用: (尽管在Python 3.4中已弃用此功能。) 对于Python 2,请使用: 编译后的Python文件和DLL具有等效的便捷功能。

  • 问题内容: 我已经看到了几种通过首先导入来查找模块路径的方法。有没有一种方法,而无需导入模块? 问题答案: 使用pkgutil模块: 使用imp模块: 注意:使用imp模块,您 无法 做类似的事情

  • 问题内容: 我经历了许多Python相对导入问题,但是我无法理解该问题/无法正常工作。 我的目录结构是: 当我尝试运行时: 我得到错误 问题答案: 之所以发生这种情况,是因为就Python而言,它们是独立的,无关的软件包。 在与该目录相同的目录中创建一个目录,一切都会按预期进行。

  • 问题内容: 我经常在我的Swift应用程序中使用第三方Swift框架,并且希望使用它而不必一遍又一遍地写每个Swift文件。 有没有办法指定默认导入,就像使用文件在Objective-C中那样? 我已经检查了Xcode的构建设置和标志,但是没有一个提供此功能。 问题答案: 实际上,有一个我之前可以想到的非常简单的解决方法…… 只需将以下内容添加到应用程序项目的Objective-C桥接标头中: 斯