【问题标题】:Load package but package with same name is already loaded加载包但是已经加载了同名的包
【发布时间】:2018-10-15 17:39:20
【问题描述】:

我有同一个 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/
      ...
    ...

我需要在&lt;HERE&gt; 中导入“旧”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,它也会在“现在”中加载一个,可能是因为名称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) 将不起作用,并给出错误“尝试了没有已知父包的相对导入”

【问题讨论】:

  • 通常不建议这样做,因为旧版本的包通常与其他包和它们正在运行的 python 版本相互依赖。我建议使用酸洗参数之类的方法,在不同的virtualenv 中启动一个新的解释器,然后在其中解开并运行
  • 在这种情况下,为什么常规导入不适合您?我认为没有任何防弹方法可以做到这一点,并保证两个版本的包之间不会有导入命名空间冲突。我不知道有任何库被设计为与同一库的其他版本共存。
  • 我不明白:您是否要同时使用两个版本的module.py?我假设 def x(x): 函数定义是它。
  • 这是一个坏主意 ™。您不应同时使用同一个库的多个版本。
  • @HåkenLid 因为如果我已经在新目录中完成了import package.subpackage2,即使我更改了sys.path,它也不会从旧目录导入package.subpackage.module。我相信它连接到包含'package'sys.modules

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


【解决方案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() 函数可能很有用,尽管您可能不需要它们。

【讨论】:

  • 谢谢,但这还不够。我编辑了这个问题,详细介绍了 importlib 会发生什么
  • @FiatLux:我已经更新了我的答案,告诉你如何做到这一点
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-02-09
  • 1970-01-01
  • 1970-01-01
  • 2021-11-17
  • 1970-01-01
  • 1970-01-01
  • 2023-04-02
相关资源
最近更新 更多