在Python 3中动态导入模块时出现问题

时间:2019-01-09 19:13:15

标签: python python-3.x python-import importerror python-importlib

我遇到这样的情况,在我的Python 3项目中,必须在运行时包含某些模块。我为此使用importlib.import_module


第二次更新:我确实找到了一种方法来完成我想要的事情。一些其他代码可能会使此处的某些链接略微有些偏离。我将发布答案以显示我的工作。



首次更新:通过各种Python组,有人告诉我这是不可能的。我在底部有一个更新部分。


设置

这个想法是,当用户运行我的脚本时,将搜索所有名为“ agents _ *。py”(其中*替换为某个单词的文件)的类,并将其导入。因此,例如,名为agents_human.py的文件将具有名为HumanAgent的类。正是将要实例化的HumanAgent类,这就是为什么我需要导入模块(文件)以便发生这种情况的原因。

基本问题

问题有两个:

  1. 我似乎无法让该模块在多个目录中查找。
  2. 安装我的程序后,导入模块根本无法工作。

问题上下文

我有一个代码库,显示了我要执行的操作:https://github.com/jeffnyman/pacumen

问题出在load_agent()方法中;特别是this statement

为此,如果您克隆该仓库,则可以从项目根目录运行以下命令:

python3 -m pacumen
  • 如果agents_human.py文件位于项目的根目录中,那么将起作用
  • 如果agents_human.py文件是项目中的agents目录,它将 起作用。

在我的代码存储库中,agents_human.py文件位于agents目录中,因此导入失败。但是,如果将该文件移至根目录,则可以看到导入成功。但是,从load_agent()函数中可以看出,我已经将当前工作目录以及“ agents”目录添加到路径(lines 119 - 121

当我说导入失败时,我的意思是:当调用导入逻辑时,将触发the ImportError exception。同样,仅当agents_human.py文件位于agents目录中时。

要解决的问题

理想情况下,我希望这对两种方法都适用:文件位于根目录中以及agents目录中。

其他问题背景

我只是不得不将文件放在根目录中。但是,更糟糕的是,在安装程序时(而不是从项目根目录运行),即使是上面可用的部分也不再起作用。

具体来说,我从克隆项目的项目根目录中执行以下操作:

pip3 install .

然后我创建一些目录(例如test_pacumen)。在其中放置了一个agents_human.py文件,唯一的内容是这样:

class HumanAgent:
  pass

(还需要我项目中的layouts目录。)

然后我跑:

pacumen

在这种情况下,逻辑将永远不会导入agents_human.py文件……即使它位于执行命令的根目录下。

我尝试了什么

通过查看各种示例,我的理解是我必须通过相对名称导入或提供显式锚。但这似乎是通过提供在其中搜索模块的路径进行的。我上面链接的代码似乎就是这样做的。

我获得了在路径上找到的模块名称(通过this line),并且在所有情况下,它确实找到了我放置的相关文件。因此,似乎该路径正在起作用;不是导入,而是导入。那是我找不到很多指导的地方。

我还尝试过以这种方式获取每个文件路径的路径的结尾部分:

module_end_dir = os.path.basename(os.path.normpath(module_dir))

然后,在适当的位置,我已经尝试过:

agent_module = importlib.import_module(f"{module_end_dir}.{module_name[:-3]}")

agents_human.py位于agents目录中时,该方法在本地有效,但是如果agents_human.py位于根目录中,则该方法无效。在安装该程序后,它仍然根本无法工作。

问题摘要

  1. 在第一个问题上下文(未安装的上下文)中,我不知道为什么无法从agents_human.py导入agents模块,但是可以从根。

  2. 在第二个上下文(已安装)中,即使agents_human.py模块似乎在根目录中也无法导入。

我希望我在这里提供的内容不会太混乱。

更新:

我想到了为load_agent函数尝试类似的方法:

def load_agent(pacman, not_human):
  module = importlib.import_module("agents.agents_human")
  return getattr(module, pacman)

在通过项目根目录运行程序时有效,但是在通过pip安装项目时无效。显然,这与脚本的入口点有关,以及在这种情况下如何无法使Python识别目录为模块。

我提供了另一个示例,如下所示:

def load_agent(pacman, not_human):
  import re
  pysearchre = re.compile('.py$', re.IGNORECASE)
  agent_files = filter(pysearchre.search, os.listdir(os.path.join(os.getcwd(), 'agents')))
  form_module = lambda fp: '.' + os.path.splitext(fp)[0]
  agents = map(form_module, agent_files)

  importlib.import_module('agents')

  for agent in agents:
      if not agent.startswith('__'):
          agent_module = importlib.import_module(agent, package="agents")

  if pacman in dir(agent_module):
      return getattr(agent_module, pacman)

  raise Exception("The agent " + pacman + " is not specified in any agents_*.py file.")

相同的情况。无需安装即可在程序运行时使用。安装程序时不会。

基本上,这是为了确认通过pip安装Python程序时我想做的事是不可能的。我不完全相信,但是我提供了此更新,以防其他人遇到。

2 个答案:

答案 0 :(得分:1)

您所做的操作不起作用,因为您在setup.py中有入口点,而且还因为安装程序时Python没有agents目录的概念。

您可以尝试将代理路径添加到sys.path:

sys.path.insert(0, os.getcwd() + '/agents')

但是我怀疑当有人安装您的程序时这是否有用。 (而且,当您从发现的项目中运行程序时,您不需要这样做。)直接从项目本身运行时,通过pip进行安装基本上会将执行脚本与目录上下文分离。

您可能要尝试的另一件事就是通过文件位置导入。换句话说,请勿将agents目录(来自安装)视为实际模块。相反,只需将其视为是:目录位置。问题在于这是Python设法在版本之间搞砸的另一个领域。

如果您使用的是3.5及更高版本,则可以执行以下操作:

import importlib.util
spec = importlib.util.spec_from_file_location("agents.agent_human", "/agents/agents_human.py")
foo = importlib.util.module_from_spec(spec)
spec.loader.exec_module(foo)

如果您使用的是Python 3.3或3.4,则可以执行以下操作:

from importlib.machinery import SourceFileLoader
foo = SourceFileLoader("agents.agent_human", "/agents/agents_human.py").load_module()

但是请注意,即使在Python 3.4中,它也已被弃用。

您可能想尝试一些变化。假设您想尝试查找该目录中所有文件中的类,则可能必须添加一些逻辑以检查agents目录中的任何文件。

答案 1 :(得分:0)

我找到了一个基本可行的解决方案,但是我不得不稍微修改一下要求。最初,我希望这种情况是有人可以安装我的pacumen项目并在项目根目录或项目根目录内的agents目录中找到某些代理文件。

一个问题是,尽管这将适用于项目的本地副本,但是如果通过pip安装了项目,则无法导入agents目录,因为该目录未被识别。 / p>

我最后做的是接受我在项目根目录中仅包含模式为agents_*.py的文件。我实际上可以通过这样在load_agents()方法中增加一行来实现这一目的:

sys.path.insert(0, os.getcwd())

这里是method with the line in question。再加上我已有的代码,就可以很好地工作。