加载软件包,但已经加载了具有相同名称的软件包

时间:2018-10-15 17:39:21

标签: python python-3.x python-import binary-reproducibility

我有同一Python包的两个版本。我需要从当前版本的子包中的模块中调用旧版本(过去已复制过)的旧版本内的函数

我现在的位置:

now/
  package/
    __init__.py
    subpackage/
      __init__.py
      module.py -> "import package.subpackage.... <HERE>"
    subpackage2/
      ...
    ...

旧版本:

past/
  package/
    __init__.py
    subpackage/
      __init__.py
      module.py -> "import package.subpackage; from . import module2; .... def f(x) ..."
      module2.py
    subpackage2/
      ...
    ...

我需要在<HERE>中导入“旧” f并运行它。

理想

  • 函数f应该在旧程序包中生活,而不知道新版本的程序包
  • 新程序包中的模块应该调用它,让它活着,获取结果,然后完全忘记旧程序包的存在(因此,让f执行之后,调用“ import package.subpackage2”她的东西应该运行“新”版本)
  • 这样做不应该太复杂

基本思想是通过将我用于某些任务的代码与输出数据一起保存,然后能够运行其中的一部分来提高可重复性。

可悲的是,我知道这对于Python 3而言并不是一件简单的任务,因此我准备接受某种折衷方案。例如,我准备接受运行旧的f(x)后,“新”代码中的名称package将绑定到旧的代码。

编辑

我使用importlib进行了两种尝试。这个想法是先创建一个对象mod然后执行f = getattr(mod, "f"),但这是行不通的

  1. sys.path更改为['.../past/package/subpackage'],然后调用importlib.import_module('package.subpackage.module')。问题在于,即使更改了sys.path,它也会在“ now”中加载一个,这可能是因为package中已经有名称sys.modules
  2. spec = importlib.util.spec_from_file_location("module", "path..to..past..module.py")) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) 在这种情况下,相对导入(from . import module2.py)将不起作用,并显示错误“尝试了相对导入,没有已知的父包”

1 个答案:

答案 0 :(得分:1)

有一种方法可以很简单地工作,但是您必须对旧软件包进行一些修改。

您可以简单地在now/package/old/__init__.py中创建一个文件,其中包含:

__path__ = ['/absolute/path/to/old/package']

在新程序包中,您可以执行以下操作:

from package.old.package.subpackage.module import f as old_f

这里的问题是,旧软件包尝试使用绝对导入来导入其自己的软件包,它将改为从新软件包中加载内容。因此,当从自己的包中导入东西时,旧包将只需要使用相对的导入,否则您必须在旧包执行的所有绝对导入之前加上package.old。

如果您可以通过这种方式修改旧软件包,那应该没问题。如果该限制对您不起作用,请继续阅读。

如果确实如此,请确保出于某些原因不想修改旧软件包。然后,做一些不可思议的事情,您想用自己的版本替换builtins.__import__,该版本根据谁进行导入而返回不同的模块。您可以通过检查调用堆栈来确定谁在进行导入。

例如,这是您可能会做的(在Python 3.6上测试):

import builtins
import inspect
import package.old

old_package_path = package.old.__path__[0]

OUR_PACKAGE_NAME = 'package'
OUR_PACKAGE_NAME_WITH_DOT = OUR_PACKAGE_NAME + '.'


def import_module(name, globs=None, locs=None, fromlist=(), level=0):
    # only intercept imports for our own package from our old module
    if not name.startswith(OUR_PACKAGE_NAME_WITH_DOT) or \
            not inspect.stack()[1].filename.startswith(old_package_path):
        return real_import(name, globs, locs, fromlist, level)

    new_name = OUR_PACKAGE_NAME + '.old.' + name[len(OUR_PACKAGE_NAME_WITH_DOT):]
    mod = real_import(new_name, globs, locs, fromlist, level)
    return mod.old

# save the original __import__ since we'll need it to do the actual import
real_import = builtins.__import__
builtins.__import__ = import_module

builtins.__import__在解释器遇到的任何导入语句上被调用,并且该调用不会被缓存,因此即使每次调用使用相同的名称,您也可以返回不同的内容。


以下是我的旧答案,此处仅出于历史目的

我不太了解您要做什么,但是在Python 3中使用importlib可能可以做到这一点。

您只需创建一个模块加载器,即可从显式文件路径加载模块。

还有一个invalidate_caches()reload()函数可能有用,尽管您可能不需要它们。