summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGuido van Rossum <guido@python.org>2007-02-25 05:08:26 (GMT)
committerGuido van Rossum <guido@python.org>2007-02-25 05:08:26 (GMT)
commitebbc01ef505d4c2f694fb72c7880b8bad0102e4a (patch)
tree8f207f8a1bc4161c7c913c67128b71071c0aa4d4
parentee91be45df796b8e5721d9142a6e92e55a465451 (diff)
downloadcpython-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.py136
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