ChatGPT解决这个技术问题 Extra ChatGPT

如何在给定完整路径的情况下导入模块?

如何在给定完整路径的情况下加载 Python 模块?

请注意,该文件可以位于文件系统中的任何位置。

好而简单的问题 - 以及有用的答案,但它们让我想知道 python 口头禅“有一种明显的方法”会发生什么。它看起来不像是一个单一的或简单而明显的答案。 . 对于这样一个基本操作来说,这似乎是可笑的 hacky 和版本依赖(而且在新版本中它看起来更加臃肿......)。
@inger python 口头禅“有一种明显的方法”发生了什么[...] [不是] 一个单一的或简单而明显的答案 [...] 可笑的 hacky [... ] 在新版本中更加臃肿 欢迎来到可怕的 python 包管理世界。 Python 的 importvirtualenvpipsetuptools 之类的都应该被扔掉并用工作代码代替。我只是试图摸索 virtualenv 或者是 pipenv 并且必须通过相当于 Jumbo Jet 手册的工作。这种设计如何被炫耀为处理部门的解决方案完全让我无法理解。
相关 XKCD xkcd.com/1987
@JohnFrazer 由于不断唠叨那些懒得阅读两段文档的人,情况变得更糟了。您的 XKCD 并不真正相关,因为它显示了这类人在尝试某些事情直到某些事情奏效时可以实现的目标。此外,仅仅因为有一种新方法并不意味着现在有“两种明显的方法”。旧方式在某些情况下很明显,新方式向其他情况介绍了易用性。当您真正关心 DevX 时,就会发生这种情况。
并认为 Java 甚至 PHP(这些天)有清晰而简单的方法来拆分包/命名空间中的内容并重用它。看到 Python 如此痛苦,它在其他各个方面都采用了简单性,这真是令人震惊。

j
jdehesa

对于 Python 3.5+ 使用 (docs):

import importlib.util
import sys
spec = importlib.util.spec_from_file_location("module.name", "/path/to/file.py")
foo = importlib.util.module_from_spec(spec)
sys.modules["module.name"] = foo
spec.loader.exec_module(foo)
foo.MyClass()

对于 Python 3.3 和 3.4,请使用:

from importlib.machinery import SourceFileLoader

foo = SourceFileLoader("module.name", "/path/to/file.py").load_module()
foo.MyClass()

(尽管这在 Python 3.4 中已被弃用。)

对于 Python 2 使用:

import imp

foo = imp.load_source('module.name', '/path/to/file.py')
foo.MyClass()

对于已编译的 Python 文件和 DLL,有等效的便利函数。

另见http://bugs.python.org/issue21436


如果我知道命名空间 - 'module.name' - 我会使用 __import__
@SridharRatnakumar imp.load_source 的第一个参数的值仅设置返回模块的 .__name__。它不影响加载。
@丹D。 — imp.load_source() 的第一个参数确定在 sys.modules 字典中创建的新条目的键,因此第一个参数确实会影响加载。
@AXO 甚至更多人想知道为什么如此简单和基本的事情必须如此复杂。它没有许多其他语言。
@Mahesha999 因为 importlib.import_module() 不允许您按文件名导入模块,这是最初的问题所在。
D
Daryl Spitzer

向 sys.path 添加路径(而不是使用 imp)的优势在于,它简化了从单个包中导入多个模块时的操作。例如:

import sys
# the mock-0.3.1 dir contains testcase.py, testutils.py & mock.py
sys.path.append('/foo/bar/mock-0.3.1')

from testcase import TestCase
from testutils import RunTests
from mock import Mock, sentinel, patch

我们如何使用 sys.path.append 指向单个 python 文件而不是目录?
:-) 也许您的问题更适合作为 StackOverflow 问题,而不是对答案的评论。
python 路径可以包含 zip 档案、“eggs”(一种复杂的 zip 档案)等。可以从中导入模块。所以路径元素确实是文件的容器,但它们不一定是目录。
请注意 Python 缓存导入语句的事实。在极少数情况下,您有两个不同的文件夹共享一个类名 (classX),向 sys.path 添加路径、导入 classX、删除路径并重复剩余路径的方法将不起作用。 Python 将始终从其缓存的第一个路径加载该类。就我而言,我的目标是创建一个插件系统,其中所有插件都实现特定的 classX。我最终使用了 SourceFileLoader,请注意它的 deprecation is controversial
请注意,这种方法允许导入的模块从同一目录中导入其他模块,这些模块通常会这样做,而接受的答案的方法则不会(至少在 3.7 上)。如果在运行时不知道模块名称,则可以在此处使用 importlib.import_module(mod_name) 而不是显式导入我会在最后添加一个 sys.path.pop(),不过,假设导入的代码不会尝试导入更多模块用过的。
P
Peter Mortensen

