Python 3.5+:如何在给定完整文件路径的情况下动态导入模块(在存在隐式兄弟导入的情况下)? [英] Python 3.5+: How to dynamically import a module given the full file path (in the presence of implicit sibling imports)?

查看:117
本文介绍了Python 3.5+:如何在给定完整文件路径的情况下动态导入模块(在存在隐式兄弟导入的情况下)?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

The standard library clearly documents how to import source files directly (given the absolute file path to the source file), but this approach does not work if that source file uses implicit sibling imports as described in the example below.

如何在存在隐式兄弟导入的情况下调整该示例?

How could that example be adapted to work in the presence of implicit sibling imports?

我已经查看了这个其他 Stackoverflow对该主题提出疑问,但它们没有解决在手动导入的文件中隐式兄弟导入

I already checked out this and this other Stackoverflow questions on the topic, but they do not address implicit sibling imports within the file being imported by hand.

这是一个说明性示例

目录结构:

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文件夹/ implicit_sibling_import.py 如果__name__ =='__ main __':阻止评论out yield ISI在Python 3.6中说:world


Running python folder/implicit_sibling_import.py with the if __name__ == '__main__': block commented out yields ISI says: world in Python 3.6.

但是运行 python目录/ 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.py python app .py 按预期产生 world ,但我想避免重复 sys.path 如果可能的话。

Workaround

If I add import sys; sys.path.insert(0, os.path.dirname(isi_path)) to app.py, python app.py yields world as intended, but I would like to avoid munging the sys.path if possible.

我想 python app.py 打印 ISI说:world 我想通过修改 path_import function。

I'd like python app.py to print ISI says: world and I'd like to accomplish this by modifying the path_import function.

我不确定修改 sys.path 的含义。例如。如果有目录/ requests.py ,我将目录的路径添加到 sys .path ,我不希望导入请求开始导入目录/ requests.py 而不是导入我用安装的请求库 pip安装请求

I'm not sure of the implications of mangling sys.path. Eg. if there was directory/requests.py and I added the path to directory to the sys.path, I wouldn't want import requests to start importing directory/requests.py instead of importing the requests library that I installed with pip install requests.

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

The solution MUST be implemented as a python function that accepts the absolute file path to the desired module and returns the module object.

理想情况下,解决方案不应该引入副作用(例如,如果它确实修改了 sys.path ,它应该返回 sys.path 到其原始状态)。如果该解决方案确实引入了副作用,那么它应该解释为什么在没有引入副作用的情况下无法实现解决方案。

Ideally, the solution should not introduce side-effects (eg. if it does modify sys.path, it should return sys.path to its original state). If the solution does introduce side-effects, it should explain why a solution cannot be achieved without introducing side-effects.

如果我有多个项目这样做,我不想记得设置 PYTHONPATH 每次我在它们之间切换。用户应该能够 pip install 我的项目并运行它而无需任何额外的设置。

If I have multiple projects doing this, I don't want to have to remember to set PYTHONPATH every time I switch between them. The user should just be able to pip install my project and run it without any additional setup.

-m 标志是推荐/ pythonic方法,但标准库也清楚地记录如何直接导入源文件。我想知道如何调整这种方法来应对隐含的相对进口。显然,Python的内部必须这样做,那么内部结构与直接导入源文件文档有什么不同呢?

The -m flag is the recommended/pythonic approach, but the standard library also clearly documents How to import source files directly. I'd like to know how I can adapt that approach to cope with implicit relative imports. Clearly, Python's internals must do this, so how do the internals differ from the "import source files directly" documentation?

推荐答案

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

The easiest solution I could come up with is to temporarily modify sys.path in the function doing the import:

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 恢复到之前的状态,因此不会产生不必要的副作用。

This should not cause any problems unless you do imports in another thread concurrently. Otherwise, since sys.path is restored to its previous state, there should be no unwanted side effects.

编辑:

我意识到我的答案有点令人不满意但是,深入研究代码会发现,行规范。 loader.exec_module(module)基本上导致 exec(spec.loader.get_code(module .__ name __),module .__ dict __)被调用。这里 spec.loader.get_code(module .__ name __)就是lib.py中包含的代码。

I realize that my answer is somewhat unsatisfactory but, digging into the code reveals that, the line spec.loader.exec_module(module) basically results in exec(spec.loader.get_code(module.__name__),module.__dict__) getting called. Here spec.loader.get_code(module.__name__) is simply the code contained in lib.py.

因此,对问题的更好答案必须找到一种方法,使 import 语句的行为简单通过exec语句的第二个参数注入一个或多个全局变量。但是,无论你做什么使导入机器看起来都在该文件的文件夹中,它必须在初始导入的时间内停留,因为当你调用它们时,该文件中的函数可能会执行进一步的导入,如@所述。问题评论中的user2357112。

Thus a better answer to the question would have to find a way to make the import statement behave differently by simply injecting one or more global variables through the second argument of the exec-statement. However, "whatever you do to make the import machinery look in that file's folder, it'll have to linger beyond the duration of the initial import, since functions from that file might perform further imports when you call them", as stated by @user2357112 in the question comments.

不幸的是,改变 import 语句行为的唯一方法似乎是更改 sys.path 或在包 __ path __ 中。 module .__ dict __ 已经包含 __ path __ 所以这似乎不起作用,它留下 sys。 path (或试图找出为什么exec不会将代码视为包,即使它有 __ path __ __ package__ ... - 但我不知道从哪里开始 - 也许它与没有 __ init __。py 文件)有关。

Unfortunately the only way to change the behavior of the import statement seems to be to change sys.path or in a package __path__. module.__dict__ already contains __path__ so that doesn't seem to work which leaves sys.path (Or trying to figure out why exec does not treat the code as a package even though it has __path__ and __package__ ... - But I don't know where to start - Maybe it has something to do with having no __init__.py file).

此外,这个问题似乎并不特定于 importlib ,而是兄弟导入

Furthermore this issue does not seem to be specific to importlib but rather a general problem with sibling imports.

Edit2:如果你不希望模块最终进入 sys.modules 以下应该可以工作(请注意,任何模块都添加到 sys.modules 导入期间删除):

If you don't want the module to end up in sys.modules the following should work (Note that any modules added to sys.modules during the import are removed):

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.5+:如何在给定完整文件路径的情况下动态导入模块(在存在隐式兄弟导入的情况下)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