Import a python module without the .py extension
Asked Answered
B

7

86

I have a file called foobar (without .py extension). In the same directory I have another python file that tries to import it:

import foobar

But this only works if I rename the file to foobar.py. Is it possible to import a python module that doesn't have the .py extension?

Update: the file has no extension because I also use it as a standalone script, and I don't want to type the .py extension to run it.

Update2: I will go for the symlink solution mentioned below.

Brawn answered 8/4, 2010 at 15:11 Comment(5)
I'm intrigued. Why do you have a python file without the py extension?Actinal
Sometimes it's nice to use python for configuration files (extension as .conf) or to denote a special type of file. In my case, it'd be more of a convenience for an Administrator.Oro
I have a file with configuration that is used both as a python file and as a bash script. I gave it a pysh extension...Tiler
If that is configuration related things, I recommend using ConfigParser. wiki.python.org/moin/ConfigParserExamplesMidgut
@voyager One reason is python scripts with .cgi extensions instead of .py extensionLevan
S
65

You can use the imp.load_source function (from the imp module), to load a module dynamically from a given file-system path.

import imp
foobar = imp.load_source('foobar', '/path/to/foobar')

This SO discussion also shows some interesting options.

Swanky answered 8/4, 2010 at 15:15 Comment(6)
Fixed. [it is more constructive to suggest an Edit, though]Swanky
but again section is required as I understood from >>> foobar = imp.load_source('','credentials') [default] NameError: name 'default' is not definedCharkha
What is the use of the first argument 'foobar' if it is assigned in the return value?Ventriloquize
@AnmolSinghJaggi It sets the module __name__ property; normally that's determined from the filename, but since you're using a non-standard filename (which might not even contain any valid python identifier at all), you have to specify the module name. The variable name in which you store a reference to the created module object is irrelevant, much as if you import foo.bar as baz the module referenced by the variable baz will still have its original __name__.Haemocyte
This has been deprecated since 3.4. Any idea how to import from a file without the .py extension in 3.4+?Bronk
For Python 3.4+: this answer is more exact: stackoverflow.com/questions/2601047/…Makings
E
37

Here is a solution for Python 3.4+:

from importlib.util import spec_from_loader, module_from_spec
from importlib.machinery import SourceFileLoader 

spec = spec_from_loader("foobar", SourceFileLoader("foobar", "/path/to/foobar"))
foobar = module_from_spec(spec)
spec.loader.exec_module(foobar)

Using spec_from_loader and explicitly specifying a SourceFileLoader will force the machinery to load the file as source, without trying to figure out the type of the file from the extension. This means that you can load the file even though it is not listed in importlib.machinery.SOURCE_SUFFIXES.

If you want to keep importing the file by name after the first load, add the module to sys.modules:

sys.modules['foobar'] = foobar

You can find an implementation of this function in a utility library I maintain called haggis. haggis.load.load_module has options for adding the module to sys.modules, setting a custom name, and injecting variables into the namespace for the code to use.

Excitation answered 25/4, 2017 at 5:52 Comment(6)
That module needs a foobar = importlib.nice_import('foobar') helper desperately.Enright
@Ciro. It already has that. I don't think that /some/arbitrary/file.weird_extension qualifies as "nice". That being said, I've started using python code for all my configuration files once I discovered this. It's just so convenient.Excitation
What if I want to import * ?Davon
@AlexHarvey. This gives you a module object. You can do something like globals().update(foobar.__dict__) or so, but I would recommend against it.Excitation
I'm trying to do this as a single executable and import it just for testing. A bit disappointed at the answers, too much speculation, too few concrete answers... is there a 'nice_import' ? Name it!Teletype
@OriginalHacker. I'm not sure where you see speculation. This is how you use existing import machinery to load an arbitrary file as though it were a python file. Not sure what you mean by "nice_import". That being said, if you're not happy with the solution being three lines, you can check out haggis.load.load_moduleExcitation
T
18