要导入您的模块,您需要将其目录临时或永久添加到环境变量中。

暂时地

import sys
sys.path.append("/path/to/my/modules/")
import my_module

永久

将以下行添加到 Linux 中的 .bashrc(或替代)文件并在终端中执行 source ~/.bashrc(或替代):

export PYTHONPATH="${PYTHONPATH}:/path/to/my/modules/"

来源/来源:saarrrranother Stack Exchange question


如果您想在其他地方的 jupyter notebook 中创建一个项目,这个“临时”解决方案是一个很好的答案。
但是... 篡改路径很危险
@ShaiAlon您正在添加路径,因此除了将代码从一台计算机传输到另一台计算机时没有危险,路径可能会混乱。所以,对于包开发,我只导入本地包。此外,包名称应该是唯一的。如果您担心,请使用临时解决方案。
K
K. Frank

如果您的顶级模块不是一个文件,而是使用 __init__.py 打包为一个目录,那么可接受的解决方案几乎可以工作,但并不完全正确。在 Python 3.5+ 中需要以下代码(注意添加的以 'sys.modules' 开头的行):

MODULE_PATH = "/path/to/your/module/__init__.py"
MODULE_NAME = "mymodule"
import importlib
import sys
spec = importlib.util.spec_from_file_location(MODULE_NAME, MODULE_PATH)
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module 
spec.loader.exec_module(module)

如果没有这一行,当执行 exec_module 时,它会尝试将顶级 __init__.py 中的相对导入绑定到顶级模块名称——在本例中为“mymodule”。但是“mymodule”尚未加载,因此您将收到错误“SystemError:未加载父模块'mymodule',无法执行相对导入”。所以你需要在加载之前绑定名称。这样做的原因是相对导入系统的基本不变量:“保持不变的是,如果你有 sys.modules['spam'] 和 sys.modules['spam.foo'] (就像你在上面的导入之后一样) ),后者必须作为前者的 foo 属性出现”as discussed here


非常感谢!此方法启用子模块之间的相对导入。伟大的!
此答案与此处的文档相匹配:docs.python.org/3/library/…
但什么是 mymodule
@Gulzar,这是您想给模块起的任何名称,以便您以后可以这样做:“from mymodule import myclass”
尽管非常规,但如果您的包入口点不是 __init__.py,您仍然可以将其作为包导入。在创建规范后包括 spec.submodule_search_locations = [os.path.dirname(MODULE_PATH)]。您还可以通过将此值设置为 None__init__.py 视为非包(例如单个模块)
P
Peter Mortensen

听起来您不想专门导入配置文件(它有很多副作用和其他复杂性)。您只想运行它,并能够访问生成的命名空间。标准库以 runpy.run_path 的形式专门为此提供了一个 API:

from runpy import run_path
settings = run_path("/path/to/file.py")

该接口在 Python 2.7 和 Python 3.2+ 中可用。


我喜欢这种方法,但是当我得到 run_path 的结果时,它是一个我似乎无法访问的字典?
“无法访问”是什么意思?您不能从中导入(这就是为什么在实际上不需要导入样式访问时这只是一个不错的选择),但内容应该可以通过常规 dict API(result[name]result.get('name', default_value) 等)获得
@Maggyero 命令行永远不会通过 runpy.run_path,但如果给定的路径是目录或 zip 文件,那么它最终会委托给 runpy.run_module 以执行 __main__。 “它是脚本、目录还是 zip 文件?”的重复逻辑还不够复杂,不值得委托给 Python 代码。
此外,通过查看 C 函数 pymain_run_moduleimplementation,似乎 CPython 委托给 Python 函数 runpy._run_module_as_main 而不是 runpy.run_module - 尽管如果我理解正确,唯一的区别是第一个函数执行代码在内置 __main__ 环境中(参见 here)而第二个函数在新环境中执行它?
@Maggyero 是的,这是唯一的区别。最初它使用公共函数,但结果与解释器的 -i 选项交互很糟糕(这会将您放入原始 __main__ 模块中的交互式 shell,因此在新模块中运行 -m 很不方便)
P
Peter Mortensen

