##// 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 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 skipped = {}
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.skipped.get(filename[:-1], None) == pymtime:
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.skipped:
88 del self.skipped[filename[:-1]]
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 them
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 import IPython.iplib
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 thoses excluded by %aimport) automatically now.
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 thoses excluded by %aimport) every time
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 difficult, and unexpected things may occur. Some of the common
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