Guido van Rossum | ebbc01e | 2007-02-25 05:08:26 +0000 | [diff] [blame^] | 1 | """Alternative to reload(). |
| 2 | |
| 3 | This works by executing the module in a scratch namespace, and then |
| 4 | patching classes, methods and functions. This avoids the need to |
| 5 | patch instances. New objects are copied into the target namespace. |
| 6 | """ |
| 7 | |
| 8 | import imp |
| 9 | import sys |
| 10 | import types |
| 11 | |
| 12 | |
| 13 | def xreload(mod): |
| 14 | """Reload a module in place, updating classes, methods and functions. |
| 15 | |
| 16 | Args: |
| 17 | mod: a module object |
| 18 | |
| 19 | Returns: |
| 20 | The (updated) input object itself. |
| 21 | """ |
| 22 | # Get the module name, e.g. 'foo.bar.whatever' |
| 23 | modname = mod.__name__ |
| 24 | # Get the module namespace (dict) early; this is part of the type check |
| 25 | modns = mod.__dict__ |
| 26 | # Parse it into package name and module name, e.g. 'foo.bar' and 'whatever' |
| 27 | i = modname.rfind(".") |
| 28 | if i >= 0: |
| 29 | pkgname, modname = modname[:i], modname[i+1:] |
| 30 | else: |
| 31 | pkgname = None |
| 32 | # Compute the search path |
| 33 | if pkgname: |
| 34 | # We're not reloading the package, only the module in it |
| 35 | pkg = sys.modules[pkgname] |
| 36 | path = pkg.__path__ # Search inside the package |
| 37 | else: |
| 38 | # Search the top-level module path |
| 39 | pkg = None |
| 40 | path = None # Make find_module() uses the default search path |
| 41 | # Find the module; may raise ImportError |
| 42 | (stream, filename, (suffix, mode, kind)) = imp.find_module(modname, path) |
| 43 | # Turn it into a code object |
| 44 | try: |
| 45 | # Is it Python source code or byte code read from a file? |
| 46 | # XXX Could handle frozen modules, zip-import modules |
| 47 | if kind not in (imp.PY_COMPILED, imp.PY_SOURCE): |
| 48 | # Fall back to built-in reload() |
| 49 | return reload(mod) |
| 50 | if kind == imp.PY_SOURCE: |
| 51 | source = stream.read() |
| 52 | code = compile(source, filename, "exec") |
| 53 | else: |
| 54 | code = marshal.load(stream) |
| 55 | finally: |
| 56 | if stream: |
| 57 | stream.close() |
| 58 | # Execute the code im a temporary namespace; if this fails, no changes |
| 59 | tmpns = {} |
| 60 | exec(code, tmpns) |
| 61 | # Now we get to the hard part |
| 62 | oldnames = set(modns) |
| 63 | newnames = set(tmpns) |
| 64 | # Add newly introduced names |
| 65 | for name in newnames - oldnames: |
| 66 | modns[name] = tmpns[name] |
| 67 | # Delete names that are no longer current |
| 68 | for name in oldnames - newnames - {"__name__"}: |
| 69 | del modns[name] |
| 70 | # Now update the rest in place |
| 71 | for name in oldnames & newnames: |
| 72 | modns[name] = _update(modns[name], tmpns[name]) |
| 73 | # Done! |
| 74 | return mod |
| 75 | |
| 76 | |
| 77 | def _update(oldobj, newobj): |
| 78 | """Update oldobj, if possible in place, with newobj. |
| 79 | |
| 80 | If oldobj is immutable, this simply returns newobj. |
| 81 | |
| 82 | Args: |
| 83 | oldobj: the object to be updated |
| 84 | newobj: the object used as the source for the update |
| 85 | |
| 86 | Returns: |
| 87 | either oldobj, updated in place, or newobj. |
| 88 | """ |
| 89 | if type(oldobj) is not type(newobj): |
| 90 | # Cop-out: if the type changed, give up |
| 91 | return newobj |
| 92 | if hasattr(newobj, "__reload_update__"): |
| 93 | # Provide a hook for updating |
| 94 | return newobj.__reload_update__(oldobj) |
| 95 | if isinstance(newobj, types.ClassType): |
| 96 | return _update_class(oldobj, newobj) |
| 97 | if isinstance(newobj, types.FunctionType): |
| 98 | return _update_function(oldobj, newobj) |
| 99 | if isinstance(newobj, types.MethodType): |
| 100 | return _update_method(oldobj, newobj) |
| 101 | # XXX Support class methods, static methods, other decorators |
| 102 | # Not something we recognize, just give up |
| 103 | return newobj |
| 104 | |
| 105 | |
| 106 | def _update_function(oldfunc, newfunc): |
| 107 | """Update a function object.""" |
| 108 | oldfunc.__doc__ = newfunc.__doc__ |
| 109 | oldfunc.__dict__.update(newfunc.__dict__) |
| 110 | oldfunc.func_code = newfunc.func_code |
| 111 | oldfunc.func_defaults = newfunc.func_defaults |
| 112 | # XXX What else? |
| 113 | return oldfunc |
| 114 | |
| 115 | |
| 116 | def _update_method(oldmeth, newmeth): |
| 117 | """Update a method object.""" |
| 118 | # XXX What if im_func is not a function? |
| 119 | _update_function(oldmeth.im_func, newmeth.im_func) |
| 120 | return oldmeth |
| 121 | |
| 122 | |
| 123 | def _update_class(oldclass, newclass): |
| 124 | """Update a class object.""" |
| 125 | # XXX What about __slots__? |
| 126 | olddict = oldclass.__dict__ |
| 127 | newdict = newclass.__dict__ |
| 128 | oldnames = set(olddict) |
| 129 | newnames = set(newdict) |
| 130 | for name in newnames - oldnames: |
| 131 | setattr(oldclass, name, newdict[name]) |
| 132 | for name in oldnames - newnames: |
| 133 | delattr(oldclass, name) |
| 134 | for name in oldnames & newnames - {"__dict__", "__doc__"}: |
| 135 | setattr(oldclass, name, newdict[name]) |
| 136 | return oldclass |