Attachment 'xreload.py'
Download 1 """Alternative to reload().
2
3 This works by executing the module in a scratch namespace, and then
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
31 """
32
33 import imp
34 import sys
35 import types
36
37
38 def xreload(mod, path=None):
39 """Reload a module in place, updating classes, methods and functions.
40
41 mod can live in a package. If path is None, we try to find mod from mod's
42 packages's path. If path is not None, we try to use it as the list of top
43 level packages for mod. For example:
44
45 mod = xxx.yyy.m. If path is None, we try to find m in xxx.yyy.__path__.
46 If path is ['path1/', 'path2/'], we try to find m in 'path1/xxx/yyy' first
47 and then in 'path2/xxx/yyy'.
48
49 Args:
50 mod: a module object
51
52 Returns:
53 The (updated) input object itself.
54 """
55 # Get the module name, e.g. 'foo.bar.whatever'
56 modname = mod.__name__
57 # Get the module namespace (dict) early; this is part of the type check
58 modns = mod.__dict__
59 # Parse it into package name and module name, e.g. 'foo.bar' and 'whatever'
60 i = modname.rfind(".")
61 if i >= 0:
62 pkgname, modname = modname[:i], modname[i+1:]
63 else:
64 pkgname = None
65 # Compute the search path
66 if pkgname:
67 # We're not reloading the package, only the module in it
68 pkg = sys.modules[pkgname]
69 if path is None:
70 path = pkg.__path__ # Search inside the current parent package
71 else:
72 ps = pkgname.split('.')
73 import os.path
74 print path
75 path = [os.path.join(*([p] + ps)) for p in path]
76 else:
77 # Search the top-level module path
78 pkg = None
79 # path = None # Make find_module() use the default search path
80 print "xreload is using path=%r" % path
81 # Find the module; may raise ImportError
82 (stream, filename, (suffix, mode, kind)) = imp.find_module(modname, path)
83 # Turn it into a code object
84 try:
85 # Is it Python source code or byte code read from a file?
86 if kind not in (imp.PY_COMPILED, imp.PY_SOURCE):
87 # Fall back to built-in reload()
88 return reload(mod)
89 if kind == imp.PY_SOURCE:
90 source = stream.read()
91 code = compile(source, filename, "exec")
92 else:
93 code = marshal.load(stream)
94 finally:
95 if stream:
96 stream.close()
97 # Execute the code. We copy the module dict to a temporary; then
98 # clear the module dict; then execute the new code in the module
99 # dict; then swap things back and around. This trick (due to
100 # Glyph Lefkowitz) ensures that the (readonly) __globals__
101 # attribute of methods and functions is set to the correct dict
102 # object.
103 tmpns = modns.copy()
104 modns.clear()
105 modns["__name__"] = tmpns["__name__"]
106 exec(code, modns)
107 # Now we get to the hard part
108 oldnames = set(tmpns)
109 newnames = set(modns)
110 # Update attributes in place
111 for name in oldnames & newnames:
112 modns[name] = _update(tmpns[name], modns[name])
113 # Don't lose our filename, for inspect
114 modns["__file__"] = filename
115 # Done!
116 return mod
117
118
119 def _update(oldobj, newobj):
120 """Update oldobj, if possible in place, with newobj.
121
122 If oldobj is immutable, this simply returns newobj.
123
124 Args:
125 oldobj: the object to be updated
126 newobj: the object used as the source for the update
127
128 Returns:
129 either oldobj, updated in place, or newobj.
130 """
131 if oldobj is newobj:
132 # Probably something imported
133 return newobj
134 if type(oldobj) is not type(newobj):
135 # Cop-out: if the type changed, give up
136 return newobj
137 if hasattr(newobj, "__reload_update__"):
138 # Provide a hook for updating
139 return newobj.__reload_update__(oldobj)
140 if isinstance(newobj, types.ClassType):
141 return _update_class(oldobj, newobj)
142 if isinstance(newobj, types.FunctionType):
143 return _update_function(oldobj, newobj)
144 if isinstance(newobj, types.MethodType):
145 return _update_method(oldobj, newobj)
146 if isinstance(newobj, classmethod):
147 return _update_classmethod(oldobj, newobj)
148 if isinstance(newobj, staticmethod):
149 return _update_staticmethod(oldobj, newobj)
150 # Not something we recognize, just give up
151 return newobj
152
153
154 # All of the following functions have the same signature as _update()
155
156
157 def _update_function(oldfunc, newfunc):
158 """Update a function object."""
159 oldfunc.__doc__ = newfunc.__doc__
160 oldfunc.__dict__.update(newfunc.__dict__)
161 oldfunc.func_code = newfunc.func_code
162 oldfunc.func_defaults = newfunc.func_defaults
163 return oldfunc
164
165
166 def _update_method(oldmeth, newmeth):
167 """Update a method object."""
168 # XXX What if im_func is not a function?
169 _update(oldmeth.im_func, newmeth.im_func)
170 return oldmeth
171
172
173 def _update_class(oldclass, newclass):
174 """Update a class object."""
175 olddict = oldclass.__dict__
176 newdict = newclass.__dict__
177 oldnames = set(olddict)
178 newnames = set(newdict)
179 for name in newnames - oldnames:
180 setattr(oldclass, name, newdict[name])
181 for name in oldnames - newnames:
182 delattr(oldclass, name)
183 for name in oldnames & newnames - set(["__dict__", "__doc__"]):
184 setattr(oldclass, name, _update(olddict[name], newdict[name]))
185 return oldclass
186
187
188 def _update_classmethod(oldcm, newcm):
189 """Update a classmethod update."""
190 # While we can't modify the classmethod object itself (it has no
191 # mutable attributes), we *can* extract the underlying function
192 # (by calling __get__(), which returns a method object) and update
193 # it in-place. We don't have the class available to pass to
194 # __get__() but any object except None will do.
195 _update(oldcm.__get__(0), newcm.__get__(0))
196 return newcm
197
198
199 def _update_staticmethod(oldsm, newsm):
200 """Update a staticmethod update."""
201 # While we can't modify the staticmethod object itself (it has no
202 # mutable attributes), we *can* extract the underlying function
203 # (by calling __get__(), which returns it) and update it in-place.
204 # We don't have the class available to pass to __get__() but any
205 # object except None will do.
206 _update(oldsm.__get__(0), newsm.__get__(0))
207 return newsm
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.