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

6 投票
3 回答
2401 浏览
提问于 2025-04-16 22:12

如何让一个Python程序轻松地从一个任意命名的文件中导入Python模块呢?

标准的导入机制似乎帮不上忙。有一个重要的限制,就是我不想出现伴随的字节码文件; 如果我使用imp.load_module去加载一个名为foo的源文件,就会出现一个名为fooc的文件,这样会让事情变得混乱和困惑。

Python的导入机制认为它最清楚文件名应该是什么:模块文件在特定的文件系统位置找到,特别是这些文件名有特定的后缀(比如foo.py表示Python源代码,等等),而且不能有其他后缀。

这和另一个约定有冲突,至少在Unix系统上:作为命令执行的文件应该不提及实现语言的名称。比如,执行“foo”的命令应该在一个名为foo的程序文件中,没有后缀。

不过,要对这样的程序文件进行单元测试,就需要导入这个文件。我需要从程序文件中获取对象,作为一个Python模块对象,方便在单元测试中进行操作,就像import所提供的那样。

那么,如何以Pythonic的方式导入模块,特别是从一个名字不以.py结尾的文件中,而且不让导入时出现字节码文件呢?

3 个回答

0

我遇到了一个问题,找了很久也没找到一个我觉得合适的解决办法,最后我通过创建一个带有.py后缀的符号链接来解决了这个问题。也就是说,执行的脚本叫做 command,指向它的符号链接叫 command.py,而单元测试则放在 command_test.py 里。当我把这个文件作为模块导入时,符号链接也能顺利使用,而且执行脚本的命名方式也符合常规。

这样做还有一个好处,就是可以很方便地把单元测试放在一个和可执行文件不同的目录里。

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

到目前为止,我最好的实现方法是(只使用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

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

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

撰写回答