您也可以这样做,将配置文件所在的目录添加到 Python 加载路径中,然后进行正常导入,假设您事先知道文件的名称,在本例中为“config”。

凌乱,但它的工作原理。

configfile = '~/config.py'

import os
import sys

sys.path.append(os.path.dirname(os.path.expanduser(configfile)))

import config

那不是动态的。
我试过:config_file = 'setup-for-chats', setup_file = get_setup_file(config_file + ".py"), sys.path.append(os.path.dirname(os.path.expanduser(setup_file))), import config_file >>“ImportError:没有名为 config_file 的模块”
P
Peter Mortensen

您可以使用

load_source(module_name, path_to_file)

imp module 中的方法。


... 和 imp.load_dynamic(module_name, path_to_file) 用于 DLL
注意 imp 现在已弃用。
P
Peter Mortensen

你的意思是加载还是导入?

您可以操纵 sys.path 列表指定模块的路径,然后导入您的模块。例如,给定一个模块:

/foo/bar.py

你可以这样做:

import sys
sys.path[0:0] = ['/foo'] # Puts the /foo directory at the start of your path
import bar

B/c sys.path[0] = xy 覆盖第一个路径项,而 path[0:0] =xy 等价于 path.insert(0, xy)
嗯 path.insert 对我有用,但 [0:0] 技巧没有。
sys.path[0:0] = ['/foo']
Explicit is better than implicit. 那么为什么不用 sys.path.insert(0, ...) 而不是 sys.path[0:0]
@dom0 那就用 sys.path.append(...) 吧。更清楚了。
P
Peter Mortensen

这是一些适用于所有 Python 版本的代码,从 2.7 到 3.5,甚至可能还有其他版本。

config_file = "/tmp/config.py"
with open(config_file) as f:
    code = compile(f.read(), config_file, 'exec')
    exec(code, globals(), locals())

我测试了它。它可能很难看,但到目前为止,它是唯一适用于所有版本的。


这个答案对我有用,而 load_source 没有,因为它导入脚本并在导入时提供对模块和全局变量的脚本访问。
请注意,此答案的行为与导入模块不同,至于模块(是否以正常方式导入)代码的“全局”范围是模块对象,而对于这个答案,它是被调用对象的全局范围。 (尽管也可以修改此答案以更改范围,但任何字典都可以作为 globalslocals 传入)
M
Mad Physicist

我提出了一个稍微修改过的 @SebastianRittau's wonderful answer 版本(我认为是 Python > 3.4),它允许您使用 spec_from_loader 而不是 spec_from_file_location 将具有任何扩展名的文件作为模块加载:

from importlib.util import spec_from_loader, module_from_spec
from importlib.machinery import SourceFileLoader 

spec = spec_from_loader("module.name", SourceFileLoader("module.name", "/path/to/file.py"))
mod = module_from_spec(spec)
spec.loader.exec_module(mod)

在显式 SourceFileLoader 中编码路径的优点是 machinery 不会尝试从扩展名中找出文件的类型。这意味着您可以使用此方法加载类似于 .txt 的文件,但如果不指定加载程序,您将无法使用 spec_from_file_location 执行此操作,因为 .txt 不在 importlib.machinery.SOURCE_SUFFIXES 中。

我已将基于此的实现和 @SamGrondahl's useful modification 放入我的实用程序库 haggis。该函数称为 haggis.load.load_module。它添加了一些巧妙的技巧,例如在加载模块命名空间时将变量注入模块命名空间的能力。


P
Peter Mortensen

您可以使用 __import__chdir 执行此操作:

def import_file(full_path_to_module):
    try:
        import os
        module_dir, module_file = os.path.split(full_path_to_module)
        module_name, module_ext = os.path.splitext(module_file)
        save_cwd = os.getcwd()
        os.chdir(module_dir)
        module_obj = __import__(module_name)
        module_obj.__file__ = full_path_to_module
        globals()[module_name] = module_obj
        os.chdir(save_cwd)
    except Exception as e:
        raise ImportError(e)
    return module_obj


import_file('/home/somebody/somemodule.py')

