Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
Dynamically Load Modules
-
I have an app I'm working on which is supposed to load games dynamically from a sub folder. This is the basic setup.
Main Folder/ games/ game1.py ... gameN.py main.py
In
main.py
I want to load all games in the games subfolder and their respectivepyui
file. This is so I can create new games and the mainui
file can grab them. This will make extending my app with new games fairly painless.I have tried using the
__import__()
function, but I have not had much luck. Do y'all have any ideas? -
import importlib module_name = 'requests' # NOT requests.py mod = importlib.import_module(module_name)
-
You may want to look at runpy from the standard library.
-
After searching the Internet and StackOverflow I found a person with a similar problem. I looked at the answers and found this within the comments from the original poster who had found a solution on his own. It's not pretty and can do bad things if I'm not careful, but it works.
## Load Games modules path = os.path.dirname(os.path.abspath(__file__))+'/games' modules = [f.split('.')[0] for f in os.listdir(path) if f.endswith(".py")] ## I hate myself for this :( for module in modules: cmd = 'from %s import *' % module exec(cmd)
If you have any suggestions to make this better/safer, please let me know. But for now, this works as needed.
-
Shellista had a nice mechanism for dynamic imports. The key was to use
importlib.import_module
, which returns the module, which you insert into your global dict. Below are the relevant functions from the ModuleInstaller (which did a lot more, also downloading modules if needed). The key is to make sure you add the path to sys.path first, then useimport_module
and add the result to globals() so you can reference it. Ahh, I see ccc already mentioned this module.def _global_import(self, modulename): module = importlib.import_module(modulename) globals()[modulename] = module def _add_module_path(self): '''Add the installed module path to sys.path''' mod_path = self.full_install_path + os.sep if not os.path.exists(mod_path): os.makedirs(mod_path) if not mod_path in sys.path: sys.path.insert(0, self.full_install_path + os.sep) def try_import(self): self._add_module_path() self._global_import(self.module_name)
Also..are you sure you want to import modules, rather than run them? Unless the scripts and pyuis are written to allow them to be imported, I think you will have problems; for instance actions won't be assigned correctly.
-
Thanks @JonB.
I have my games written so that each
ui
has a class associated with it, which is named the same as the module so as to avoid "collisions". And each class has the actions in it. -
Sorry to say this, but my personal experiences with the import mechanisms of ShellistaExt and Shellista/dev-modular were not the best. In most cases I kept getting "package not found" type errors that were hard or impossible to fix, and the only way I could successfully use non-builtin commands was with an absolutely clean Python environment (i. e. full app restart).
Since the purpose of blmacbeth's script seems to be running other scripts as if they were the main script (rather than importing them as a library),
runpy
is probably the easiest option. Or for full control over the environment in which the scripts are run, useexec
:import os import sys with open(os.path.join(os.path.dirname(sys.argv[0]), "games", "nameofthegame.py")) as f: custom_env = {} exec(f.read(), custom_env)
This way you can even do some advanced things by manually compiling the code before
exec
ing it, but I doubt you'd need those kinds of features for this script. -
I liked @briarfox's runpy idea best. Here is a working example:
import os, runpy path = os.path.join(os.path.dirname(__file__), 'games') python_scripts = [f for f in os.listdir(path) if f.lower().endswith('.py')] for i, ps in enumerate(python_scripts): print('{:>3} - {}'.format(i, ps[:-3])) while True: selection = raw_input('Which game do you want to run: ') if selection.lower() in ('', 'q', 'quit'): # enter nothing to quit break try: runpy.run_path(os.path.join(path, python_scripts[int(selection)])) except (IndexError, ValueError): print('Please pick one of the numbers above!!')