Import arbitrary python source file. (Python 3.3+)
How can I import an arbitrary python source file (whose filename could contain any characters, and does not always ends with .py
) in Python 3.3+?
I used imp.load_module
as follows:
>>> import imp
>>> path = '/tmp/a-b.txt'
>>> with open(path, 'U') as f:
... mod = imp.load_module('a_b', f, path, ('.py', 'U', imp.PY_SOURCE))
...
>>> mod
<module 'a_b' from '/tmp/a-b.txt'>
It still works in Python 3.3, but according to imp.load_module
documentation, it is deprecated:
Deprecated since version 3.3: Unneeded as loaders should be used to load modules and find_module() is deprecated.
and imp
module documentation recommends to use importlib
:
Note New programs should use importlib rather than this module.
What is the proper way to load an arbitrary python source file in Python 3.3+ without using the deprecated imp.load_module
function?
Found a solution from importlib
test code.
Using importlib.machinery.SourceFileLoader:
>>> import importlib.machinery
>>> loader = importlib.machinery.SourceFileLoader('a_b', '/tmp/a-b.txt')
>>> mod = loader.load_module()
>>> mod
<module 'a_b' from '/tmp/a-b.txt'>
NOTE: only works in Python 3.3+.
UPDATE Loader.load_module
is deprecated since Python 3.4. Use Loader.exec_module
instead:
>>> import types
>>> import importlib.machinery
>>> loader = importlib.machinery.SourceFileLoader('a_b', '/tmp/a-b.txt')
>>> mod = types.ModuleType(loader.name)
>>> loader.exec_module(mod)
>>> mod
<module 'a_b'>
>>> import importlib.machinery
>>> import importlib.util
>>> loader = importlib.machinery.SourceFileLoader('a_b', '/tmp/a-b.txt')
>>> spec = importlib.util.spec_from_loader(loader.name, loader)
>>> mod = importlib.util.module_from_spec(spec)
>>> loader.exec_module(mod)
>>> mod
<module 'a_b' from '/tmp/a-b.txt'>
Shorter version of @falsetru 's solution:
>>> import importlib.util
>>> spec = importlib.util.spec_from_file_location('a_b', '/tmp/a-b.py')
>>> mod = importlib.util.module_from_spec(spec)
>>> spec.loader.exec_module(mod)
>>> mod
<module 'a_b' from '/tmp/a-b.txt'>
I tested it with Python 3.5 and 3.6.
According to the comments, it does not work with arbitrary file extensions.
Similar to @falsetru but for Python 3.5+ and accounting for what the importlib
doc states on using importlib.util.module_from_spec
over types.ModuleType
:
This function [
importlib.util.module_from_spec
] is preferred over usingtypes.ModuleType
to create a new module as spec is used to set as many import-controlled attributes on the module as possible.
We are able to import any file with importlib
alone by modifying the importlib.machinery.SOURCE_SUFFIXES
list.
import importlib
importlib.machinery.SOURCE_SUFFIXES.append('') # empty string to allow any file
spec = importlib.util.spec_from_file_location(module_name, file_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
# if desired: importlib.machinery.SOURCE_SUFFIXES.pop()
importlib
helper function
Here is a convenient, ready-to-use helper to replace imp
, with an example. The technique is the same as that of https://stackoverflow.com/a/19011259/895245 , this is just providing a more convenient function.
main.py
#!/usr/bin/env python3
import os
import importlib
def import_path(path):
module_name = os.path.basename(path).replace('-', '_')
spec = importlib.util.spec_from_loader(
module_name,
importlib.machinery.SourceFileLoader(module_name, path)
)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
sys.modules[module_name] = module
return module
notmain = import_path('not-main')
print(notmain)
print(notmain.x)
not-main
x = 1
Run:
python3 main.py
Output:
<module 'not_main' from 'not-main'>
1
I replace -
with _
because my importable Python executables without extension have hyphens as in my-cmd
. This is not mandatory, but produces better module names like my_cmd
.
This pattern is also mentioned in the docs at: https://docs.python.org/3.7/library/importlib.html#importing-a-source-file-directly
I ended up moving to it because after updating to Python 3.7, import imp
prints:
DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
and I don't know how to turn that off, this was asked at:
- The imp module is deprecated
- How to ignore deprecation warnings in Python
Tested in Python 3.7.3.