Importe un archivo de nombre arbitrario como un módulo de Python, sin generar un archivo de código de bytes

¿Cómo puede un progtwig Python importar fácilmente un módulo Python desde un archivo con un nombre arbitrario ?

El mecanismo de importación de la biblioteca estándar no parece ayudar. Una restricción importante es que no quiero que aparezca un archivo de código de bytes adjunto ; Si uso imp.load_module en un archivo fuente llamado foo , aparece un archivo llamado fooc , que es confuso y confuso.

El mecanismo de importación de Python espera que sepa mejor cuál será el nombre del archivo: los archivos del módulo se encuentran en ubicaciones específicas del sistema de archivos y, en particular, que los nombres de los archivos tienen sufijos particulares ( foo.py para el código fuente de Python, etc.) y no otros.

Eso choca con otra convención, al menos en Unix: los archivos que se ejecutarán como un comando deben nombrarse sin hacer referencia al lenguaje de implementación . El comando para hacer “foo”, por ejemplo, debe estar en un archivo de progtwig llamado foo sin sufijo.

Sin embargo, las pruebas unitarias de este tipo de archivo de progtwig requieren la importación de ese archivo. Necesito los objetos del archivo de progtwig como un objeto de módulo de Python, listo para la manipulación en los casos de prueba de la unidad, exactamente como la import me daría.

¿Cuál es la forma Pythonic de importar un módulo , especialmente desde un archivo cuyo nombre no termina con .py , sin que aparezca un archivo de código de bytes para esa importación?

 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) 

Mi mejor implementación hasta ahora es (usando características solo en Python 2.6 o posterior):

 import os import sys import imp import contextlib @contextlib.contextmanager def preserve_value(namespace, name): """ A context manager to preserve, then restre, the specified binding. :param namespace: The namespace object (eg 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 

Esto es un poco demasiado amplio: deshabilita la generación de archivos de bytecode durante todo el proceso de importación del módulo, lo que significa que otros módulos importados durante ese proceso tampoco tendrán archivos de bytecode generados.

Todavía estoy buscando una manera de deshabilitar la generación de archivos de bytecode solo para el archivo de módulo especificado.

Me encontré con este problema, y ​​después de no encontrar una solución que realmente me gustara, lo resolví haciendo un enlace simbólico con una extensión .py. Por lo tanto, el script ejecutable podría llamarse command , el enlace simbólico que apunta a él es command.py , y las pruebas de unidad están en command_test.py . El enlace simbólico se acepta alegremente al importar el archivo como un módulo y la secuencia de comandos ejecutable sigue las convenciones de nomenclatura estándar.

Esto tiene la ventaja adicional de facilitar el mantenimiento de las pruebas unitarias en un directorio diferente al del ejecutable.