##// END OF EJS Templates
Avoid reloading builtin `types` module....
Matthias Bussonnier -
Show More
@@ -1,339 +1,347 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 Provides a reload() function that acts recursively.
3 Provides a reload() function that acts recursively.
4
4
5 Python's normal :func:`python:reload` function only reloads the module that it's
5 Python's normal :func:`python:reload` function only reloads the module that it's
6 passed. The :func:`reload` function in this module also reloads everything
6 passed. The :func:`reload` function in this module also reloads everything
7 imported from that module, which is useful when you're changing files deep
7 imported from that module, which is useful when you're changing files deep
8 inside a package.
8 inside a package.
9
9
10 To use this as your default reload function, type this for Python 2::
10 To use this as your default reload function, type this for Python 2::
11
11
12 import __builtin__
12 import __builtin__
13 from IPython.lib import deepreload
13 from IPython.lib import deepreload
14 __builtin__.reload = deepreload.reload
14 __builtin__.reload = deepreload.reload
15
15
16 Or this for Python 3::
16 Or this for Python 3::
17
17
18 import builtins
18 import builtins
19 from IPython.lib import deepreload
19 from IPython.lib import deepreload
20 builtins.reload = deepreload.reload
20 builtins.reload = deepreload.reload
21
21
22 A reference to the original :func:`python:reload` is stored in this module as
22 A reference to the original :func:`python:reload` is stored in this module as
23 :data:`original_reload`, so you can restore it later.
23 :data:`original_reload`, so you can restore it later.
24
24
25 This code is almost entirely based on knee.py, which is a Python
25 This code is almost entirely based on knee.py, which is a Python
26 re-implementation of hierarchical module import.
26 re-implementation of hierarchical module import.
27 """
27 """
28 #*****************************************************************************
28 #*****************************************************************************
29 # Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.edu>
29 # Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.edu>
30 #
30 #
31 # Distributed under the terms of the BSD License. The full license is in
31 # Distributed under the terms of the BSD License. The full license is in
32 # the file COPYING, distributed as part of this software.
32 # the file COPYING, distributed as part of this software.
33 #*****************************************************************************
33 #*****************************************************************************
34
34
35 import builtins as builtin_mod
35 import builtins as builtin_mod
36 from contextlib import contextmanager
36 from contextlib import contextmanager
37 import imp
37 import imp
38 import sys
38 import sys
39
39
40 from types import ModuleType
40 from types import ModuleType
41 from warnings import warn
41 from warnings import warn
42 import types
42
43
43 original_import = builtin_mod.__import__
44 original_import = builtin_mod.__import__
44
45
45 @contextmanager
46 @contextmanager
46 def replace_import_hook(new_import):
47 def replace_import_hook(new_import):
47 saved_import = builtin_mod.__import__
48 saved_import = builtin_mod.__import__
48 builtin_mod.__import__ = new_import
49 builtin_mod.__import__ = new_import
49 try:
50 try:
50 yield
51 yield
51 finally:
52 finally:
52 builtin_mod.__import__ = saved_import
53 builtin_mod.__import__ = saved_import
53
54
54 def get_parent(globals, level):
55 def get_parent(globals, level):
55 """
56 """
56 parent, name = get_parent(globals, level)
57 parent, name = get_parent(globals, level)
57
58
58 Return the package that an import is being performed in. If globals comes
59 Return the package that an import is being performed in. If globals comes
59 from the module foo.bar.bat (not itself a package), this returns the
60 from the module foo.bar.bat (not itself a package), this returns the
60 sys.modules entry for foo.bar. If globals is from a package's __init__.py,
61 sys.modules entry for foo.bar. If globals is from a package's __init__.py,
61 the package's entry in sys.modules is returned.
62 the package's entry in sys.modules is returned.
62
63
63 If globals doesn't come from a package or a module in a package, or a
64 If globals doesn't come from a package or a module in a package, or a
64 corresponding entry is not found in sys.modules, None is returned.
65 corresponding entry is not found in sys.modules, None is returned.
65 """
66 """
66 orig_level = level
67 orig_level = level
67
68
68 if not level or not isinstance(globals, dict):
69 if not level or not isinstance(globals, dict):
69 return None, ''
70 return None, ''
70
71
71 pkgname = globals.get('__package__', None)
72 pkgname = globals.get('__package__', None)
72
73
73 if pkgname is not None:
74 if pkgname is not None:
74 # __package__ is set, so use it
75 # __package__ is set, so use it
75 if not hasattr(pkgname, 'rindex'):
76 if not hasattr(pkgname, 'rindex'):
76 raise ValueError('__package__ set to non-string')
77 raise ValueError('__package__ set to non-string')
77 if len(pkgname) == 0:
78 if len(pkgname) == 0:
78 if level > 0:
79 if level > 0:
79 raise ValueError('Attempted relative import in non-package')
80 raise ValueError('Attempted relative import in non-package')
80 return None, ''
81 return None, ''
81 name = pkgname
82 name = pkgname
82 else:
83 else:
83 # __package__ not set, so figure it out and set it
84 # __package__ not set, so figure it out and set it
84 if '__name__' not in globals:
85 if '__name__' not in globals:
85 return None, ''
86 return None, ''
86 modname = globals['__name__']
87 modname = globals['__name__']
87
88
88 if '__path__' in globals:
89 if '__path__' in globals:
89 # __path__ is set, so modname is already the package name
90 # __path__ is set, so modname is already the package name
90 globals['__package__'] = name = modname
91 globals['__package__'] = name = modname
91 else:
92 else:
92 # Normal module, so work out the package name if any
93 # Normal module, so work out the package name if any
93 lastdot = modname.rfind('.')
94 lastdot = modname.rfind('.')
94 if lastdot < 0 < level:
95 if lastdot < 0 < level:
95 raise ValueError("Attempted relative import in non-package")
96 raise ValueError("Attempted relative import in non-package")
96 if lastdot < 0:
97 if lastdot < 0:
97 globals['__package__'] = None
98 globals['__package__'] = None
98 return None, ''
99 return None, ''
99 globals['__package__'] = name = modname[:lastdot]
100 globals['__package__'] = name = modname[:lastdot]
100
101
101 dot = len(name)
102 dot = len(name)
102 for x in range(level, 1, -1):
103 for x in range(level, 1, -1):
103 try:
104 try:
104 dot = name.rindex('.', 0, dot)
105 dot = name.rindex('.', 0, dot)
105 except ValueError:
106 except ValueError:
106 raise ValueError("attempted relative import beyond top-level "
107 raise ValueError("attempted relative import beyond top-level "
107 "package")
108 "package")
108 name = name[:dot]
109 name = name[:dot]
109
110
110 try:
111 try:
111 parent = sys.modules[name]
112 parent = sys.modules[name]
112 except:
113 except:
113 if orig_level < 1:
114 if orig_level < 1:
114 warn("Parent module '%.200s' not found while handling absolute "
115 warn("Parent module '%.200s' not found while handling absolute "
115 "import" % name)
116 "import" % name)
116 parent = None
117 parent = None
117 else:
118 else:
118 raise SystemError("Parent module '%.200s' not loaded, cannot "
119 raise SystemError("Parent module '%.200s' not loaded, cannot "
119 "perform relative import" % name)
120 "perform relative import" % name)
120
121
121 # We expect, but can't guarantee, if parent != None, that:
122 # We expect, but can't guarantee, if parent != None, that:
122 # - parent.__name__ == name
123 # - parent.__name__ == name
123 # - parent.__dict__ is globals
124 # - parent.__dict__ is globals
124 # If this is violated... Who cares?
125 # If this is violated... Who cares?
125 return parent, name
126 return parent, name
126
127
127 def load_next(mod, altmod, name, buf):
128 def load_next(mod, altmod, name, buf):
128 """
129 """
129 mod, name, buf = load_next(mod, altmod, name, buf)
130 mod, name, buf = load_next(mod, altmod, name, buf)
130
131
131 altmod is either None or same as mod
132 altmod is either None or same as mod
132 """
133 """
133
134
134 if len(name) == 0:
135 if len(name) == 0:
135 # completely empty module name should only happen in
136 # completely empty module name should only happen in
136 # 'from . import' (or '__import__("")')
137 # 'from . import' (or '__import__("")')
137 return mod, None, buf
138 return mod, None, buf
138
139
139 dot = name.find('.')
140 dot = name.find('.')
140 if dot == 0:
141 if dot == 0:
141 raise ValueError('Empty module name')
142 raise ValueError('Empty module name')
142
143
143 if dot < 0:
144 if dot < 0:
144 subname = name
145 subname = name
145 next = None
146 next = None
146 else:
147 else:
147 subname = name[:dot]
148 subname = name[:dot]
148 next = name[dot+1:]
149 next = name[dot+1:]
149
150
150 if buf != '':
151 if buf != '':
151 buf += '.'
152 buf += '.'
152 buf += subname
153 buf += subname
153
154
154 result = import_submodule(mod, subname, buf)
155 result = import_submodule(mod, subname, buf)
155 if result is None and mod != altmod:
156 if result is None and mod != altmod:
156 result = import_submodule(altmod, subname, subname)
157 result = import_submodule(altmod, subname, subname)
157 if result is not None:
158 if result is not None:
158 buf = subname
159 buf = subname
159
160
160 if result is None:
161 if result is None:
161 raise ImportError("No module named %.200s" % name)
162 raise ImportError("No module named %.200s" % name)
162
163
163 return result, next, buf
164 return result, next, buf
164
165
166
165 # Need to keep track of what we've already reloaded to prevent cyclic evil
167 # Need to keep track of what we've already reloaded to prevent cyclic evil
166 found_now = {}
168 found_now = {}
167
169
168 def import_submodule(mod, subname, fullname):
170 def import_submodule(mod, subname, fullname):
169 """m = import_submodule(mod, subname, fullname)"""
171 """m = import_submodule(mod, subname, fullname)"""
170 # Require:
172 # Require:
171 # if mod == None: subname == fullname
173 # if mod == None: subname == fullname
172 # else: mod.__name__ + "." + subname == fullname
174 # else: mod.__name__ + "." + subname == fullname
173
175
174 global found_now
176 global found_now
175 if fullname in found_now and fullname in sys.modules:
177 if fullname in found_now and fullname in sys.modules:
176 m = sys.modules[fullname]
178 m = sys.modules[fullname]
177 else:
179 else:
178 print('Reloading', fullname)
180 print('Reloading', fullname)
179 found_now[fullname] = 1
181 found_now[fullname] = 1
180 oldm = sys.modules.get(fullname, None)
182 oldm = sys.modules.get(fullname, None)
181
183
182 if mod is None:
184 if mod is None:
183 path = None
185 path = None
184 elif hasattr(mod, '__path__'):
186 elif hasattr(mod, '__path__'):
185 path = mod.__path__
187 path = mod.__path__
186 else:
188 else:
187 return None
189 return None
188
190
189 try:
191 try:
190 # This appears to be necessary on Python 3, because imp.find_module()
192 # This appears to be necessary on Python 3, because imp.find_module()
191 # tries to import standard libraries (like io) itself, and we don't
193 # tries to import standard libraries (like io) itself, and we don't
192 # want them to be processed by our deep_import_hook.
194 # want them to be processed by our deep_import_hook.
193 with replace_import_hook(original_import):
195 with replace_import_hook(original_import):
194 fp, filename, stuff = imp.find_module(subname, path)
196 fp, filename, stuff = imp.find_module(subname, path)
195 except ImportError:
197 except ImportError:
196 return None
198 return None
197
199
198 try:
200 try:
199 m = imp.load_module(fullname, fp, filename, stuff)
201 m = imp.load_module(fullname, fp, filename, stuff)
200 except:
202 except:
201 # load_module probably removed name from modules because of
203 # load_module probably removed name from modules because of
202 # the error. Put back the original module object.
204 # the error. Put back the original module object.
203 if oldm:
205 if oldm:
204 sys.modules[fullname] = oldm
206 sys.modules[fullname] = oldm
205 raise
207 raise
206 finally:
208 finally:
207 if fp: fp.close()
209 if fp: fp.close()
208
210
209 add_submodule(mod, m, fullname, subname)
211 add_submodule(mod, m, fullname, subname)
210
212
211 return m
213 return m
212
214
213 def add_submodule(mod, submod, fullname, subname):
215 def add_submodule(mod, submod, fullname, subname):
214 """mod.{subname} = submod"""
216 """mod.{subname} = submod"""
215 if mod is None:
217 if mod is None:
216 return #Nothing to do here.
218 return #Nothing to do here.
217
219
218 if submod is None:
220 if submod is None:
219 submod = sys.modules[fullname]
221 submod = sys.modules[fullname]
220
222
221 setattr(mod, subname, submod)
223 setattr(mod, subname, submod)
222
224
223 return
225 return
224
226
225 def ensure_fromlist(mod, fromlist, buf, recursive):
227 def ensure_fromlist(mod, fromlist, buf, recursive):
226 """Handle 'from module import a, b, c' imports."""
228 """Handle 'from module import a, b, c' imports."""
227 if not hasattr(mod, '__path__'):
229 if not hasattr(mod, '__path__'):
228 return
230 return
229 for item in fromlist:
231 for item in fromlist:
230 if not hasattr(item, 'rindex'):
232 if not hasattr(item, 'rindex'):
231 raise TypeError("Item in ``from list'' not a string")
233 raise TypeError("Item in ``from list'' not a string")
232 if item == '*':
234 if item == '*':
233 if recursive:
235 if recursive:
234 continue # avoid endless recursion
236 continue # avoid endless recursion
235 try:
237 try:
236 all = mod.__all__
238 all = mod.__all__
237 except AttributeError:
239 except AttributeError:
238 pass
240 pass
239 else:
241 else:
240 ret = ensure_fromlist(mod, all, buf, 1)
242 ret = ensure_fromlist(mod, all, buf, 1)
241 if not ret:
243 if not ret:
242 return 0
244 return 0
243 elif not hasattr(mod, item):
245 elif not hasattr(mod, item):
244 import_submodule(mod, item, buf + '.' + item)
246 import_submodule(mod, item, buf + '.' + item)
245
247
246 def deep_import_hook(name, globals=None, locals=None, fromlist=None, level=-1):
248 def deep_import_hook(name, globals=None, locals=None, fromlist=None, level=-1):
247 """Replacement for __import__()"""
249 """Replacement for __import__()"""
248 parent, buf = get_parent(globals, level)
250 parent, buf = get_parent(globals, level)
249
251
250 head, name, buf = load_next(parent, None if level < 0 else parent, name, buf)
252 head, name, buf = load_next(parent, None if level < 0 else parent, name, buf)
251
253
252 tail = head
254 tail = head
253 while name:
255 while name:
254 tail, name, buf = load_next(tail, tail, name, buf)
256 tail, name, buf = load_next(tail, tail, name, buf)
255
257
256 # If tail is None, both get_parent and load_next found
258 # If tail is None, both get_parent and load_next found
257 # an empty module name: someone called __import__("") or
259 # an empty module name: someone called __import__("") or
258 # doctored faulty bytecode
260 # doctored faulty bytecode
259 if tail is None:
261 if tail is None:
260 raise ValueError('Empty module name')
262 raise ValueError('Empty module name')
261
263
262 if not fromlist:
264 if not fromlist:
263 return head
265 return head
264
266
265 ensure_fromlist(tail, fromlist, buf, 0)
267 ensure_fromlist(tail, fromlist, buf, 0)
266 return tail
268 return tail
267
269
268 modules_reloading = {}
270 modules_reloading = {}
269
271
270 def deep_reload_hook(m):
272 def deep_reload_hook(m):
271 """Replacement for reload()."""
273 """Replacement for reload()."""
274 # Hardcode this one as it would raise a NotImplemeentedError from the
275 # bowels of Python and screw up the import machinery after.
276 # unlike other imports the `exclude` list aleady in place is not enough.
277
278 if m is types:
279 return m
272 if not isinstance(m, ModuleType):
280 if not isinstance(m, ModuleType):
273 raise TypeError("reload() argument must be module")
281 raise TypeError("reload() argument must be module")
274
282
275 name = m.__name__
283 name = m.__name__
276
284
277 if name not in sys.modules:
285 if name not in sys.modules:
278 raise ImportError("reload(): module %.200s not in sys.modules" % name)
286 raise ImportError("reload(): module %.200s not in sys.modules" % name)
279
287
280 global modules_reloading
288 global modules_reloading
281 try:
289 try:
282 return modules_reloading[name]
290 return modules_reloading[name]
283 except:
291 except:
284 modules_reloading[name] = m
292 modules_reloading[name] = m
285
293
286 dot = name.rfind('.')
294 dot = name.rfind('.')
287 if dot < 0:
295 if dot < 0:
288 subname = name
296 subname = name
289 path = None
297 path = None
290 else:
298 else:
291 try:
299 try:
292 parent = sys.modules[name[:dot]]
300 parent = sys.modules[name[:dot]]
293 except KeyError:
301 except KeyError:
294 modules_reloading.clear()
302 modules_reloading.clear()
295 raise ImportError("reload(): parent %.200s not in sys.modules" % name[:dot])
303 raise ImportError("reload(): parent %.200s not in sys.modules" % name[:dot])
296 subname = name[dot+1:]
304 subname = name[dot+1:]
297 path = getattr(parent, "__path__", None)
305 path = getattr(parent, "__path__", None)
298
306
299 try:
307 try:
300 # This appears to be necessary on Python 3, because imp.find_module()
308 # This appears to be necessary on Python 3, because imp.find_module()
301 # tries to import standard libraries (like io) itself, and we don't
309 # tries to import standard libraries (like io) itself, and we don't
302 # want them to be processed by our deep_import_hook.
310 # want them to be processed by our deep_import_hook.
303 with replace_import_hook(original_import):
311 with replace_import_hook(original_import):
304 fp, filename, stuff = imp.find_module(subname, path)
312 fp, filename, stuff = imp.find_module(subname, path)
305 finally:
313 finally:
306 modules_reloading.clear()
314 modules_reloading.clear()
307
315
308 try:
316 try:
309 newm = imp.load_module(name, fp, filename, stuff)
317 newm = imp.load_module(name, fp, filename, stuff)
310 except:
318 except:
311 # load_module probably removed name from modules because of
319 # load_module probably removed name from modules because of
312 # the error. Put back the original module object.
320 # the error. Put back the original module object.
313 sys.modules[name] = m
321 sys.modules[name] = m
314 raise
322 raise
315 finally:
323 finally:
316 if fp: fp.close()
324 if fp: fp.close()
317
325
318 modules_reloading.clear()
326 modules_reloading.clear()
319 return newm
327 return newm
320
328
321 # Save the original hooks
329 # Save the original hooks
322 original_reload = imp.reload # Python 3
330 original_reload = imp.reload
323
331
324 # Replacement for reload()
332 # Replacement for reload()
325 def reload(module, exclude=('sys', 'os.path', 'builtins', '__main__',
333 def reload(module, exclude=('sys', 'os.path', 'builtins', '__main__',
326 'numpy', 'numpy._globals')):
334 'numpy', 'numpy._globals')):
327 """Recursively reload all modules used in the given module. Optionally
335 """Recursively reload all modules used in the given module. Optionally
328 takes a list of modules to exclude from reloading. The default exclude
336 takes a list of modules to exclude from reloading. The default exclude
329 list contains sys, __main__, and __builtin__, to prevent, e.g., resetting
337 list contains sys, __main__, and __builtin__, to prevent, e.g., resetting
330 display, exception, and io hooks.
338 display, exception, and io hooks.
331 """
339 """
332 global found_now
340 global found_now
333 for i in exclude:
341 for i in exclude:
334 found_now[i] = 1
342 found_now[i] = 1
335 try:
343 try:
336 with replace_import_hook(deep_import_hook):
344 with replace_import_hook(deep_import_hook):
337 return deep_reload_hook(module)
345 return deep_reload_hook(module)
338 finally:
346 finally:
339 found_now = {}
347 found_now = {}
General Comments 0
You need to be logged in to leave comments. Login now