Show More
@@ -19,6 +19,7 b' for documentation.' | |||
|
19 | 19 | #------------------------------------------------------------------------------ |
|
20 | 20 | |
|
21 | 21 | import time, os, threading, sys, types, imp, inspect, traceback, atexit |
|
22 | import weakref | |
|
22 | 23 | |
|
23 | 24 | def _get_compiled_ext(): |
|
24 | 25 | """Official way to get the extension of compiled files (.pyc or .pyo)""" |
@@ -29,7 +30,7 b' def _get_compiled_ext():' | |||
|
29 | 30 | PY_COMPILED_EXT = _get_compiled_ext() |
|
30 | 31 | |
|
31 | 32 | class ModuleReloader(object): |
|
32 |
|
|
|
33 | failed = {} | |
|
33 | 34 | """Modules that failed to reload: {module: mtime-on-failed-reload, ...}""" |
|
34 | 35 | |
|
35 | 36 | modules = {} |
@@ -40,6 +41,9 b' class ModuleReloader(object):' | |||
|
40 | 41 | |
|
41 | 42 | check_all = True |
|
42 | 43 | """Autoreload all modules, not just those listed in 'modules'""" |
|
44 | ||
|
45 | old_objects = {} | |
|
46 | """(module-name, name) -> weakref, for replacing old code objects""" | |
|
43 | 47 | |
|
44 | 48 | def check(self, check_all=False): |
|
45 | 49 | """Check whether some modules need to be reloaded.""" |
@@ -77,62 +81,152 b' class ModuleReloader(object):' | |||
|
77 | 81 | pymtime = os.stat(filename[:-1]).st_mtime |
|
78 | 82 | if pymtime <= os.stat(filename).st_mtime: |
|
79 | 83 | continue |
|
80 |
if self. |
|
|
84 | if self.failed.get(filename[:-1], None) == pymtime: | |
|
81 | 85 | continue |
|
82 | 86 | except OSError: |
|
83 | 87 | continue |
|
84 | 88 | |
|
85 | 89 | try: |
|
86 | superreload(m) | |
|
87 |
if filename[:-1] in self. |
|
|
88 |
del self. |
|
|
90 | superreload(m, reload, self.old_objects) | |
|
91 | if filename[:-1] in self.failed: | |
|
92 | del self.failed[filename[:-1]] | |
|
89 | 93 | except: |
|
90 | self.skipped[filename[:-1]] = pymtime | |
|
94 | print >> sys.stderr, "[autoreload of %s failed: %s]" % ( | |
|
95 | modname, traceback.format_exc(1)) | |
|
96 | self.failed[filename[:-1]] = pymtime | |
|
91 | 97 | |
|
92 | def update_function(old, new, attrnames): | |
|
93 | for name in attrnames: | |
|
94 | setattr(old, name, getattr(new, name)) | |
|
98 | #------------------------------------------------------------------------------ | |
|
99 | # superreload | |
|
100 | #------------------------------------------------------------------------------ | |
|
95 | 101 | |
|
96 | def superreload(module, reload=reload): | |
|
102 | def update_function(old, new): | |
|
103 | """Upgrade the code object of a function""" | |
|
104 | for name in ['func_code', 'func_defaults', 'func_doc', | |
|
105 | 'func_closure', 'func_globals', 'func_dict']: | |
|
106 | try: | |
|
107 | setattr(old, name, getattr(new, name)) | |
|
108 | except (AttributeError, TypeError): | |
|
109 | pass | |
|
110 | ||
|
111 | def update_class(old, new): | |
|
112 | """Replace stuff in the __dict__ of a class, and upgrade | |
|
113 | method code objects""" | |
|
114 | for key in old.__dict__.keys(): | |
|
115 | old_obj = getattr(old, key) | |
|
116 | ||
|
117 | try: | |
|
118 | new_obj = getattr(new, key) | |
|
119 | except AttributeError: | |
|
120 | # obsolete attribute: remove it | |
|
121 | try: | |
|
122 | delattr(old, key) | |
|
123 | except (AttributeError, TypeError): | |
|
124 | pass | |
|
125 | continue | |
|
126 | ||
|
127 | if update_generic(old_obj, new_obj): continue | |
|
128 | ||
|
129 | try: | |
|
130 | setattr(old, key, getattr(new, key)) | |
|
131 | except (AttributeError, TypeError): | |
|
132 | pass # skip non-writable attributes | |
|
133 | ||
|
134 | def update_property(old, new): | |
|
135 | """Replace get/set/del functions of a property""" | |
|
136 | update_generic(old.fdel, new.fdel) | |
|
137 | update_generic(old.fget, new.fget) | |
|
138 | update_generic(old.fset, new.fset) | |
|
139 | ||
|
140 | def isinstance2(a, b, typ): | |
|
141 | return isinstance(a, typ) and isinstance(b, typ) | |
|
142 | ||
|
143 | UPDATE_RULES = [ | |
|
144 | (lambda a, b: isinstance2(a, b, types.ClassType), | |
|
145 | update_class), | |
|
146 | (lambda a, b: isinstance2(a, b, types.TypeType), | |
|
147 | update_class), | |
|
148 | (lambda a, b: isinstance2(a, b, types.FunctionType), | |
|
149 | update_function), | |
|
150 | (lambda a, b: isinstance2(a, b, property), | |
|
151 | update_property), | |
|
152 | (lambda a, b: isinstance2(a, b, types.MethodType), | |
|
153 | lambda a, b: update_function(a.im_func, b.im_func)), | |
|
154 | ] | |
|
155 | ||
|
156 | def update_generic(a, b): | |
|
157 | for type_check, update in UPDATE_RULES: | |
|
158 | if type_check(a, b): | |
|
159 | update(a, b) | |
|
160 | return True | |
|
161 | return False | |
|
162 | ||
|
163 | class StrongRef(object): | |
|
164 | def __init__(self, obj): | |
|
165 | self.obj = obj | |
|
166 | def __call__(self): | |
|
167 | return self.obj | |
|
168 | ||
|
169 | def superreload(module, reload=reload, old_objects={}): | |
|
97 | 170 | """Enhanced version of the builtin reload function. |
|
98 |
|
|
|
99 | superreload replaces the class dictionary of every top-level | |
|
100 | class in the module with the new one automatically, | |
|
101 | as well as every function's code object. | |
|
171 | ||
|
172 | superreload remembers objects previously in the module, and | |
|
173 | ||
|
174 | - upgrades the class dictionary of every old class in the module | |
|
175 | - upgrades the code object of every old function and method | |
|
176 | - clears the module's namespace before reloading | |
|
102 | 177 | |
|
103 | 178 | """ |
|
104 | ||
|
179 | ||
|
180 | # collect old objects in the module | |
|
181 | for name, obj in module.__dict__.items(): | |
|
182 | if not hasattr(obj, '__module__') or obj.__module__ != module.__name__: | |
|
183 | continue | |
|
184 | key = (module.__name__, name) | |
|
185 | try: | |
|
186 | old_objects.setdefault(key, []).append(weakref.ref(obj)) | |
|
187 | except TypeError: | |
|
188 | # weakref doesn't work for all types; | |
|
189 | # create strong references for 'important' cases | |
|
190 | if isinstance(obj, types.ClassType): | |
|
191 | old_objects.setdefault(key, []).append(StrongRef(obj)) | |
|
192 | ||
|
193 | # reload module | |
|
194 | try: | |
|
195 | # clear namespace first from old cruft | |
|
196 | old_name = module.__name__ | |
|
197 | module.__dict__.clear() | |
|
198 | module.__dict__['__name__'] = old_name | |
|
199 | except (TypeError, AttributeError, KeyError): | |
|
200 | pass | |
|
105 | 201 | module = reload(module) |
|
106 | 202 | |
|
107 |
# iterate over all objects and update |
|
|
108 | count = 0 | |
|
203 | # iterate over all objects and update functions & classes | |
|
109 | 204 | for name, new_obj in module.__dict__.items(): |
|
110 | 205 | key = (module.__name__, name) |
|
111 | if _old_objects.has_key(key): | |
|
112 | for old_obj in _old_objects[key]: | |
|
113 | if type(new_obj) == types.ClassType: | |
|
114 | old_obj.__dict__.update(new_obj.__dict__) | |
|
115 | count += 1 | |
|
116 | elif type(new_obj) == types.FunctionType: | |
|
117 | update_function(old_obj, | |
|
118 | new_obj, | |
|
119 | "func_code func_defaults func_doc".split()) | |
|
120 | count += 1 | |
|
121 | elif type(new_obj) == types.MethodType: | |
|
122 | update_function(old_obj.im_func, | |
|
123 | new_obj.im_func, | |
|
124 | "func_code func_defaults func_doc".split()) | |
|
125 | count += 1 | |
|
126 | ||
|
206 | if key not in old_objects: continue | |
|
207 | ||
|
208 | new_refs = [] | |
|
209 | for old_ref in old_objects[key]: | |
|
210 | old_obj = old_ref() | |
|
211 | if old_obj is None: continue | |
|
212 | new_refs.append(old_ref) | |
|
213 | update_generic(old_obj, new_obj) | |
|
214 | ||
|
215 | if new_refs: | |
|
216 | old_objects[key] = new_refs | |
|
217 | else: | |
|
218 | del old_objects[key] | |
|
219 | ||
|
127 | 220 | return module |
|
128 | 221 | |
|
129 | 222 | reloader = ModuleReloader() |
|
130 | 223 | |
|
131 | 224 | #------------------------------------------------------------------------------ |
|
132 | # IPython monkey-patching | |
|
225 | # IPython connectivity | |
|
133 | 226 | #------------------------------------------------------------------------------ |
|
227 | import IPython.ipapi | |
|
134 | 228 | |
|
135 |
i |
|
|
229 | ip = IPython.ipapi.get() | |
|
136 | 230 | |
|
137 | 231 | autoreload_enabled = False |
|
138 | 232 | |
@@ -144,48 +238,50 b' def runcode_hook(self):' | |||
|
144 | 238 | except: |
|
145 | 239 | pass |
|
146 | 240 | |
|
147 | ||
|
148 | 241 | def enable_autoreload(): |
|
149 | 242 | global autoreload_enabled |
|
150 | 243 | autoreload_enabled = True |
|
151 | ||
|
152 | 244 | |
|
153 | 245 | def disable_autoreload(): |
|
154 | 246 | global autoreload_enabled |
|
155 | 247 | autoreload_enabled = False |
|
156 | ||
|
157 | #------------------------------------------------------------------------------ | |
|
158 | # IPython connectivity | |
|
159 | #------------------------------------------------------------------------------ | |
|
160 | ||
|
161 | import IPython.ipapi | |
|
162 | ip = IPython.ipapi.get() | |
|
163 | 248 | |
|
164 | 249 | def autoreload_f(self, parameter_s=''): |
|
165 | 250 | r""" %autoreload => Reload modules automatically |
|
166 | ||
|
251 | ||
|
167 | 252 | %autoreload |
|
168 |
Reload all modules (except those |
|
|
169 | ||
|
253 | Reload all modules (except those excluded by %aimport) automatically now. | |
|
254 | ||
|
170 | 255 | %autoreload 1 |
|
171 | 256 | Reload all modules imported with %aimport every time before executing |
|
172 | 257 | the Python code typed. |
|
173 | ||
|
258 | ||
|
174 | 259 | %autoreload 2 |
|
175 |
Reload all modules (except those |
|
|
260 | Reload all modules (except those excluded by %aimport) every time | |
|
176 | 261 | before executing the Python code typed. |
|
177 | ||
|
178 | Reloading Python modules in a reliable way is in general | |
|
179 |
|
|
|
180 | caveats relevant for 'autoreload' are: | |
|
181 | ||
|
182 | - Modules are not reloaded in any specific order, and no dependency | |
|
183 | analysis is done. For example, modules with 'from xxx import foo' | |
|
184 | retain old versions of 'foo' when 'xxx' is autoreloaded. | |
|
185 | - Functions or objects imported from the autoreloaded module to | |
|
186 | the interactive namespace are not updated. | |
|
262 | ||
|
263 | Reloading Python modules in a reliable way is in general difficult, | |
|
264 | and unexpected things may occur. %autoreload tries to work | |
|
265 | around common pitfalls by replacing code objects of functions | |
|
266 | previously in the module with new versions. This makes the following | |
|
267 | things to work: | |
|
268 | ||
|
269 | - Functions and classes imported via 'from xxx import foo' are upgraded | |
|
270 | to new versions when 'xxx' is reloaded. | |
|
271 | - Methods and properties of classes are upgraded on reload, so that | |
|
272 | calling 'c.foo()' on an object 'c' created before the reload causes | |
|
273 | the new code for 'foo' to be executed. | |
|
274 | ||
|
275 | Some of the known remaining caveats are: | |
|
276 | ||
|
277 | - Replacing code objects does not always succeed: changing a @property | |
|
278 | in a class to an ordinary method or a method to a member variable | |
|
279 | can cause problems (but in old objects only). | |
|
280 | - Functions that are removed (eg. via monkey-patching) from a module | |
|
281 | before it is reloaded are not upgraded. | |
|
187 | 282 | - C extension modules cannot be reloaded, and so cannot be |
|
188 | 283 | autoreloaded. |
|
284 | ||
|
189 | 285 | """ |
|
190 | 286 | if parameter_s == '': |
|
191 | 287 | reloader.check(True) |
@@ -241,4 +337,4 b' def init():' | |||
|
241 | 337 | ip.expose_magic('aimport', aimport_f) |
|
242 | 338 | ip.set_hook('pre_runcode_hook', runcode_hook) |
|
243 | 339 | |
|
244 | init() No newline at end of file | |
|
340 | init() |
@@ -1,3 +1,13 b'' | |||
|
1 | 2008-06-09 Ville Vainio <vivainio@gmail.com> | |
|
2 | ||
|
3 | * Extensions/ipy_autoreload.py: Apply Pauli Virtanen's patch | |
|
4 | to autoreloading that also replaces the changed code objects | |
|
5 | with new versions | |
|
6 | ||
|
7 | * pspersistence.py: report UsageError on %store -w w/o arg, | |
|
8 | and other usage pattern errors. Bug report by Johann Cohen-Tanugi. | |
|
9 | ||
|
10 | ||
|
1 | 11 | 2008-06-03 Ville Vainio <vivainio@gmail.com> |
|
2 | 12 | |
|
3 | 13 | * ipython.rst, ipython.1: remove -twisted from man page, |
General Comments 0
You need to be logged in to leave comments.
Login now