diff options
author | Guido van Rossum <guido@python.org> | 2007-02-25 05:08:26 (GMT) |
---|---|---|
committer | Guido van Rossum <guido@python.org> | 2007-02-25 05:08:26 (GMT) |
commit | ebbc01ef505d4c2f694fb72c7880b8bad0102e4a (patch) | |
tree | 8f207f8a1bc4161c7c913c67128b71071c0aa4d4 | |
parent | ee91be45df796b8e5721d9142a6e92e55a465451 (diff) | |
download | cpython-ebbc01ef505d4c2f694fb72c7880b8bad0102e4a.zip cpython-ebbc01ef505d4c2f694fb72c7880b8bad0102e4a.tar.gz cpython-ebbc01ef505d4c2f694fb72c7880b8bad0102e4a.tar.bz2 |
First draft of a different solution to the reload() problem.
-rw-r--r-- | Lib/xreload.py | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/Lib/xreload.py b/Lib/xreload.py new file mode 100644 index 0000000..ba5370e --- /dev/null +++ b/Lib/xreload.py @@ -0,0 +1,136 @@ +"""Alternative to reload(). + +This works by executing the module in a scratch namespace, and then +patching classes, methods and functions. This avoids the need to +patch instances. New objects are copied into the target namespace. +""" + +import imp +import sys +import types + + +def xreload(mod): + """Reload a module in place, updating classes, methods and functions. + + Args: + mod: a module object + + Returns: + The (updated) input object itself. + """ + # Get the module name, e.g. 'foo.bar.whatever' + modname = mod.__name__ + # Get the module namespace (dict) early; this is part of the type check + modns = mod.__dict__ + # Parse it into package name and module name, e.g. 'foo.bar' and 'whatever' + i = modname.rfind(".") + if i >= 0: + pkgname, modname = modname[:i], modname[i+1:] + else: + pkgname = None + # Compute the search path + if pkgname: + # We're not reloading the package, only the module in it + pkg = sys.modules[pkgname] + path = pkg.__path__ # Search inside the package + else: + # Search the top-level module path + pkg = None + path = None # Make find_module() uses the default search path + # Find the module; may raise ImportError + (stream, filename, (suffix, mode, kind)) = imp.find_module(modname, path) + # Turn it into a code object + try: + # Is it Python source code or byte code read from a file? + # XXX Could handle frozen modules, zip-import modules + if kind not in (imp.PY_COMPILED, imp.PY_SOURCE): + # Fall back to built-in reload() + return reload(mod) + if kind == imp.PY_SOURCE: + source = stream.read() + code = compile(source, filename, "exec") + else: + code = marshal.load(stream) + finally: + if stream: + stream.close() + # Execute the code im a temporary namespace; if this fails, no changes + tmpns = {} + exec(code, tmpns) + # Now we get to the hard part + oldnames = set(modns) + newnames = set(tmpns) + # Add newly introduced names + for name in newnames - oldnames: + modns[name] = tmpns[name] + # Delete names that are no longer current + for name in oldnames - newnames - {"__name__"}: + del modns[name] + # Now update the rest in place + for name in oldnames & newnames: + modns[name] = _update(modns[name], tmpns[name]) + # Done! + return mod + + +def _update(oldobj, newobj): + """Update oldobj, if possible in place, with newobj. + + If oldobj is immutable, this simply returns newobj. + + Args: + oldobj: the object to be updated + newobj: the object used as the source for the update + + Returns: + either oldobj, updated in place, or newobj. + """ + if type(oldobj) is not type(newobj): + # Cop-out: if the type changed, give up + return newobj + if hasattr(newobj, "__reload_update__"): + # Provide a hook for updating + return newobj.__reload_update__(oldobj) + if isinstance(newobj, types.ClassType): + return _update_class(oldobj, newobj) + if isinstance(newobj, types.FunctionType): + return _update_function(oldobj, newobj) + if isinstance(newobj, types.MethodType): + return _update_method(oldobj, newobj) + # XXX Support class methods, static methods, other decorators + # Not something we recognize, just give up + return newobj + + +def _update_function(oldfunc, newfunc): + """Update a function object.""" + oldfunc.__doc__ = newfunc.__doc__ + oldfunc.__dict__.update(newfunc.__dict__) + oldfunc.func_code = newfunc.func_code + oldfunc.func_defaults = newfunc.func_defaults + # XXX What else? + return oldfunc + + +def _update_method(oldmeth, newmeth): + """Update a method object.""" + # XXX What if im_func is not a function? + _update_function(oldmeth.im_func, newmeth.im_func) + return oldmeth + + +def _update_class(oldclass, newclass): + """Update a class object.""" + # XXX What about __slots__? + olddict = oldclass.__dict__ + newdict = newclass.__dict__ + oldnames = set(olddict) + newnames = set(newdict) + for name in newnames - oldnames: + setattr(oldclass, name, newdict[name]) + for name in oldnames - newnames: + delattr(oldclass, name) + for name in oldnames & newnames - {"__dict__", "__doc__"}: + setattr(oldclass, name, newdict[name]) + return oldclass |