当标准库已经解决了这个问题时,为什么还要编写 14 行错误代码?您尚未对 full_path_to_module 或 os.whatever 操作的格式或内容进行错误检查;并且使用包罗万象的 except: 子句很少是一个好主意。
你应该在这里使用更多的“try-finally”。例如 save_cwd = os.getcwd() try: … finally: os.chdir(save_cwd)
@ChrisJohnson this is already addressed by the standard library是的,但是python有不向后兼容的讨厌习惯......因为检查的答案说在3.3之前和之后有2种不同的方式。在这种情况下,我宁愿编写自己的通用函数,也不愿即时检查版本。是的,也许这段代码没有很好的错误保护,但它显示了一个想法(它是 os.chdir(),我还没想过),基于它我可以编写更好的代码。因此+1。
如果这实际上返回了模块,那就太酷了。
K
Kumar KS

如果我们在同一个项目中有脚本,但在不同的目录下,我们可以通过以下方法解决这个问题。

在这种情况下,utils.pysrc/main/util/

import sys
sys.path.append('./')

import src.main.util.utils
#or
from src.main.util.utils import json_converter # json_converter is example method

M
Mathieu Rodic

我相信你可以使用imp.find_module()imp.load_module()来加载指定的模块。您需要将模块名称从路径中拆分出来,即如果您想加载 /home/mypath/mymodule.py,您需要执行以下操作:

imp.find_module('mymodule', '/home/mypath/')

...但这应该可以完成工作。


P
Peter Mortensen

创建 Python 模块 test.py:

import sys
sys.path.append("<project-path>/lib/")
from tes1 import Client1
from tes2 import Client2
import tes3

创建 Python 模块 test_check.py:

from test import Client1
from test import Client2
from test import test3

我们可以从模块中导入导入的模块。


M
Mathieu Rodic

您可以使用 pkgutil 模块(特别是 walk_packages 方法)获取当前目录中的软件包列表。从那里使用 importlib 机制来导入您想要的模块是微不足道的:

import pkgutil
import importlib

packages = pkgutil.walk_packages(path='.')
for importer, name, is_package in packages:
    mod = importlib.import_module(name)
    # do whatever you want with module now, it's been imported!

f
fny

有一个专门用于此的 package

from thesmuggler import smuggle

# À la `import weapons`
weapons = smuggle('weapons.py')

# À la `from contraband import drugs, alcohol`
drugs, alcohol = smuggle('drugs', 'alcohol', source='contraband.py')

# À la `from contraband import drugs as dope, alcohol as booze`
dope, booze = smuggle('drugs', 'alcohol', source='contraband.py')

它在 Python 版本(Jython 和 PyPy 也是)中进行了测试,但根据项目的大小,它可能会有些过大。


R
Redlegjed

Python 3.4 的这个领域似乎理解起来极其曲折!然而,在开始使用 Chris Calloway 的代码进行一些黑客攻击后,我设法得到了一些工作。这是基本功能。

def import_module_from_file(full_path_to_module):
    """
    Import a module given the full path/filename of the .py file

    Python 3.4

    """

    module = None

    try:

        # Get module name and path from full path
        module_dir, module_file = os.path.split(full_path_to_module)
        module_name, module_ext = os.path.splitext(module_file)

        # Get module "spec" from filename
        spec = importlib.util.spec_from_file_location(module_name,full_path_to_module)

        module = spec.loader.load_module()

    except Exception as ec:
        # Simple error printing
        # Insert "sophisticated" stuff here
        print(ec)

    finally:
        return module

这似乎使用了 Python 3.4 中未弃用的模块。我不假装理解为什么,但它似乎在程序中起作用。我发现 Chris 的解决方案在命令行上有效,但在程序内部无效。


u
ubershmekel

我为您制作了一个使用 imp 的包。我称之为 import_file,它是这样使用的:

>>>from import_file import import_file
>>>mylib = import_file('c:\\mylib.py')
>>>another = import_file('relative_subdir/another.py')

你可以在:

http://pypi.python.org/pypi/import_file

http://code.google.com/p/import-file/


操作系统目录? (批准评论的最少字符)。
我花了一整天的时间对 pyinstaller 生成的 exe 中的导入错误进行故障排除。最后,这是唯一对我有用的东西。非常感谢你做了这个!
P
Peter Zhu

要从给定文件名导入模块,您可以临时扩展路径,并在 finally 块 reference: 中恢复系统路径

filename = "directory/module.py"

directory, module_name = os.path.split(filename)
module_name = os.path.splitext(module_name)[0]

path = list(sys.path)
sys.path.insert(0, directory)
try:
    module = __import__(module_name)
finally:
    sys.path[:] = path # restore

A
Ataxias

使用 importlib 而不是 imp 包的简单解决方案(针对 Python 2.7 进行了测试,尽管它也应该适用于 Python 3):

import importlib

