Thinking about pyinstaller and python 3 it occurred to me that it should be
possible to get a complete list of what a script imports in a somewhat
simpler way than pyinstaller presently does it.
Below find a script that first imports a target script and then executes
it. It copies sys.modules at each point, and in this way it finds out what
has been imported.
This script is incomplete. I would expect in finished form it would take
the target script name as a command argument, along with some options for
example "--no-exec" if it is not necessary to execute the script. (But I
was surprised to find that a PyQt5 app added quite a few modules during
execution that were not loaded during its import.)
The output at present is just a list of modules required at each phase of
the run. However, it would be trivial to output a pyinstaller TOC object in
source form, or a set of TOCs by filtering the data for binaries, etc.
Here's the script, try it out (you have to set script_name= ).
#================================
import sys #everybody imports sys
# Define the target script, this would be given as a command argument
script_name = 'my_script' # some moderately complex GUI app here
# Record the modules needed to get this script going to this point.
module_list_base = sys.modules.copy()
# Import the target script and record what it imported.
script_module = __import__(script_name)
module_list_static_import = sys.modules.copy()
# Conditionally, execute the target script to capture dynamic imports.
# e.g., if a --no-exec option is not given
# Import what we need, we take these out of the inventory later.
import imp
import importlib
import marshal
# Create a code object by un-marshalling the cached .pyc if any
cache_code = None
if hasattr(script_module,'__cached__') and script_module.__cached__ :
bytecode_file = open(script_module.__cached__,'rb')
magic = bytecode_file.read(4)
if magic == imp.get_magic() :
timestamp = bytecode_file.read(4)
padding = bytecode_file.read(4)
bytecode_blob = bytecode_file.read()
cache_code = marshal.loads(bytecode_blob)
bytecode_file.close()
if cache_code is None :
# No .pyc, create code object via compiling source
script_file = script_module.__file__
source_file = open(script_file,'r',encoding='UTF-8')
source_blob = source_file.read()
source_file.close()
cache_code = compile(source_blob,'<string>','exec')
# Now get rid of the sys.modules entries for things we imported
# to do that compile. If the executed app uses them, they will
# be reloaded and go back into the list. "marshal" is built-in.
del sys.modules['imp']
del sys.modules['importlib']
del sys.modules['importlib._bootstrap']
del sys.modules['importlib.machinery']
# Actually execute the code
exec(cache_code)
# Snapshot the total of imports after execution
module_list_dynamic_import = sys.modules.copy()
def dict_diff(da, db):
'''Return a dict containing only
members of da that are not in db: da-db'''
ka = set(da.keys())
kb = set(db.keys())
kd = ka - kb
return { k:da[k] for k in kd }
def print_module_dict(da):
'''Print the names and filepaths of modules in e.g. sys.modules'''
for k in sorted(da.keys()):
f = '(builtin)'
if hasattr(da[k], '__file__') :
f = da[k].__file__
print( k, f)
print('\n==== Essential base modules =====\n')
print_module_dict(module_list_base)
print('\n==== Static imports by script',script_name,'====\n')
print_module_dict( dict_diff(module_list_static_import,module_list_base) )
print('\n==== Modules added by executing script',script_name,'====\n')
print_module_dict( dict_diff(module_list_dynamic_import,
module_list_static_import) )
#================================
--
You received this message because you are subscribed to the Google Groups
"PyInstaller" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/pyinstaller.
For more options, visit https://groups.google.com/d/optout.