将任意命名的文件导入为Python模块,而不生成字节码文件

时间:2011-07-25 04:49:32

标签: python unit-testing

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

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

Python导入机制希望它最好地知道文件名是什么:在特定的文件系统位置找到模块文件,特别是文件名具有特定的后缀(Python源代码foo.py等)。而没有其他人。

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

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

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

3 个答案:

答案 0 :(得分:6)

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)

答案 1 :(得分:1)

到目前为止,我最好的实现是(仅在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

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

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

答案 2 :(得分:0)

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

这样做的另一个好处是可以轻松地将单元测试保存在与可执行文件不同的目录中。