dirname, basename = os.path.split(pyfilepath) # pyfilepath: '/my/path/mymodule.py'
sys.path.append(dirname) # only directories should be added to PYTHONPATH
module_name = os.path.splitext(basename)[0] # '/my/path/mymodule.py' --> 'mymodule'
module = importlib.import_module(module_name) # name space of defined module (otherwise we would literally look for "module_name")

现在您可以直接使用导入模块的命名空间,如下所示:

a = module.myvar
b = module.myfunc(a)

这个解决方案的优点是我们甚至不需要知道我们想要导入的模块的实际名称,就可以在我们的代码中使用它。这很有用,例如在模块路径是可配置参数的情况下。


这样,您正在修改 sys.path,它并不适合所有用例。
@bgusach 这可能是真的,但在某些情况下它也是可取的(在从单个包中导入多个模块时,添加到 sys.path 的路径可以简化事情)。无论如何,如果不希望这样做,可以立即执行sys.path.pop()
P
Peter Mortensen

我并不是说它更好,但为了完整起见,我想推荐 exec 函数,它在 Python 2 和 Python 3 中都可用。

exec 允许您在以字典形式提供的全局范围或内部范围内执行任意代码。

例如,如果您有一个使用函数 foo() 存储在 "/path/to/module" 中的模块,您可以通过执行以下操作来运行它:

module = dict()
with open("/path/to/module") as f:
    exec(f.read(), module)
module['foo']()

这使得动态加载代码更加明确,并赋予您一些额外的能力,例如提供自定义内置函数的能力。

如果通过属性而不是键访问对您来说很重要,您可以为全局变量设计一个自定义 dict 类,以提供此类访问权限,例如:

class MyModuleClass(dict):
    def __getattr__(self, name):
        return self.__getitem__(name)

ジョージ

添加到 Sebastian Rittau 的答案:至少对于 CPython,有 pydoc,虽然没有正式声明,但导入文件就是它的作用:

from pydoc import importfile
module = importfile('/path/to/module.py')

PS。为了完整起见,在撰写本文时引用了当前的实现:pydoc.py,我很高兴地说,在 xkcd 1987 的脉络中,它既不使用issue 21436 中提到的实现——至少,不是逐字记录。


j
joran

这应该工作

path = os.path.join('./path/to/folder/with/py/files', '*.py')
for infile in glob.glob(path):
    basename = os.path.basename(infile)
    basename_without_extension = basename[:-3]

    # http://docs.python.org/library/imp.html?highlight=imp#module-imp
    imp.load_source(basename_without_extension, infile)

删除扩展的更通用方法是:name, ext = os.path.splitext(os.path.basename(infile))。您的方法有效,因为先前对 .py 扩展名的限制。此外,您可能应该将模块导入到某个变量/字典条目。
E
Eric Leschinski

在运行时导入包模块(Python 配方)

http://code.activestate.com/recipes/223972/

###################
##                #
## classloader.py #
##                #
###################

import sys, types

def _get_mod(modulePath):
    try:
        aMod = sys.modules[modulePath]
        if not isinstance(aMod, types.ModuleType):
            raise KeyError
    except KeyError:
        # The last [''] is very important!
        aMod = __import__(modulePath, globals(), locals(), [''])
        sys.modules[modulePath] = aMod
    return aMod

def _get_func(fullFuncName):
    """Retrieve a function object from a full dotted-package name."""

    # Parse out the path, module, and function
    lastDot = fullFuncName.rfind(u".")
    funcName = fullFuncName[lastDot + 1:]
    modPath = fullFuncName[:lastDot]

    aMod = _get_mod(modPath)
    aFunc = getattr(aMod, funcName)

    # Assert that the function is a *callable* attribute.
    assert callable(aFunc), u"%s is not callable." % fullFuncName

    # Return a reference to the function itself,
    # not the results of the function.
    return aFunc

def _get_class(fullClassName, parentClass=None):
    """Load a module and retrieve a class (NOT an instance).

    If the parentClass is supplied, className must be of parentClass
    or a subclass of parentClass (or None is returned).
    """
    aClass = _get_func(fullClassName)

    # Assert that the class is a subclass of parentClass.
    if parentClass is not None:
        if not issubclass(aClass, parentClass):
            raise TypeError(u"%s is not a subclass of %s" %
                            (fullClassName, parentClass))

    # Return a reference to the class itself, not an instantiated object.
    return aClass


######################
##       Usage      ##
######################

class StorageManager: pass
class StorageManagerMySQL(StorageManager): pass

