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 |
|
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,21 +309,21 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 | |
371 |
|
321 | |||
372 |
|
322 | |||
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