1

我有同一个 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”应该运行“new”版本)
  • 这样做不应该非常复杂

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

遗憾的是,我明白这对 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) 将不起作用,并给出错误“尝试了没有已知父包的相对导入”
4

1 回答 1

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__在解释器遇到的任何 import 语句上被调用,并且调用不会被缓存,因此即使它们使用相同的名称,每次调用它时也可以返回不同的内容。


以下是我的旧答案,仅用于历史目的

我不太明白你想要做什么,但这很可能在 Python 3 中通过使用importlib来完成。

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

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

于 2018-10-15T18:09:51.033 回答