def storage_object(aFullClassName, allOptions={}):
    aStoreClass = _get_class(aFullClassName, StorageManager)
    return aStoreClass(allOptions)

P
Peter Mortensen

在 Linux 中,在 Python 脚本所在的目录中添加符号链接是可行的。

IE:

ln -s /absolute/path/to/module/module.py /absolute/path/to/script/module.py

Python 解释器将创建 /absolute/path/to/script/module.pyc 并在您更改 /absolute/path/to/module/module.py 的内容时更新它。

然后在文件 mypythonscript.py 中包含以下内容:

from module import *

这是我使用的hack,它给我带来了一些问题。其中一个更痛苦的问题是,IDEA 存在一个问题,即它不会从链接中获取更改的代码,但仍试图保存它认为存在的内容。最后一个拯救的比赛条件就是坚持......因此我失去了相当多的工作。
@Gripp 不确定我是否理解您的问题,但我经常(几乎完全)使用 CyberDuck 之类的客户端通过 SFTP 从我的桌面在远程服务器上编辑我的脚本,在这种情况下尝试和编辑符号链接文件,而不是编辑原始文件更安全。您可以通过使用 git 并检查您的 git status 来发现其中一些问题,以验证您对脚本所做的更改实际上是在将其返回到源文档中,而不是迷失在以太中。
P
Peter Mortensen

这将允许在 3.4 中导入已编译的 (pyd) Python 模块:

import sys
import importlib.machinery

def load_module(name, filename):
    # If the Loader finds the module name in this list it will use
    # module_name.__file__ instead so we need to delete it here
    if name in sys.modules:
        del sys.modules[name]
    loader = importlib.machinery.ExtensionFileLoader(name, filename)
    module = loader.load_module()
    locals()[name] = module
    globals()[name] = module

load_module('something', r'C:\Path\To\something.pyd')
something.do_something()

P
Peter Mortensen

一个非常简单的方法:假设你想要导入文件的相对路径 ../../MyLibs/pyfunc.py

libPath = '../../MyLibs'
import sys
if not libPath in sys.path: sys.path.append(libPath)
import pyfunc as pf

但是,如果您没有守卫就成功了,那么您最终可以走很长的路。


P
Peter Mortensen

我基于 importlib 模块编写了自己的全局和可移植导入函数,用于:

能够将两个模块作为子模块导入,并将模块的内容导入父模块(如果没有父模块,则导入全局)。

能够导入文件名中带有句点字符的模块。

能够导入具有任何扩展名的模块。

能够为子模块使用独立名称,而不是默认情况下不带扩展名的文件名。

能够根据先前导入的模块定义导入顺序,而不是依赖于 sys.path 或任何搜索路径存储。

示例目录结构:

<root>
 |
 +- test.py
 |
 +- testlib.py
 |
 +- /std1
 |   |
 |   +- testlib.std1.py
 |
 +- /std2
 |   |
 |   +- testlib.std2.py
 |
 +- /std3
     |
     +- testlib.std3.py

包含依赖和顺序:

test.py
  -> testlib.py
    -> testlib.std1.py
      -> testlib.std2.py
    -> testlib.std3.py

执行:

最新更改存储:https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/python/tacklelib/tacklelib.py

测试.py:

import os, sys, inspect, copy

SOURCE_FILE = os.path.abspath(inspect.getsourcefile(lambda:0)).replace('\\','/')
SOURCE_DIR = os.path.dirname(SOURCE_FILE)

print("test::SOURCE_FILE: ", SOURCE_FILE)

# portable import to the global space
sys.path.append(TACKLELIB_ROOT) # TACKLELIB_ROOT - path to the library directory
import tacklelib as tkl

tkl.tkl_init(tkl)

# cleanup
del tkl # must be instead of `tkl = None`, otherwise the variable would be still persist
sys.path.pop()

tkl_import_module(SOURCE_DIR, 'testlib.py')

print(globals().keys())

testlib.base_test()
testlib.testlib_std1.std1_test()
testlib.testlib_std1.testlib_std2.std2_test()
#testlib.testlib.std3.std3_test()                             # does not reachable directly ...
getattr(globals()['testlib'], 'testlib.std3').std3_test()     # ... but reachable through the `globals` + `getattr`

tkl_import_module(SOURCE_DIR, 'testlib.py', '.')

print(globals().keys())

