Show More
@@ -19,6 +19,7 b' for documentation.' | |||||
19 | #------------------------------------------------------------------------------ |
|
19 | #------------------------------------------------------------------------------ | |
20 |
|
20 | |||
21 | import time, os, threading, sys, types, imp, inspect, traceback, atexit |
|
21 | import time, os, threading, sys, types, imp, inspect, traceback, atexit | |
|
22 | import weakref | |||
22 |
|
23 | |||
23 | def _get_compiled_ext(): |
|
24 | def _get_compiled_ext(): | |
24 | """Official way to get the extension of compiled files (.pyc or .pyo)""" |
|
25 | """Official way to get the extension of compiled files (.pyc or .pyo)""" | |
@@ -29,7 +30,7 b' def _get_compiled_ext():' | |||||
29 | PY_COMPILED_EXT = _get_compiled_ext() |
|
30 | PY_COMPILED_EXT = _get_compiled_ext() | |
30 |
|
31 | |||
31 | class ModuleReloader(object): |
|
32 | class ModuleReloader(object): | |
32 |
|
|
33 | failed = {} | |
33 | """Modules that failed to reload: {module: mtime-on-failed-reload, ...}""" |
|
34 | """Modules that failed to reload: {module: mtime-on-failed-reload, ...}""" | |
34 |
|
35 | |||
35 | modules = {} |
|
36 | modules = {} | |
@@ -40,6 +41,9 b' class ModuleReloader(object):' | |||||
40 |
|
41 | |||
41 | check_all = True |
|
42 | check_all = True | |
42 | """Autoreload all modules, not just those listed in 'modules'""" |
|
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 | def check(self, check_all=False): |
|
48 | def check(self, check_all=False): | |
45 | """Check whether some modules need to be reloaded.""" |
|
49 | """Check whether some modules need to be reloaded.""" | |
@@ -77,62 +81,152 b' class ModuleReloader(object):' | |||||
77 | pymtime = os.stat(filename[:-1]).st_mtime |
|
81 | pymtime = os.stat(filename[:-1]).st_mtime | |
78 | if pymtime <= os.stat(filename).st_mtime: |
|
82 | if pymtime <= os.stat(filename).st_mtime: | |
79 | continue |
|
83 | continue | |
80 |
if self. |
|
84 | if self.failed.get(filename[:-1], None) == pymtime: | |
81 | continue |
|
85 | continue | |
82 | except OSError: |
|
86 | except OSError: | |
83 | continue |
|
87 | continue | |
84 |
|
88 | |||
85 | try: |
|
89 | try: | |
86 | superreload(m) |
|
90 | superreload(m, reload, self.old_objects) | |
87 |
if filename[:-1] in self. |
|
91 | if filename[:-1] in self.failed: | |
88 |
del self. |
|
92 | del self.failed[filename[:-1]] | |
89 | except: |
|
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): |
|
98 | #------------------------------------------------------------------------------ | |
93 | for name in attrnames: |
|
99 | # superreload | |
94 | setattr(old, name, getattr(new, name)) |
|
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 | """Enhanced version of the builtin reload function. |
|
170 | """Enhanced version of the builtin reload function. | |
98 |
|
|
171 | ||
99 | superreload replaces the class dictionary of every top-level |
|
172 | superreload remembers objects previously in the module, and | |
100 | class in the module with the new one automatically, |
|
173 | ||
101 | as well as every function's code object. |
|
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 | module = reload(module) |
|
201 | module = reload(module) | |
106 |
|
202 | |||
107 |
# iterate over all objects and update |
|
203 | # iterate over all objects and update functions & classes | |
108 | count = 0 |
|
|||
109 | for name, new_obj in module.__dict__.items(): |
|
204 | for name, new_obj in module.__dict__.items(): | |
110 | key = (module.__name__, name) |
|
205 | key = (module.__name__, name) | |
111 | if _old_objects.has_key(key): |
|
206 | if key not in old_objects: continue | |
112 | for old_obj in _old_objects[key]: |
|
207 | ||
113 | if type(new_obj) == types.ClassType: |
|
208 | new_refs = [] | |
114 | old_obj.__dict__.update(new_obj.__dict__) |
|
209 | for old_ref in old_objects[key]: | |
115 | count += 1 |
|
210 | old_obj = old_ref() | |
116 | elif type(new_obj) == types.FunctionType: |
|
211 | if old_obj is None: continue | |
117 | update_function(old_obj, |
|
212 | new_refs.append(old_ref) | |
118 | new_obj, |
|
213 | update_generic(old_obj, new_obj) | |
119 | "func_code func_defaults func_doc".split()) |
|
214 | ||
120 | count += 1 |
|
215 | if new_refs: | |
121 | elif type(new_obj) == types.MethodType: |
|
216 | old_objects[key] = new_refs | |
122 | update_function(old_obj.im_func, |
|
217 | else: | |
123 | new_obj.im_func, |
|
218 | del old_objects[key] | |
124 | "func_code func_defaults func_doc".split()) |
|
219 | ||
125 | count += 1 |
|
|||
126 |
|
||||
127 | return module |
|
220 | return module | |
128 |
|
221 | |||
129 | reloader = ModuleReloader() |
|
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 | autoreload_enabled = False |
|
231 | autoreload_enabled = False | |
138 |
|
232 | |||
@@ -144,48 +238,50 b' def runcode_hook(self):' | |||||
144 | except: |
|
238 | except: | |
145 | pass |
|
239 | pass | |
146 |
|
240 | |||
147 |
|
||||
148 | def enable_autoreload(): |
|
241 | def enable_autoreload(): | |
149 | global autoreload_enabled |
|
242 | global autoreload_enabled | |
150 | autoreload_enabled = True |
|
243 | autoreload_enabled = True | |
151 |
|
||||
152 |
|
244 | |||
153 | def disable_autoreload(): |
|
245 | def disable_autoreload(): | |
154 | global autoreload_enabled |
|
246 | global autoreload_enabled | |
155 | autoreload_enabled = False |
|
247 | autoreload_enabled = False | |
156 |
|
||||
157 | #------------------------------------------------------------------------------ |
|
|||
158 | # IPython connectivity |
|
|||
159 | #------------------------------------------------------------------------------ |
|
|||
160 |
|
||||
161 | import IPython.ipapi |
|
|||
162 | ip = IPython.ipapi.get() |
|
|||
163 |
|
248 | |||
164 | def autoreload_f(self, parameter_s=''): |
|
249 | def autoreload_f(self, parameter_s=''): | |
165 | r""" %autoreload => Reload modules automatically |
|
250 | r""" %autoreload => Reload modules automatically | |
166 |
|
251 | |||
167 | %autoreload |
|
252 | %autoreload | |
168 |
Reload all modules (except those |
|
253 | Reload all modules (except those excluded by %aimport) automatically now. | |
169 |
|
254 | |||
170 | %autoreload 1 |
|
255 | %autoreload 1 | |
171 | Reload all modules imported with %aimport every time before executing |
|
256 | Reload all modules imported with %aimport every time before executing | |
172 | the Python code typed. |
|
257 | the Python code typed. | |
173 |
|
258 | |||
174 | %autoreload 2 |
|
259 | %autoreload 2 | |
175 |
Reload all modules (except those |
|
260 | Reload all modules (except those excluded by %aimport) every time | |
176 | before executing the Python code typed. |
|
261 | before executing the Python code typed. | |
177 |
|
262 | |||
178 | Reloading Python modules in a reliable way is in general |
|
263 | Reloading Python modules in a reliable way is in general difficult, | |
179 |
|
|
264 | and unexpected things may occur. %autoreload tries to work | |
180 | caveats relevant for 'autoreload' are: |
|
265 | around common pitfalls by replacing code objects of functions | |
181 |
|
266 | previously in the module with new versions. This makes the following | ||
182 | - Modules are not reloaded in any specific order, and no dependency |
|
267 | things to work: | |
183 | analysis is done. For example, modules with 'from xxx import foo' |
|
268 | ||
184 | retain old versions of 'foo' when 'xxx' is autoreloaded. |
|
269 | - Functions and classes imported via 'from xxx import foo' are upgraded | |
185 | - Functions or objects imported from the autoreloaded module to |
|
270 | to new versions when 'xxx' is reloaded. | |
186 | the interactive namespace are not updated. |
|
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 | - C extension modules cannot be reloaded, and so cannot be |
|
282 | - C extension modules cannot be reloaded, and so cannot be | |
188 | autoreloaded. |
|
283 | autoreloaded. | |
|
284 | ||||
189 | """ |
|
285 | """ | |
190 | if parameter_s == '': |
|
286 | if parameter_s == '': | |
191 | reloader.check(True) |
|
287 | reloader.check(True) | |
@@ -241,4 +337,4 b' def init():' | |||||
241 | ip.expose_magic('aimport', aimport_f) |
|
337 | ip.expose_magic('aimport', aimport_f) | |
242 | ip.set_hook('pre_runcode_hook', runcode_hook) |
|
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 | 2008-06-03 Ville Vainio <vivainio@gmail.com> |
|
11 | 2008-06-03 Ville Vainio <vivainio@gmail.com> | |
2 |
|
12 | |||
3 | * ipython.rst, ipython.1: remove -twisted from man page, |
|
13 | * ipython.rst, ipython.1: remove -twisted from man page, |
General Comments 0
You need to be logged in to leave comments.
Login now