I have a package containing subpackages only one of which I need imported during runtime - but I need to test they are valid. Here is my folder structure:
game/
__init__.py
game1/
__init__.py
constants.py
...
game2/
__init__.py
constants.py
...
For now the code that runs on boot does:
import pkgutil
import game as _game
# Detect the known games
for importer,modname,ispkg in pkgutil.iter_modules(_game.__path__):
if not ispkg: continue # game support modules are packages
# Equivalent of "from game import <modname>"
try:
module = __import__('game',globals(),locals(),[modname],-1)
except ImportError:
deprint(u'Error in game support module:', modname, traceback=True)
continue
submod = getattr(module,modname)
if not hasattr(submod,'fsName') or not hasattr(submod,'exe'): continue
_allGames[submod.fsName.lower()] = submod
but this has the disadvantage that all the subpackages are imported, importing the other modules in the subpackage (such as the constants.py etc) which amounts to some few magabytes of garbage. So I want to substitute this code with a test that the submodules are valid (they would import fine). I guess I should be using eval somehow - but how ? Or what should I do ?
EDIT: tldr;
I am looking for an equivalent to the core of the loop above:
try:
probaly_eval(game, modname) # fails iff `from game import modname` fails
# but does _not_ import the module
except: # I'd rather have a more specific error here but methinks not possible
deprint(u'Error in game support module:', modname, traceback=True)
continue
So I want a clear answer if an exact equivalent to the import statement vis a vis error checking exists - without importing the module. That's my question, a lot of answerers and commenters answered different questions.
If you want to compile the file without importing it (in current interpreter), you may use
py_compile.compile
as:Above code writes the error to
std.error
. In case you want to raise the exception, you will have to setdoraise
asTrue
(defaultFalse
). Hence, your code will be:As per the
py_compile.compile
's documents:Check to make sure the compiled module is not imported (in current interpreter):
Maybe you're looking for the
py_compile
orcompileall
modules.Here the documentation:
https://docs.python.org/2/library/py_compile.html
https://docs.python.org/2/library/compileall.html#module-compileall
You can load the one you want as a module and call it from within your program.
For example:
We already had a custom importer (disclaimer: I did not write that code I 'm just the current maintainer) whose
load_module
:So I thought I could replace the parts that access the
sys.modules
cache with overriddable methods that would in my override leave that cache alone:So:
and define:
Then I added the importer in the sys.meta_path and was good to go:
Right ? Wrong!
Huh ? I am currently importing that very same module. Well the answer is probably in import's docs
That's not completely to the point but what I guess is that the statement
from .constants import *
looks up the sys.modules to check if the parent module is there, and I see no way of bypassing that (note that our custom loader is using the builtin import mechanism for modules,mod.__loader__ = self
is set after the fact).So I updated my FakeImporter to use the sys.modules cache and then clean that up.
This however blew in a new way - or rather two ways:
a reference to the game/ package was held in
bash
top package instance in sys.modules:because
game
is imported asbash.game
. That reference held references to allgame1, game2,...
, subpackages so those were never garbage collectedbash.brec
by the samebash
module instance. This reference was imported asfrom .. import brec
in game\game1 without triggering an import, to updateSomeClass
. However, in yet another module, an import of the formfrom ...brec import SomeClass
did trigger an import and another instance of the brec module ended up in the sys.modules. That instance had a non updatedSomeClass
and blew with an AttributeError.Both were fixed by manually deleting those references - so gc collected all modules (for 5 mbytes of ram out of 75) and the
from .. import brec
did trigger an import (thisfrom ... import foo
vsfrom ...foo import bar
warrants a question).The moral of the story is that it is possible but:
If this sounds complicated and error prone it is - at least now I have a much cleaner view of interdependencies and their perils - time to address that.
This post was sponsored by Pydev's debugger - I found the
gc
module very useful in grokking what was going on - tips from here. Of course there were a lot of variables that were the debugger's and that complicated stuffYou can't really do what you want efficiently. In order to see if a package is "valid", you need to run it -- not just check if it exists -- because it could have errors or unmet dependencies.
Using the
pycompile
andcompileall
will only test if you can compile a python file, not import a module. There is a big difference between the two.import foo
could represent/foo.py
or/foo/__init__.py
./site-packages/
or python is looking in one of the many possible places for a module.Imagine this is your python file:
The above will compile, but if you import them... it will raise an ImportError if you don't have
makebelieve
installed and will raise a ValueError if you do.My suggestions are:
import the package then unload the modules. to unload them, just iterate over stuff in
sys.modules.keys()
. if you're worried about external modules that are loaded, you could overrideimport
to log what your packages load. An example of this is in a terrible profiling package i wrote: https://github.com/jvanasco/import_logger [I forgot where I got the idea to override import from. Maybecelery
?] As some noted, unloading modules is entirely dependent on the interpreter -- but pretty much every option you have has many drawbacks.Use subprocesses to spin up a new interpreter via
popen
. iepopen('python', '-m', 'module_name')
. This would have a lot of overhead if you do this to every needed module (an overhead of each interpreter and import), but you could write a ".py" file that imports everything you need and just try to run that. In either case, you would have to analyze the output -- as importing a "valid" package could cause acceptable errors during execution. i can't recall if the subprocess inherits your environment vars or not , but I believe it does. The subprocess is an entirely new operating system process/interpreter, so the modules will be loaded into that short-lived processes' memory.clarified answer.I believe
imp.find_module
satisfies at least some of your requirements: https://docs.python.org/2/library/imp.html#imp.find_moduleA quick test shows that it does not trigger an import:
Here's an example usage in some of my code (which attempts to classify modules): https://github.com/asottile/aspy.refactor_imports/blob/2b9bf8bd2cf22ef114bcc2eb3e157b99825204e0/aspy/refactor_imports/classify.py#L38-L44