Learning Python - Mark Lutz [328]
The exec function (and its cousin for expressions, eval) compiles a string of code and passes it to the Python interpreter to be executed. In Python, the byte code compiler is available at runtime, so you can write programs that construct and run other programs like this. By default, exec runs the code in the current scope, but you can get more specific by passing in optional namespace dictionaries. The only real drawback to exec is that it must compile the import statement each time it runs; if it runs many times, your code may run quicker if it uses the built-in __import__ function to load from a name string instead. The effect is similar, but __import__ returns the module object, so assign it to a name here to keep it: >>> modname = "string" >>> string = __import__(modname) >>> string Transitive Module Reloads We studied module reloads in Chapter 22, as a way to pick up changes in code without stopping and restarting a program. When you reload a module, though, Python only reloads that particular module’s file; it doesn’t automatically reload modules that the file being reloaded happens to import. For example, if you reload some module A, and A imports modules B and C, the reload applies only to A, not to B and C. The statements inside A that import B and C are rerun during the reload, but they just fetch the already loaded B and C module objects (assuming they’ve been imported before). In actual code, here’s the file A.py: import B # Not reloaded when A is import C # Just an import of an already loaded module % python >>> . . . >>> from imp import reload >>> reload(A) By default, this means that you cannot depend on reloads picking up changes in all the modules in your program transitively—instead, you must use multiple reload calls to update the subcomponents independently. This can require substantial work for large systems you’re testing interactively. You can design your systems to reload their subcomponents automatically by adding reload calls in parent modules like A, but this complicates the modules’ code. A better approach is to write a general tool to do transitive reloads automatically by scanning modules’ __dict__ attributes and checking each item’s type to find nested modules to reload. Such a utility function could call itself recursively to navigate arbitrarily shaped import dependency chains. Module __dict__ attributes were introduced earlier in, the section Modules Are Objects: Metaprograms, and the type call was presented in Chapter 9; we just need to combine the two tools. For example, the module reloadall.py listed next has a reload_all function that automatically reloads a module, every module that the module imports, and so on, all the way to the bottom of each import chain. It uses a dictionary to keep track of already reloaded modules, recursion to walk the import chains, and the standard library’s types module, which simply predefines type results for built-in types. The visited dictionary technique works to avoid cycles here when imports are recursive or redundant, because module objects can be dictionary keys (as we learned in Chapter 5, a set would offer similar functionality if we use visited.add(module) to insert): """ reloadall.py: transitively reload nested modules """ import types from imp import reload # from required in 3.0 def status(module): print('reloading ' + module.__name__) def transitive_reload(module, visited): if not module in visited: # Trap cycles, duplicates status(module) # Reload this module reload(module) # And visit children visited[module] = None for attrobj in module.__dict__.values(): # For all attrs if type(attrobj) == types.ModuleType: # Recur if module transitive_reload(attrobj, visited) def reload_all(*args): visited = {} for arg in args: if type(arg) == types.ModuleType: transitive_reload(arg, visited) if __name__ == '__main__': import reloadall # Test code: reload