diff --git a/Lib/test/test_xreload.py b/Lib/test/test_xreload.py deleted file mode 100644 index dcd6569fd27..00000000000 --- a/Lib/test/test_xreload.py +++ /dev/null @@ -1,103 +0,0 @@ -"""Doctests for module reloading. - ->>> from xreload import xreload ->>> from test.test_xreload import make_mod ->>> make_mod() ->>> import x ->>> C = x.C ->>> Cfoo = C.foo ->>> Cbar = C.bar ->>> Cstomp = C.stomp ->>> b = C() ->>> bfoo = b.foo ->>> b.foo() -42 ->>> bfoo() -42 ->>> Cfoo(b) -42 ->>> Cbar() -42 42 ->>> Cstomp() -42 42 42 ->>> make_mod(repl="42", subst="24") ->>> xreload(x) - ->>> b.foo() -24 ->>> bfoo() -24 ->>> Cfoo(b) -24 ->>> Cbar() -24 24 ->>> Cstomp() -24 24 24 - -""" - -SAMPLE_CODE = """ -class C: - def foo(self): - print(42) - @classmethod - def bar(cls): - print(42, 42) - @staticmethod - def stomp(): - print (42, 42, 42) -""" - -import os -import sys -import shutil -import doctest -import xreload -import tempfile -from test.test_support import run_unittest - -tempdir = None -save_path = None - - -def setUp(unused=None): - global tempdir, save_path - tempdir = tempfile.mkdtemp() - save_path = list(sys.path) - sys.path.append(tempdir) - - -def tearDown(unused=None): - global tempdir, save_path - if save_path is not None: - sys.path = save_path - save_path = None - if tempdir is not None: - shutil.rmtree(tempdir) - tempdir = None - - -def make_mod(name="x", repl=None, subst=None): - if not tempdir: - setUp() - assert tempdir - fn = os.path.join(tempdir, name + ".py") - f = open(fn, "w") - sample = SAMPLE_CODE - if repl is not None and subst is not None: - sample = sample.replace(repl, subst) - try: - f.write(sample) - finally: - f.close() - - -def test_suite(): - return doctest.DocTestSuite(setUp=setUp, tearDown=tearDown) - - -def test_main(): - run_unittest(test_suite()) - -if __name__ == "__main__": - test_main() diff --git a/Lib/xreload.py b/Lib/xreload.py deleted file mode 100644 index fea9a841b8b..00000000000 --- a/Lib/xreload.py +++ /dev/null @@ -1,190 +0,0 @@ -"""Alternative to reload(). - -This works by executing the module in a scratch namespace, and then -patching classes, methods and functions in place. This avoids the -need to patch instances. New objects are copied into the target -namespace. - -Some of the many limitiations include: - -- Global mutable objects other than classes are simply replaced, not patched - -- Code using metaclasses is not handled correctly - -- Code creating global singletons is not handled correctly - -- Functions and methods using decorators (other than classmethod and - staticmethod) is not handled correctly - -- Renamings are not handled correctly - -- Dependent modules are not reloaded - -- When a dependent module contains 'from foo import bar', and - reloading foo deletes foo.bar, the dependent module continues to use - the old foo.bar object rather than failing - -- Frozen modules and modules loaded from zip files aren't handled - correctly - -- Classes involving __slots__ are not handled correctly -""" - -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? - 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. We copy the module dict to a temporary; then - # clear the module dict; then execute the new code in the module - # dict; then swap things back and around. This trick (due to - # Glyph Lefkowitz) ensures that the (readonly) __globals__ - # attribute of methods and functions is set to the correct dict - # object. - tmpns = modns.copy() - modns.clear() - modns["__name__"] = tmpns["__name__"] - exec(code, modns) - # Now we get to the hard part - oldnames = set(tmpns) - newnames = set(modns) - # Update attributes in place - for name in oldnames & newnames: - modns[name] = _update(tmpns[name], modns[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 oldobj is newobj: - # Probably something imported - return 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) - if isinstance(newobj, classmethod): - return _update_classmethod(oldobj, newobj) - if isinstance(newobj, staticmethod): - return _update_staticmethod(oldobj, newobj) - # Not something we recognize, just give up - return newobj - - -# All of the following functions have the same signature as _update() - - -def _update_function(oldfunc, newfunc): - """Update a function object.""" - oldfunc.__doc__ = newfunc.__doc__ - oldfunc.__dict__.update(newfunc.__dict__) - oldfunc.__code__ = newfunc.__code__ - oldfunc.__defaults__ = newfunc.__defaults__ - return oldfunc - - -def _update_method(oldmeth, newmeth): - """Update a method object.""" - # XXX What if im_func is not a function? - _update(oldmeth.im_func, newmeth.im_func) - return oldmeth - - -def _update_class(oldclass, newclass): - """Update a class object.""" - 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, _update(olddict[name], newdict[name])) - return oldclass - - -def _update_classmethod(oldcm, newcm): - """Update a classmethod update.""" - # While we can't modify the classmethod object itself (it has no - # mutable attributes), we *can* extract the underlying function - # (by calling __get__(), which returns a method object) and update - # it in-place. We don't have the class available to pass to - # __get__() but any object except None will do. - _update(oldcm.__get__(0), newcm.__get__(0)) - return newcm - - -def _update_staticmethod(oldsm, newsm): - """Update a staticmethod update.""" - # While we can't modify the staticmethod object itself (it has no - # mutable attributes), we *can* extract the underlying function - # (by calling __get__(), which returns it) and update it in-place. - # We don't have the class available to pass to __get__() but any - # object except None will do. - _update(oldsm.__get__(0), newsm.__get__(0)) - return newsm