base_test()
testlib_std1.std1_test()
testlib_std1.testlib_std2.std2_test()
#testlib.std3.std3_test()                                     # does not reachable directly ...
globals()['testlib.std3'].std3_test()                         # ... but reachable through the `globals` + `getattr`

测试库.py:

# optional for 3.4.x and higher
#import os, inspect
#
#SOURCE_FILE = os.path.abspath(inspect.getsourcefile(lambda:0)).replace('\\','/')
#SOURCE_DIR = os.path.dirname(SOURCE_FILE)

print("1 testlib::SOURCE_FILE: ", SOURCE_FILE)

tkl_import_module(SOURCE_DIR + '/std1', 'testlib.std1.py', 'testlib_std1')

# SOURCE_DIR is restored here
print("2 testlib::SOURCE_FILE: ", SOURCE_FILE)

tkl_import_module(SOURCE_DIR + '/std3', 'testlib.std3.py')

print("3 testlib::SOURCE_FILE: ", SOURCE_FILE)

def base_test():
  print('base_test')

testlib.std1.py:

# optional for 3.4.x and higher
#import os, inspect
#
#SOURCE_FILE = os.path.abspath(inspect.getsourcefile(lambda:0)).replace('\\','/')
#SOURCE_DIR = os.path.dirname(SOURCE_FILE)

print("testlib.std1::SOURCE_FILE: ", SOURCE_FILE)

tkl_import_module(SOURCE_DIR + '/../std2', 'testlib.std2.py', 'testlib_std2')

def std1_test():
  print('std1_test')

testlib.std2.py:

# optional for 3.4.x and higher
#import os, inspect
#
#SOURCE_FILE = os.path.abspath(inspect.getsourcefile(lambda:0)).replace('\\','/')
#SOURCE_DIR = os.path.dirname(SOURCE_FILE)

print("testlib.std2::SOURCE_FILE: ", SOURCE_FILE)

def std2_test():
  print('std2_test')

testlib.std3.py:

# optional for 3.4.x and higher
#import os, inspect
#
#SOURCE_FILE = os.path.abspath(inspect.getsourcefile(lambda:0)).replace('\\','/')
#SOURCE_DIR = os.path.dirname(SOURCE_FILE)

print("testlib.std3::SOURCE_FILE: ", SOURCE_FILE)

def std3_test():
  print('std3_test')

输出 (3.7.4):

test::SOURCE_FILE:  <root>/test01/test.py
import : <root>/test01/testlib.py as testlib -> []
1 testlib::SOURCE_FILE:  <root>/test01/testlib.py
import : <root>/test01/std1/testlib.std1.py as testlib_std1 -> ['testlib']
import : <root>/test01/std1/../std2/testlib.std2.py as testlib_std2 -> ['testlib', 'testlib_std1']
testlib.std2::SOURCE_FILE:  <root>/test01/std1/../std2/testlib.std2.py
2 testlib::SOURCE_FILE:  <root>/test01/testlib.py
import : <root>/test01/std3/testlib.std3.py as testlib.std3 -> ['testlib']
testlib.std3::SOURCE_FILE:  <root>/test01/std3/testlib.std3.py
3 testlib::SOURCE_FILE:  <root>/test01/testlib.py
dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__annotations__', '__builtins__', '__file__', '__cached__', 'os', 'sys', 'inspect', 'copy', 'SOURCE_FILE', 'SOURCE_DIR', 'TackleGlobalImportModuleState', 'tkl_membercopy', 'tkl_merge_module', 'tkl_get_parent_imported_module_state', 'tkl_declare_global', 'tkl_import_module', 'TackleSourceModuleState', 'tkl_source_module', 'TackleLocalImportModuleState', 'testlib'])
base_test
std1_test
std2_test
std3_test
import : <root>/test01/testlib.py as . -> []
1 testlib::SOURCE_FILE:  <root>/test01/testlib.py
import : <root>/test01/std1/testlib.std1.py as testlib_std1 -> ['testlib']
import : <root>/test01/std1/../std2/testlib.std2.py as testlib_std2 -> ['testlib', 'testlib_std1']
testlib.std2::SOURCE_FILE:  <root>/test01/std1/../std2/testlib.std2.py
2 testlib::SOURCE_FILE:  <root>/test01/testlib.py
import : <root>/test01/std3/testlib.std3.py as testlib.std3 -> ['testlib']
testlib.std3::SOURCE_FILE:  <root>/test01/std3/testlib.std3.py
3 testlib::SOURCE_FILE:  <root>/test01/testlib.py
dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__annotations__', '__builtins__', '__file__', '__cached__', 'os', 'sys', 'inspect', 'copy', 'SOURCE_FILE', 'SOURCE_DIR', 'TackleGlobalImportModuleState', 'tkl_membercopy', 'tkl_merge_module', 'tkl_get_parent_imported_module_state', 'tkl_declare_global', 'tkl_import_module', 'TackleSourceModuleState', 'tkl_source_module', 'TackleLocalImportModuleState', 'testlib', 'testlib_std1', 'testlib.std3', 'base_test'])
base_test
std1_test
std2_test
std3_test

