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 |
Guido van Rossum | e8ef4e1 | 2007-02-26 16:57:52 +0000 | [diff] [blame^] | 4 | patching classes, methods and functions in place. This avoids the |
| 5 | need to patch instances. New objects are copied into the target |
| 6 | namespace. |
| 7 | |
| 8 | Some of the many limitiations include: |
| 9 | |
| 10 | - Global mutable objects other than classes are simply replaced, not patched |
| 11 | |
| 12 | - Code using metaclasses is not handled correctly |
| 13 | |
| 14 | - Code creating global singletons is not handled correctly |
| 15 | |
| 16 | - Functions and methods using decorators (other than classmethod and |
| 17 | staticmethod) is not handled correctly |
| 18 | |
| 19 | - Renamings are not handled correctly |
| 20 | |
| 21 | - Dependent modules are not reloaded |
| 22 | |
| 23 | - When a dependent module contains 'from foo import bar', and |
| 24 | reloading foo deletes foo.bar, the dependent module continues to use |
| 25 | the old foo.bar object rather than failing |
| 26 | |
| 27 | - Frozen modules and modules loaded from zip files aren't handled |
| 28 | correctly |
| 29 | |
| 30 | - Classes involving __slots__ are not handled correctly |
Guido van Rossum | ebbc01e | 2007-02-25 05:08:26 +0000 | [diff] [blame] | 31 | """ |
| 32 | |
| 33 | import imp |
| 34 | import sys |
| 35 | import types |
| 36 | |
| 37 | |
| 38 | def xreload(mod): |
| 39 | """Reload a module in place, updating classes, methods and functions. |
| 40 | |
| 41 | Args: |
| 42 | mod: a module object |
| 43 | |
| 44 | Returns: |
| 45 | The (updated) input object itself. |
| 46 | """ |
| 47 | # Get the module name, e.g. 'foo.bar.whatever' |
| 48 | modname = mod.__name__ |
| 49 | # Get the module namespace (dict) early; this is part of the type check |
| 50 | modns = mod.__dict__ |
| 51 | # Parse it into package name and module name, e.g. 'foo.bar' and 'whatever' |
| 52 | i = modname.rfind(".") |
| 53 | if i >= 0: |
| 54 | pkgname, modname = modname[:i], modname[i+1:] |
| 55 | else: |
| 56 | pkgname = None |
| 57 | # Compute the search path |
| 58 | if pkgname: |
| 59 | # We're not reloading the package, only the module in it |
| 60 | pkg = sys.modules[pkgname] |
| 61 | path = pkg.__path__ # Search inside the package |
| 62 | else: |
| 63 | # Search the top-level module path |
| 64 | pkg = None |
| 65 | path = None # Make find_module() uses the default search path |
| 66 | # Find the module; may raise ImportError |
| 67 | (stream, filename, (suffix, mode, kind)) = imp.find_module(modname, path) |
| 68 | # Turn it into a code object |
| 69 | try: |
| 70 | # Is it Python source code or byte code read from a file? |
Guido van Rossum | ebbc01e | 2007-02-25 05:08:26 +0000 | [diff] [blame] | 71 | if kind not in (imp.PY_COMPILED, imp.PY_SOURCE): |
| 72 | # Fall back to built-in reload() |
| 73 | return reload(mod) |
| 74 | if kind == imp.PY_SOURCE: |
| 75 | source = stream.read() |
| 76 | code = compile(source, filename, "exec") |
| 77 | else: |
| 78 | code = marshal.load(stream) |
| 79 | finally: |
| 80 | if stream: |
| 81 | stream.close() |
Guido van Rossum | 5c0a6de | 2007-02-25 21:22:21 +0000 | [diff] [blame] | 82 | # Execute the code. We copy the module dict to a temporary; then |
| 83 | # clear the module dict; then execute the new code in the module |
| 84 | # dict; then swap things back and around. This trick (due to |
| 85 | # Glyph Lefkowitz) ensures that the (readonly) __globals__ |
| 86 | # attribute of methods and functions is set to the correct dict |
| 87 | # object. |
| 88 | tmpns = modns.copy() |
| 89 | modns.clear() |
| 90 | modns["__name__"] = tmpns["__name__"] |
| 91 | exec(code, modns) |
Guido van Rossum | ebbc01e | 2007-02-25 05:08:26 +0000 | [diff] [blame] | 92 | # Now we get to the hard part |
Guido van Rossum | 5c0a6de | 2007-02-25 21:22:21 +0000 | [diff] [blame] | 93 | oldnames = set(tmpns) |
| 94 | newnames = set(modns) |
| 95 | # Update attributes in place |
Guido van Rossum | ebbc01e | 2007-02-25 05:08:26 +0000 | [diff] [blame] | 96 | for name in oldnames & newnames: |
Guido van Rossum | 5c0a6de | 2007-02-25 21:22:21 +0000 | [diff] [blame] | 97 | modns[name] = _update(tmpns[name], modns[name]) |
Guido van Rossum | ebbc01e | 2007-02-25 05:08:26 +0000 | [diff] [blame] | 98 | # Done! |
| 99 | return mod |
| 100 | |
| 101 | |
| 102 | def _update(oldobj, newobj): |
| 103 | """Update oldobj, if possible in place, with newobj. |
| 104 | |
| 105 | If oldobj is immutable, this simply returns newobj. |
| 106 | |
| 107 | Args: |
| 108 | oldobj: the object to be updated |
| 109 | newobj: the object used as the source for the update |
| 110 | |
| 111 | Returns: |
| 112 | either oldobj, updated in place, or newobj. |
| 113 | """ |
Guido van Rossum | 9a7e774 | 2007-02-25 16:19:20 +0000 | [diff] [blame] | 114 | if oldobj is newobj: |
| 115 | # Probably something imported |
| 116 | return newobj |
Guido van Rossum | ebbc01e | 2007-02-25 05:08:26 +0000 | [diff] [blame] | 117 | if type(oldobj) is not type(newobj): |
| 118 | # Cop-out: if the type changed, give up |
| 119 | return newobj |
| 120 | if hasattr(newobj, "__reload_update__"): |
| 121 | # Provide a hook for updating |
| 122 | return newobj.__reload_update__(oldobj) |
| 123 | if isinstance(newobj, types.ClassType): |
| 124 | return _update_class(oldobj, newobj) |
| 125 | if isinstance(newobj, types.FunctionType): |
| 126 | return _update_function(oldobj, newobj) |
| 127 | if isinstance(newobj, types.MethodType): |
| 128 | return _update_method(oldobj, newobj) |
Guido van Rossum | 9a7e774 | 2007-02-25 16:19:20 +0000 | [diff] [blame] | 129 | if isinstance(newobj, classmethod): |
| 130 | return _update_classmethod(oldobj, newobj) |
| 131 | if isinstance(newobj, staticmethod): |
| 132 | return _update_staticmethod(oldobj, newobj) |
Guido van Rossum | ebbc01e | 2007-02-25 05:08:26 +0000 | [diff] [blame] | 133 | # Not something we recognize, just give up |
| 134 | return newobj |
| 135 | |
| 136 | |
Guido van Rossum | 9a7e774 | 2007-02-25 16:19:20 +0000 | [diff] [blame] | 137 | # All of the following functions have the same signature as _update() |
| 138 | |
| 139 | |
Guido van Rossum | ebbc01e | 2007-02-25 05:08:26 +0000 | [diff] [blame] | 140 | def _update_function(oldfunc, newfunc): |
| 141 | """Update a function object.""" |
| 142 | oldfunc.__doc__ = newfunc.__doc__ |
| 143 | oldfunc.__dict__.update(newfunc.__dict__) |
Neal Norwitz | 221085d | 2007-02-25 20:55:47 +0000 | [diff] [blame] | 144 | oldfunc.__code__ = newfunc.__code__ |
| 145 | oldfunc.__defaults__ = newfunc.__defaults__ |
Guido van Rossum | ebbc01e | 2007-02-25 05:08:26 +0000 | [diff] [blame] | 146 | return oldfunc |
| 147 | |
| 148 | |
| 149 | def _update_method(oldmeth, newmeth): |
| 150 | """Update a method object.""" |
| 151 | # XXX What if im_func is not a function? |
Guido van Rossum | 9a7e774 | 2007-02-25 16:19:20 +0000 | [diff] [blame] | 152 | _update(oldmeth.im_func, newmeth.im_func) |
Guido van Rossum | ebbc01e | 2007-02-25 05:08:26 +0000 | [diff] [blame] | 153 | return oldmeth |
| 154 | |
| 155 | |
| 156 | def _update_class(oldclass, newclass): |
| 157 | """Update a class object.""" |
Guido van Rossum | ebbc01e | 2007-02-25 05:08:26 +0000 | [diff] [blame] | 158 | olddict = oldclass.__dict__ |
| 159 | newdict = newclass.__dict__ |
| 160 | oldnames = set(olddict) |
| 161 | newnames = set(newdict) |
| 162 | for name in newnames - oldnames: |
| 163 | setattr(oldclass, name, newdict[name]) |
| 164 | for name in oldnames - newnames: |
| 165 | delattr(oldclass, name) |
| 166 | for name in oldnames & newnames - {"__dict__", "__doc__"}: |
Guido van Rossum | 9a7e774 | 2007-02-25 16:19:20 +0000 | [diff] [blame] | 167 | setattr(oldclass, name, _update(olddict[name], newdict[name])) |
Guido van Rossum | ebbc01e | 2007-02-25 05:08:26 +0000 | [diff] [blame] | 168 | return oldclass |
Guido van Rossum | 9a7e774 | 2007-02-25 16:19:20 +0000 | [diff] [blame] | 169 | |
| 170 | |
| 171 | def _update_classmethod(oldcm, newcm): |
| 172 | """Update a classmethod update.""" |
| 173 | # While we can't modify the classmethod object itself (it has no |
| 174 | # mutable attributes), we *can* extract the underlying function |
| 175 | # (by calling __get__(), which returns a method object) and update |
| 176 | # it in-place. We don't have the class available to pass to |
| 177 | # __get__() but any object except None will do. |
| 178 | _update(oldcm.__get__(0), newcm.__get__(0)) |
| 179 | return newcm |
| 180 | |
| 181 | |
| 182 | def _update_staticmethod(oldsm, newsm): |
| 183 | """Update a staticmethod update.""" |
| 184 | # While we can't modify the staticmethod object itself (it has no |
| 185 | # mutable attributes), we *can* extract the underlying function |
| 186 | # (by calling __get__(), which returns it) and update it in-place. |
| 187 | # We don't have the class available to pass to __get__() but any |
| 188 | # object except None will do. |
| 189 | _update(oldsm.__get__(0), newsm.__get__(0)) |
| 190 | return newsm |