##// END OF EJS Templates
Pauli's autoreload patch to do proper "superreload", i.e. replace code objects of used function objects. Fixes #237691
Ville M. Vainio -
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 skipped = {}
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.skipped.get(filename[:-1], None) == pymtime:
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.skipped:
91 if filename[:-1] in self.failed:
88 del self.skipped[filename[:-1]]
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 them
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 import IPython.iplib
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 thoses excluded by %aimport) automatically now.
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 thoses excluded by %aimport) every time
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 difficult, and unexpected things may occur. Some of the common
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