##// END OF EJS Templates
Combined recursive approach with check for already visited objects to avoid infinite recursion....
Niclas -
Show More
@@ -268,66 +268,14 b' def update_function(old, new):'
268 pass
268 pass
269
269
270
270
271 def _find_instances(old_type):
271 def update_instances(old, new, objects=None, visited={}):
272 """Try to find all instances of a class that need updating.
273
274 Classic graph exploration, we want to avoid re-visiting object multiple times.
275 """
276 # find ipython workspace stack frame, this is just to bootstrap where we
277 # find the object that need updating.
278 frame = next(frame_nfo.frame for frame_nfo in inspect.stack()
279 if 'trigger' in frame_nfo.function)
280 # build generator for non-private variable values from workspace
281 shell = frame.f_locals['self'].shell
282 user_ns = shell.user_ns
283 user_ns_hidden = shell.user_ns_hidden
284 nonmatching = object()
285 objects = ( value for key, value in user_ns.items()
286 if not key.startswith('_')
287 and (value is not user_ns_hidden.get(key, nonmatching))
288 and not inspect.ismodule(value))
289
290 # note: in the following we do use dict as object might not be hashable.
291 # list of objects we found that will need an update.
292 to_update = {}
293
294 # list of object we have not recursed into yet
295 open_set = {}
296
297 # list of object we have visited already
298 closed_set = {}
299
300 open_set.update({id(o):o for o in objects})
301
302 it = 0
303 while len(open_set) > 0:
304 it += 1
305 if it > 100_000:
306 raise ValueError('infinite')
307 (current_id,current) = next(iter(open_set.items()))
308 if type(current) is old_type:
309 to_update[current_id] = current
310 if hasattr(current, '__dict__') and not (inspect.isfunction(current)
311 or inspect.ismethod(current)):
312 potential_new = {id(o):o for o in current.__dict__.values() if id(o) not in closed_set.keys()}
313 open_set.update(potential_new)
314 # if object is a container, search it
315 if hasattr(current, 'items') or (hasattr(current, '__contains__')
316 and not isinstance(current, str)):
317 potential_new = (value for key, value in current.items()
318 if not str(key).startswith('_')
319 and not inspect.ismodule(value) and not id(value) in closed_set.keys())
320 open_set.update(potential_new)
321 del open_set[id(current)]
322 closed_set[id(current)] = current
323 return to_update.values()
324
325 def update_instances(old, new, objects=None):
326 """Iterate through objects recursively, searching for instances of old and
272 """Iterate through objects recursively, searching for instances of old and
327 replace their __class__ reference with new. If no objects are given, start
273 replace their __class__ reference with new. If no objects are given, start
328 with the current ipython workspace.
274 with the current ipython workspace.
329 """
275 """
330 if not objects:
276 if objects is None:
277 # make sure visited is cleaned when not called recursively
278 visited = {}
331 # find ipython workspace stack frame
279 # find ipython workspace stack frame
332 frame = next(frame_nfo.frame for frame_nfo in inspect.stack()
280 frame = next(frame_nfo.frame for frame_nfo in inspect.stack()
333 if 'trigger' in frame_nfo.function)
281 if 'trigger' in frame_nfo.function)
@@ -349,7 +297,9 b' def update_instances(old, new, objects=None):'
349
297
350 # try if objects is iterable
298 # try if objects is iterable
351 try:
299 try:
352 for obj in objects:
300 for obj in (obj for obj in objects if id(obj) not in visited):
301 # add current object to visited to avoid revisiting
302 visited.update({id(obj):obj})
353
303
354 # update, if object is instance of old_class (but no subclasses)
304 # update, if object is instance of old_class (but no subclasses)
355 if type(obj) is old:
305 if type(obj) is old:
@@ -359,12 +309,12 b' def update_instances(old, new, objects=None):'
359 # if object is instance of other class, look for nested instances
309 # if object is instance of other class, look for nested instances
360 if hasattr(obj, '__dict__') and not (inspect.isfunction(obj)
310 if hasattr(obj, '__dict__') and not (inspect.isfunction(obj)
361 or inspect.ismethod(obj)):
311 or inspect.ismethod(obj)):
362 update_instances(old, new, obj.__dict__)
312 update_instances(old, new, obj.__dict__, visited)
363
313
364 # if object is a container, search it
314 # if object is a container, search it
365 if hasattr(obj, 'items') or (hasattr(obj, '__contains__')
315 if hasattr(obj, 'items') or (hasattr(obj, '__contains__')
366 and not isinstance(obj, str)):
316 and not isinstance(obj, str)):
367 update_instances(old, new, obj)
317 update_instances(old, new, obj, visited)
368
318
369 except TypeError:
319 except TypeError:
370 pass
320 pass
@@ -373,7 +323,7 b' def update_instances(old, new, objects=None):'
373 def update_class(old, new):
323 def update_class(old, new):
374 """Replace stuff in the __dict__ of a class, and upgrade
324 """Replace stuff in the __dict__ of a class, and upgrade
375 method code objects, and add new methods, if any"""
325 method code objects, and add new methods, if any"""
376 print('old is', old)
326 print('old is', id(old))
377 for key in list(old.__dict__.keys()):
327 for key in list(old.__dict__.keys()):
378 old_obj = getattr(old, key)
328 old_obj = getattr(old, key)
379 try:
329 try:
@@ -405,8 +355,7 b' def update_class(old, new):'
405 pass # skip non-writable attributes
355 pass # skip non-writable attributes
406
356
407 # update all instances of class
357 # update all instances of class
408 for instance in _find_instances(old):
358 update_instances(old, new)
409 instance.__class__ = new
410
359
411
360
412 def update_property(old, new):
361 def update_property(old, new):
General Comments 0
You need to be logged in to leave comments. Login now