0

I'm wondering if I have some python scripts in a folder that will have common functionality like method names but different functionality can by dynamically imported. These python scripts will be in a single folder but I don't know how many their will be. I'd like to have a script look in the folder, get a list of the scripts and create objects for each of them.

user1904898
  • 59
  • 4
  • 18
  • Possible duplicate of [Building a minimal plugin architecture in Python](https://stackoverflow.com/questions/932069/building-a-minimal-plugin-architecture-in-python) –  May 04 '18 at 19:57

2 Answers2

3

What you're describing is what's usually called a plugin system.

For Python 3.4 and later, there's a standard recipe for importing a source file.

The only things left to do are (a) enumerating all the source files in a specified directory, and (b) calling some factory/register/whatever function on each module after you've loaded it.

So:

def load_module(path):
    name = os.path.split(path)[-1]
    spec = importlib.util.spec_from_file_location(name, path)
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    return module

plugins = []
for fname in os.listdir(PLUGIN_DIR):
    if not fname.startswith('.') and fname.endswith('.py'):
        try:
            module = load_module(os.path.join(PLUGIN_DIR, fname)
            plugins.append(module.plugin_factory())

The plugin_factory is just a function or class that "creates an object" for each module. You can call it whatever you want. Often, either every plugin provides a class that's a subclass of (or duck-types as) some type that you defined, or provides a function that returns an instance of some class that you defined. But in really simple cases, you can just use the module itself as the plugin object. In that case, of course, the last line just becomes plugins.append(module).

And obviously, if you want to access the plugins by name rather than just iterating over them (and they don't have some name attribute), you'll want to store them in a dict rather than a list.

And of course you can add them to sys.modules, as in the recipe in the docs, but usually you don't need that.

abarnert
  • 354,177
  • 51
  • 601
  • 671
1

Suppose we have three files in current dir.

a.py:

def f():
    pass

b.py:

def g():
    pass

c.py:

def h():
    pass

And the following main.py script which scans current directory for python files, and imports them as needed:

import os

modules = {}
for f in os.listdir('.'):
    if os.path.isfile(f) and f.endswith('.py') and f != 'main.py':
        modname = f[:-3] # remove '.py' extension
        modules[modname] = __import__(modname)

# now the various functions are available via:
modules['a'].f()
modules['b'].g()
modules['c'].h()

In Python >=3.4 you can use the importlib module, and avoid __import__, whose use is discouraged in the python docs:

import os
import importlib

modules = {}
for f in os.listdir('.'):
    if os.path.isfile(f) and f.endswith('.py') and f != 'main.py':
        modname = f[:-3] # remove '.py' extension
        spec = importlib.util.spec_from_file_location(modname, f)
        modules[modname] = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(modules[modname])

modules['a'].f()
modules['b'].g()
modules['c'].h()
fferri
  • 18,285
  • 5
  • 46
  • 95
  • You really shouldn't be using `__import__`, [as its own docs say](https://docs.python.org/3/library/functions.html#__import__). (Technically, you also shouldn't assume that `f[:-3]` matches the name of the module, but unless you've installed some funky import hooks, that's not going to bit you.) – abarnert May 04 '18 at 19:40
  • The ironic thing is that `__import__` used in this way is portable between py2 and py3, while `imp`/`importlib` breaks with the version change. – fferri May 05 '18 at 14:05