【问题标题】:Import arbitrary-named file as a Python module, without generating bytecode file将任意命名的文件作为 Python 模块导入,而不生成字节码文件
【发布时间】:2011-07-25 04:49:31
【问题描述】:

Python 程序如何轻松从具有任意名称的文件中导入 Python 模块

标准库导入机制似乎没有帮助。一个重要的限制是我不希望出现随附的字节码文件;如果我在名为foo 的源文件上使用imp.load_module,则会出现一个名为fooc 的文件,这很混乱。

Python 导入机制希望它最清楚文件名是什么:模块文件位于特定的文件系统位置,特别是文件名具有特定的后缀(foo.py 用于 Python 源代码等)并且没有其他人。

这与另一个约定冲突,至少在 Unix 上:将作为命令执行的文件应该是 named without reference to the implementation language。例如,执行“foo”的命令应该在一个名为 foo 的程序文件中,没有后缀。

但是,对这样的程序文件进行单元测试需要导入该文件。我需要程序文件中的对象作为 Python 模块对象,准备好在单元测试用例中进行操作,就像 import 给我的那样。

什么是导入模块的 Pythonic 方式,尤其是从名称不以 .py 结尾的文件,没有出现该导入的字节码文件 ?

【问题讨论】:

  • 没有“.py”扩展名的命令是没有问题的,所以真的没有与约定冲突
  • gnibbler:我不明白你的评论。整个问题都基于观察到的问题。您提出的答案忽略了问题中列出的限制条件。
  • 在 unix 上,您通常只需将要运行的 Python 文件命名为“foo”而不是 "foo.py". The first line of "foo" should be something like #!/usr/bin/env python`,这样 shell 就知道使用 python 来键入“./foo”时运行脚本
  • gnibbler:是的,完全正确。我们都同意如何制作这样的文件。然后你会得到问题中描述的问题。我不明白我们为什么要在 cmets 进行这个讨论,因为这个问题清楚地表明了我们都同意的方法存在什么问题。
  • 我的方法是在测试中将 foo.py 保留在源代码控制中,并在“make install”步骤中去除 .py 扩展名。

标签: python unit-testing


【解决方案1】:
import os
import imp

py_source_open_mode = "U"
py_source_description = (".py", py_source_open_mode, imp.PY_SOURCE)

module_filepath = "foo/bar/baz"
module_name = os.path.basename(module_filepath)
with open(module_filepath, py_source_open_mode) as module_file:
    foo_module = imp.load_module(
            module_name, module_file, module_filepath, py_source_description)

【讨论】:

  • 这有一个缺点是模块被编译成一个字节码文件,该文件将出现在磁盘上(在这种情况下名为foo/bar/bazc)。这不是我们所需要的。
【解决方案2】:

到目前为止,我最好的实现是(仅使用 Python 2.6 或更高版本中的功能):

import os
import sys
import imp
import contextlib

@contextlib.contextmanager
def preserve_value(namespace, name):
    """ A context manager to preserve, then restore, the specified binding.

        :param namespace: The namespace object (e.g. a class or dict)
            containing the name binding.
        :param name: The name of the binding to be preserved.
        :yield: None.

        When the context manager is entered, the current value bound to
        `name` in `namespace` is saved. When the context manager is
        exited, the binding is re-established to the saved value.

        """
    saved_value = getattr(namespace, name)
    yield
    setattr(namespace, name, saved_value)


def make_module_from_file(module_name, module_filepath):
    """ Make a new module object from the source code in specified file.

        :param module_name: The name of the resulting module object.
        :param module_filepath: The filesystem path to open for
            reading the module's Python source.
        :return: The module object.

        The Python import mechanism is not used. No cached bytecode
        file is created, and no entry is placed in `sys.modules`.

        """
    py_source_open_mode = 'U'
    py_source_description = (".py", py_source_open_mode, imp.PY_SOURCE)

    with open(module_filepath, py_source_open_mode) as module_file:
        with preserve_value(sys, 'dont_write_bytecode'):
            sys.dont_write_bytecode = True
            module = imp.load_module(
                    module_name, module_file, module_filepath,
                    py_source_description)

    return module


def import_program_as_module(program_filepath):
    """ Import module from program file `program_filepath`.

        :param program_filepath: The full filesystem path to the program.
            This name will be used for both the source file to read, and
            the resulting module name.
        :return: The module object.

        A program file has an arbitrary name; it is not suitable to
        create a corresponding bytecode file alongside. So the creation
        of bytecode is suppressed during the import.

        The module object will also be added to `sys.modules`.

        """
    module_name = os.path.basename(program_filepath)

    module = make_module_from_file(module_name, program_filename)
    sys.modules[module_name] = module

    return module

这有点过于宽泛了:它在模块的整个导入过程中禁用字节码文件生成,这意味着在该过程中导入的其他模块也不会生成字节码文件。

我仍在寻找一种方法来为指定的模块文件禁用字节码文件生成。

【讨论】:

  • 使用标准库,Luke!
  • warvariuc:请参阅有关已尝试的标准库方法的问题,以及为什么它们不符合要求。
【解决方案3】:

我遇到了这个问题,在找不到我真正喜欢的解决方案后,通过创建一个带有 .py 扩展名的符号链接来解决它。因此,可执行脚本可能称为command,指向它的符号链接是command.py,单元测试在command_test.py 中。将文件作为模块导入时,符号链接很高兴被接受,并且可执行脚本遵循标准命名约定。

这还有一个额外的好处,就是可以轻松地将单元测试保存在与可执行文件不同的目录中。

【讨论】:

  • 谢谢。但是,许多文本编辑器会创建文件的临时副本,然后在您保存时,他们会将副本移动到原始名称上,然后删除符号链接并在其位置放置一个真实文件。所以这种方法也有一些注意事项:-(
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-05
  • 1970-01-01
  • 2019-09-24
  • 1970-01-01
相关资源
最近更新 更多