Like others have mentioned, you could use imp.load_source, but it will make your code more difficult to read. I would really only recommend it if you need to import modules whose names or paths aren't known until run-time.

What is your reason for not wanting to use the .py extension? The most common case for not wanting to use the .py extension, is because the python script is also run as an executable, but you still want other modules to be able to import it. If this is the case, it might be beneficial to move functionality into a .py file with a similar name, and then use foobar as a wrapper.

Tritheism answered 8/4, 2010 at 16:40 Comment(5)
Or instead of wrapping, just symlink foobar.py to foobar (assuming you aren't on Windows)Strontianite
@whaley, yeah, that would be much cleaner. You could use a .bat for windows to accomplish the same thing.Tritheism
I've got a neat use case - a readme file, with examples in it, which I'd like doctest to validate. I'm hoping to make a doctest markdown doc that works...Artificer
And the answer is (for that use case) - use doctest.loadfile!Artificer
The issue with wrappers is that if someone naively copies just the wrapper to a bin/ directory, the program won't work when run from the path.Koser
B
14

imp.load_source(module_name, path) should do or you can do the more verbose imp.load_module(module_name, file_handle, ...) route if you have a file handle instead

Bloodsucker answered 8/4, 2010 at 15:18 Comment(0)
E
8

importlib helper function

Here is a convenient, ready-to-use helper to replace imp, with an example, based on what was mentioned at: https://mcmap.net/q/20024/-import-a-python-module-without-the-py-extension

main.py

#!/usr/bin/env python3

import os
import importlib
import sys

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. This is not mandatory, but produces better module names.

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:

Tested in Python 3.7.3.

Enright answered 11/5, 2019 at 13:23 Comment(0)
O
2

If you install the script with package manager (deb or alike) another option would be to use setuptools:

"...there’s no easy way to have a script’s filename match local conventions on both Windows and POSIX platforms. For another, you often have to create a separate file just for the “main” script, when your actual “main” is a function in a module somewhere... setuptools fixes all of these problems by automatically generating scripts for you with the correct extension, and on Windows it will even create an .exe file..."

https://pythonhosted.org/setuptools/setuptools.html#automatic-script-creation

Olin answered 7/7, 2014 at 5:33 Comment(0)
A
0

import imp has been deprecated.

The following is clean and minimal for me:

import sys
import types
import  pathlib

def importFileAs(
        modAsName: str,
        importedFilePath: typing.Union[str,  pathlib.Path],
) -> types.ModuleType:
    """ Import importedFilePath as modAsName, return imported module
by loading importedFilePath and registering modAsName in sys.modules.
importedFilePath can be any file and does not have to be a .py file. modAsName should be python valid.
Raises ImportError: If the file cannot be imported or any Exception: occuring during loading.

Refs:
Similar to: https://mcmap.net/q/14763/-import-arbitrary-python-source-file-python-3-3
    but allows for other than .py files as well through importlib.machinery.SourceFileLoader.
    """
    import importlib.util
    import importlib.machinery

    # from_loader does not enforce .py but  importlib.util.spec_from_file_location() does.
    spec = importlib.util.spec_from_loader(
        modAsName,
        importlib.machinery.SourceFileLoader(modAsName, importedFilePath),
    )
    if spec is None:
        raise ImportError(f"Could not load spec for module '{modAsName}' at: {importedFilePath}")
    module = importlib.util.module_from_spec(spec)

    try:
        spec.loader.exec_module(module)
    except FileNotFoundError as e:
        raise ImportError(f"{e.strerror}: {importedFilePath}") from e

    sys.modules[modAsName] = module
    return module

And then I would use it as so:

aasMarmeeManage = importFileAs('aasMarmeeManage', '/bisos/bpip/bin/aasMarmeeManage.cs')
def g_extraParams(): aasMarmeeManage.g_extraParams()
Affine answered 2/10, 2022 at 18:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.