【问题标题】:python importlib can't find a module inside a daemon contextpython importlib 在守护进程上下文中找不到模块
【发布时间】:2015-04-12 22:06:39
【问题描述】:

我有一个基于配置动态导入模块的脚本。我正在尝试在脚本上实现一个守护进程上下文(使用python-daemon 模块),它似乎干扰了python 查找相关模块的能力。

setup() 中的mymodule/__init__.py 我这样做:

load_modules(args, config, logger)

try:
    with daemon.DaemonContext(
            files_preserve = getLogfileHandlers(logger)
            ):
        main_loop(config)

我在mymodule/__main__.py 内接到了setup() 的电话,我正在以这种方式加载整个内容: PYTHONPATH=. python -m mymodule

这很好用,但是在 load_modules() 中设置的侦听端口被新添加的守护程序上下文关闭,所以我想像这样在守护程序上下文中移动该函数调用:

try:
    with daemon.DaemonContext(
            files_preserve = getLogfileHandlers(logger)
            ):
        load_modules(args, config, logger)
        main_loop(config)

模块以这种方式在load_modules() 中加载:

for mysubmodule in modules:
    try:        
        i = importlib.import_module("mymodule.{}".format(mysubmodule))
    except ImportError as err:
        logger.error("import of mymodule.{} failed: {}".format(
            mysubmodule, err))

在守护进程上下文之外使用load_modules() 可以正常工作。当我将它移动到守护进程上下文中时,它似乎无法找到它正在寻找的模块。我明白了:

import of mymodule.submodule failed: No module named submodule

这看起来像是某种命名空间问题——我注意到异常只涉及我尝试导入的模块名称的子模块部分——但我已经比较了我在守护进程上下文内外所能想到的所有内容,我找不到重要的区别。 sys.path 没有改变,守护进程上下文没有清除环境或 chroot。 cwd 当然会更改为/,但这不应该对python 查找模块的能力产生任何影响,因为. 的绝对路径出现在sys.path 中。

我在这里错过了什么?

编辑:我添加了一个SSCCE 以使情况更清楚。以下三个文件创建了一个名为“mymodule”的模块,可以从命令行以PYTHONPATH=. python -m mymodule 运行。在__init__.py 中有两个对load_module() 的调用,一个被注释掉了。您可以通过交换评论来证明问题。

mymodule/__main__.py

from mymodule import setup
import sys

if __name__ == "__main__":
    sys.exit(setup())

mymodule/__init__.py

import daemon
import importlib
import logging

def main_loop():
    logger = logging.getLogger('loop')
    logger.debug("Code runs here.")

def load_module():
    logger = logging.getLogger('load_module')
    submodule = 'foo'
    try:
        i = importlib.import_module("mymodule.{}".format(submodule))
    except ImportError as e:
        logger.error("import of mymodule.{} failed: {}".format(
            submodule, e))

def setup_logging():
    logfile = 'mymodule.log'
    fh = logging.FileHandler(logfile)
    root_logger = logging.getLogger()
    root_logger.addHandler(fh)
    root_logger.setLevel(logging.DEBUG)

def get_logfile_handlers(logger):
    handlers = []
    for handler in logger.handlers:
        handlers.append(handler.stream.fileno())
    return handlers

def setup():
    setup_logging()
    logger = logging.getLogger()

    # load_module()
    with daemon.DaemonContext(
            files_preserve = get_logfile_handlers(logger)
            ):
        load_module()
        main_loop()

mymodule/foo.py

import logging

logger=logging.getLogger('foo')
logger.debug("Inside foo.py")

【问题讨论】:

    标签: python python-2.7 python-daemon python-importlib


    【解决方案1】:

    当我在自己的项目中实现这一目标时,我花了 4 个小时来尝试解决这个问题。线索在这里:

    如果要导入的模块应该包含在包中,则传递给find_module() 的第二个参数,即父包上的__path__,将用作路径的来源。

    (来自https://docs.python.org/2/reference/simple_stmts.html#import

    成功导入mymodule后,python2不再使用sys.path搜索子模块,而是使用sys.modules["mymodule"].__path__。当您导入 mymodule 时,python2 将其 __path__ 设置为它存储在的 relative 目录:

    mymodule.__path__ = ['mymodule']
    

    守护进程后,python 的 CWD 设置为 /mysubmodule 的导入内部搜索放在 /mymodule 中。

    我通过使用 os.chdir() 在守护进程后将 CWD 更改回旧目录来解决此问题:

    oldcwd = os.getcwd()
    with DaemonizeContext():
        os.chdir(oldcwd)
        # ... daemon things
    

    【讨论】:

      【解决方案2】:

      这工作正常,但是在 load_modules() 中设置的监听端口被新添加的守护进程上下文关闭,所以

      没有。 load_modules() 应该加载模块。它不应该打开端口。

      如果您需要保留在上下文之外打开的文件或套接字,请将其传递给files_preserve。如果可能的话,最好在上下文中简单地打开文件等,就像我上面建议的那样。

      【讨论】:

      • 这些模块都做不同的事情。有些碰巧打开文件或打开监听端口。我已经检查过了,DaemonContext 没有把我放在 chroot 中。它默认情况下不会这样做,我在问题中指出了没有发生的问题。我在打开上下文之前和之后尽可能多地比较了环境,我能发现的唯一区别是 CWD 更改为 /.运行 sscce 即可查看。
      • “这些模块都做不同的事情。有些碰巧打开文件或打开监听端口。” -- 如果他们这样做是作为导入时的副作用,那么他们的设计很糟糕,你应该修复它们。编写一个可以单独调用的函数,以便在需要时打开端口/文件。
      • 另外,如果不是让你进入 chroot,files_preserve 参数到底在做什么?
      • 这就是我正在做的。我只是没有在 SSCCE 中证明这一点,因为它与手头的问题无关,即加载模块在守护进程上下文中中断。
      • 也许您应该阅读有关守护程序模块的文档。守护进程的标准过程会关闭所有打开的文件句柄。 files_preserve 用于防止某些文件句柄..在这种情况下,打开日志文件。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-12-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多