在 Python 3.7.43.2.52.7.16 中测试

优点:

可以将两个模块作为子模块导入,也可以将模块的内容导入父模块(如果没有父模块,则导入全局)。

可以导入文件名中带有句点的模块。

可以从任何扩展模块导入任何扩展模块。

可以为子模块使用独立名称,而不是默认情况下不带扩展名的文件名(例如,testlib.std.py 作为 testlib,testlib.blabla.py 作为 testlib_blabla 等等)。

不依赖于 sys.path 或任何搜索路径存储。

不需要在调用 tkl_import_module 之间保存/恢复全局变量,如 SOURCE_FILE 和 SOURCE_DIR。

[对于 3.4.x 和更高版本] 可以在嵌套的 tkl_import_module 调用中混合模块命名空间(例如:named->local->named 或 local->named->local 等)。

[对于 3.4.x 及更高版本] 可以自动将全局变量/函数/类从声明的位置导出到通过 tkl_import_module 导入的所有子模块(通过 tkl_declare_global 函数)。

缺点:

[对于 3.3.x 及更低版本] 需要在所有调用 tkl_import_module 的模块中声明 tkl_import_module(代码重复)

更新 1,2(仅适用于 3.4.x 及更高版本):

在 Python 3.4 及更高版本中,您可以通过在顶级模块中声明 tkl_import_module 来绕过在每个模块中声明 tkl_import_module 的要求,并且该函数将在一次调用中将自身注入所有子模块(这是一种自我部署导入)。

更新 3:

添加了函数 tkl_source_module 作为 bash source 的模拟,并在导入时支持执行保护(通过模块合并而不是导入实现)。

更新 4:

添加了函数 tkl_declare_global 以自动将模块全局变量导出到模块全局变量不可见的所有子模块,因为它不是子模块的一部分。

更新 5:

所有函数都移到了土库库中,请参见上面的链接。


P
Peter Mortensen

这是我仅使用 pathlib 的两个实用程序函数。它从路径推断模块名称。

默认情况下,它会递归地从文件夹中加载所有 Python 文件,并将 init.py 替换为父文件夹名称。但是您也可以提供 Path 和/或 glob 来选择某些特定文件。

from pathlib import Path
from importlib.util import spec_from_file_location, module_from_spec
from typing import Optional


def get_module_from_path(path: Path, relative_to: Optional[Path] = None):
    if not relative_to:
        relative_to = Path.cwd()

    abs_path = path.absolute()
    relative_path = abs_path.relative_to(relative_to.absolute())
    if relative_path.name == "__init__.py":
        relative_path = relative_path.parent
    module_name = ".".join(relative_path.with_suffix("").parts)
    mod = module_from_spec(spec_from_file_location(module_name, path))
    return mod


def get_modules_from_folder(folder: Optional[Path] = None, glob_str: str = "*/**/*.py"):
    if not folder:
        folder = Path(".")

    mod_list = []
    for file_path in sorted(folder.glob(glob_str)):
        mod_list.append(get_module_from_path(file_path))

    return mod_list

P
Peter Mortensen

这是一种加载文件的方法,类似于 C 等。

from importlib.machinery import SourceFileLoader
import os

def LOAD(MODULE_PATH):
    if (MODULE_PATH[0] == "/"):
        FULL_PATH = MODULE_PATH;
    else:
        DIR_PATH = os.path.dirname (os.path.realpath (__file__))
        FULL_PATH = os.path.normpath (DIR_PATH + "/" + MODULE_PATH)

    return SourceFileLoader (FULL_PATH, FULL_PATH).load_module ()

实现方式:

Y = LOAD("../Z.py")
A = LOAD("./A.py")
D = LOAD("./C/D.py")
A_ = LOAD("/IMPORTS/A.py")

Y.DEF();
A.DEF();
D.DEF();
A_.DEF();

每个文件如下所示:

def DEF():
    print("A");