Because modules expose most of their interesting properties as built-in attributes, it’s easy to write programs that manage other programs. We usually call such manager programs metaprograms because they work on top of other systems. This is also referred to as introspection, because programs can see and process object internals. Introspection is a somewhat advanced feature, but it can be useful for building programming tools.
For instance, to get to an attribute called name in a module called M, we can use attribute qualification or index the module’s attribute dictionary, exposed in the built-in __dict__ attribute we met in Chapter 23. Python also exports the list of all loaded modules as the sys.modules dictionary and provides a built-in called getattr that lets us fetch attributes from their string names—it’s like saying object.attr, but attr is an expression that yields a string at runtime. Because of that, all the following expressions reach the same attribute and object:[48]
M.name # Qualify object by attribute M.__dict__['name'] # Index namespace dictionary manually sys.modules['M'].name # Index loaded-modules table manually getattr(M, 'name') # Call built-in fetch function
By exposing module internals like this, Python helps you build programs about programs. For example, here is a module named mydir.py that puts these ideas to work to implement a customized version of the built-in dir function. It defines and exports a function called listing, which takes a module object as an argument and prints a formatted listing of the module’s namespace sorted by name:
#!python
"""
mydir.py: a module that lists the namespaces of other modules
"""
from __future__ import print_function # 2.X compatibility
seplen = 60
sepchr = '-'
def listing(module, verbose=True):
sepline = sepchr * seplen
if verbose:
print(sepline)
print('name:', module.__name__, 'file:', module.__file__)
print(sepline)
count = 0
for attr in sorted(module.__dict__): # Scan namespace keys (or enumerate)
print('%02d) %s' % (count, attr), end = ' ')
if attr.startswith('__'):
print('<built-in name>') # Skip __file__, etc.
else:
print(getattr(module, attr)) # Same as .__dict__[attr]
count += 1
if verbose:
print(sepline)
print(module.__name__, 'has %d names' % count)
print(sepline)
if __name__ == '__main__':
import mydir
listing(mydir) # Self-test code: list myself
Notice the docstring at the top; as in the prior formats.py example, because we may want to use this as a general tool, the docstring provides functional information accessible via help and GUI/browser mode of PyDoc—a tool that uses similar introspection tools to do its job. A self-test is also provided at the bottom of this module, which narcissistically imports and lists itself. Here’s the sort of output produced in Python 3.3; this script works on 2.X too (where it may list fewer names) because it prints from the __future__:
c:\code> py −3 mydir.py
------------------------------------------------------------
name: mydir file: c:\code\mydir.py
------------------------------------------------------------
00) __builtins__ <built-in name>
01) __cached__ <built-in name>
02) __doc__ <built-in name>
03) __file__ <built-in name>
04) __initializing__ <built-in name>
05) __loader__ <built-in name>
06) __name__ <built-in name>
07) __package__ <built-in name>
08) listing <function listing at 0x000000000295B488>
09) print_function _Feature((2, 6, 0, 'alpha', 2), (3, 0, 0, 'alpha', 0), 65536)
10) sepchr -
11) seplen 60
------------------------------------------------------------
mydir has 12 names
------------------------------------------------------------
To use this as a tool for listing other modules, simply pass the modules in as objects to this file’s function. Here it is listing attributes in the tkinter GUI module in the standard library (a.k.a. Tkinter in Python 2.X); it will technically work on any object with __name__, __file__, and __dict__ attributes:
>>>import mydir>>>import tkinter>>>mydir.listing(tkinter)------------------------------------------------------------ name: tkinter file: C:\Python33\lib\tkinter\__init__.py ------------------------------------------------------------ 00) ACTIVE active 01) ALL all 02) ANCHOR anchor 03) ARC arc 04) At <function At at 0x0000000002BD41E0>...many more names omitted...156) image_types <function image_types at 0x0000000002BE2378> 157) mainloop <function mainloop at 0x0000000002BCBBF8> 158) sys <module 'sys' (built-in)> 159) wantobjects 1 160) warnings <module 'warnings' from 'C:\\Python33\\lib\\warnings.py'> ------------------------------------------------------------ tkinter has 161 names ------------------------------------------------------------
We’ll meet getattr and its relatives again later. The point to notice here is that mydir is a program that lets you browse other programs. Because Python exposes its internals, you can process objects generically.[49]
[48] As we saw briefly in “Other Ways to Access Globals” in Chapter 17, because a function can access its enclosing module by going through the sys.modules table like this, it can also be used to emulate the effect of the global statement. For instance, the effect of global X; X=0 can be simulated (albeit with much more typing!) by saying this inside a function: import sys; glob=sys.modules[__name__]; glob.X=0. Remember, each module gets a __name__ attribute for free; it’s visible as a global name inside the functions within the module. This trick provides another way to change both local and global variables of the same name inside a function.
[49] You can preload tools such as mydir.listing and the reloader we’ll meet in a moment into the interactive namespace by importing them in the file referenced by the PYTHONSTARTUP environment variable. Because code in the startup file runs in the interactive namespace (module __main__), importing common tools in the startup file can save you some typing. See Appendix A for more details.