mirror of
				https://github.com/python/cpython.git
				synced 2025-11-04 11:49:12 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			190 lines
		
	
	
	
		
			6.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			190 lines
		
	
	
	
		
			6.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
"""Alternative to reload().
 | 
						|
 | 
						|
This works by executing the module in a scratch namespace, and then
 | 
						|
patching classes, methods and functions in place.  This avoids the
 | 
						|
need to patch instances.  New objects are copied into the target
 | 
						|
namespace.
 | 
						|
 | 
						|
Some of the many limitiations include:
 | 
						|
 | 
						|
- Global mutable objects other than classes are simply replaced, not patched
 | 
						|
 | 
						|
- Code using metaclasses is not handled correctly
 | 
						|
 | 
						|
- Code creating global singletons is not handled correctly
 | 
						|
 | 
						|
- Functions and methods using decorators (other than classmethod and
 | 
						|
  staticmethod) is not handled correctly
 | 
						|
 | 
						|
- Renamings are not handled correctly
 | 
						|
 | 
						|
- Dependent modules are not reloaded
 | 
						|
 | 
						|
- When a dependent module contains 'from foo import bar', and
 | 
						|
  reloading foo deletes foo.bar, the dependent module continues to use
 | 
						|
  the old foo.bar object rather than failing
 | 
						|
 | 
						|
- Frozen modules and modules loaded from zip files aren't handled
 | 
						|
  correctly
 | 
						|
 | 
						|
- Classes involving __slots__ are not handled correctly
 | 
						|
"""
 | 
						|
 | 
						|
import imp
 | 
						|
import sys
 | 
						|
import types
 | 
						|
 | 
						|
 | 
						|
def xreload(mod):
 | 
						|
    """Reload a module in place, updating classes, methods and functions.
 | 
						|
 | 
						|
    Args:
 | 
						|
      mod: a module object
 | 
						|
 | 
						|
    Returns:
 | 
						|
      The (updated) input object itself.
 | 
						|
    """
 | 
						|
    # Get the module name, e.g. 'foo.bar.whatever'
 | 
						|
    modname = mod.__name__
 | 
						|
    # Get the module namespace (dict) early; this is part of the type check
 | 
						|
    modns = mod.__dict__
 | 
						|
    # Parse it into package name and module name, e.g. 'foo.bar' and 'whatever'
 | 
						|
    i = modname.rfind(".")
 | 
						|
    if i >= 0:
 | 
						|
        pkgname, modname = modname[:i], modname[i+1:]
 | 
						|
    else:
 | 
						|
        pkgname = None
 | 
						|
    # Compute the search path
 | 
						|
    if pkgname:
 | 
						|
        # We're not reloading the package, only the module in it
 | 
						|
        pkg = sys.modules[pkgname]
 | 
						|
        path = pkg.__path__  # Search inside the package
 | 
						|
    else:
 | 
						|
        # Search the top-level module path
 | 
						|
        pkg  = None
 | 
						|
        path = None  # Make find_module() uses the default search path
 | 
						|
    # Find the module; may raise ImportError
 | 
						|
    (stream, filename, (suffix, mode, kind)) = imp.find_module(modname, path)
 | 
						|
    # Turn it into a code object
 | 
						|
    try:
 | 
						|
        # Is it Python source code or byte code read from a file?
 | 
						|
        if kind not in (imp.PY_COMPILED, imp.PY_SOURCE):
 | 
						|
            # Fall back to built-in reload()
 | 
						|
            return reload(mod)
 | 
						|
        if kind == imp.PY_SOURCE:
 | 
						|
            source = stream.read()
 | 
						|
            code = compile(source, filename, "exec")
 | 
						|
        else:
 | 
						|
            code = marshal.load(stream)
 | 
						|
    finally:
 | 
						|
        if stream:
 | 
						|
            stream.close()
 | 
						|
    # Execute the code.  We copy the module dict to a temporary; then
 | 
						|
    # clear the module dict; then execute the new code in the module
 | 
						|
    # dict; then swap things back and around.  This trick (due to
 | 
						|
    # Glyph Lefkowitz) ensures that the (readonly) __globals__
 | 
						|
    # attribute of methods and functions is set to the correct dict
 | 
						|
    # object.
 | 
						|
    tmpns = modns.copy()
 | 
						|
    modns.clear()
 | 
						|
    modns["__name__"] = tmpns["__name__"]
 | 
						|
    exec(code, modns)
 | 
						|
    # Now we get to the hard part
 | 
						|
    oldnames = set(tmpns)
 | 
						|
    newnames = set(modns)
 | 
						|
    # Update attributes in place
 | 
						|
    for name in oldnames & newnames:
 | 
						|
        modns[name] = _update(tmpns[name], modns[name])
 | 
						|
    # Done!
 | 
						|
    return mod
 | 
						|
 | 
						|
 | 
						|
def _update(oldobj, newobj):
 | 
						|
    """Update oldobj, if possible in place, with newobj.
 | 
						|
 | 
						|
    If oldobj is immutable, this simply returns newobj.
 | 
						|
 | 
						|
    Args:
 | 
						|
      oldobj: the object to be updated
 | 
						|
      newobj: the object used as the source for the update
 | 
						|
 | 
						|
    Returns:
 | 
						|
      either oldobj, updated in place, or newobj.
 | 
						|
    """
 | 
						|
    if oldobj is newobj:
 | 
						|
        # Probably something imported
 | 
						|
        return newobj
 | 
						|
    if type(oldobj) is not type(newobj):
 | 
						|
        # Cop-out: if the type changed, give up
 | 
						|
        return newobj
 | 
						|
    if hasattr(newobj, "__reload_update__"):
 | 
						|
        # Provide a hook for updating
 | 
						|
        return newobj.__reload_update__(oldobj)
 | 
						|
    if isinstance(newobj, types.ClassType):
 | 
						|
        return _update_class(oldobj, newobj)
 | 
						|
    if isinstance(newobj, types.FunctionType):
 | 
						|
        return _update_function(oldobj, newobj)
 | 
						|
    if isinstance(newobj, types.MethodType):
 | 
						|
        return _update_method(oldobj, newobj)
 | 
						|
    if isinstance(newobj, classmethod):
 | 
						|
        return _update_classmethod(oldobj, newobj)
 | 
						|
    if isinstance(newobj, staticmethod):
 | 
						|
        return _update_staticmethod(oldobj, newobj)
 | 
						|
    # Not something we recognize, just give up
 | 
						|
    return newobj
 | 
						|
 | 
						|
 | 
						|
# All of the following functions have the same signature as _update()
 | 
						|
 | 
						|
 | 
						|
def _update_function(oldfunc, newfunc):
 | 
						|
    """Update a function object."""
 | 
						|
    oldfunc.__doc__ = newfunc.__doc__
 | 
						|
    oldfunc.__dict__.update(newfunc.__dict__)
 | 
						|
    oldfunc.__code__ = newfunc.__code__
 | 
						|
    oldfunc.__defaults__ = newfunc.__defaults__
 | 
						|
    return oldfunc
 | 
						|
 | 
						|
 | 
						|
def _update_method(oldmeth, newmeth):
 | 
						|
    """Update a method object."""
 | 
						|
    # XXX What if im_func is not a function?
 | 
						|
    _update(oldmeth.im_func, newmeth.im_func)
 | 
						|
    return oldmeth
 | 
						|
 | 
						|
 | 
						|
def _update_class(oldclass, newclass):
 | 
						|
    """Update a class object."""
 | 
						|
    olddict = oldclass.__dict__
 | 
						|
    newdict = newclass.__dict__
 | 
						|
    oldnames = set(olddict)
 | 
						|
    newnames = set(newdict)
 | 
						|
    for name in newnames - oldnames:
 | 
						|
        setattr(oldclass, name, newdict[name])
 | 
						|
    for name in oldnames - newnames:
 | 
						|
        delattr(oldclass, name)
 | 
						|
    for name in oldnames & newnames - {"__dict__", "__doc__"}:
 | 
						|
        setattr(oldclass, name,  _update(olddict[name], newdict[name]))
 | 
						|
    return oldclass
 | 
						|
 | 
						|
 | 
						|
def _update_classmethod(oldcm, newcm):
 | 
						|
    """Update a classmethod update."""
 | 
						|
    # While we can't modify the classmethod object itself (it has no
 | 
						|
    # mutable attributes), we *can* extract the underlying function
 | 
						|
    # (by calling __get__(), which returns a method object) and update
 | 
						|
    # it in-place.  We don't have the class available to pass to
 | 
						|
    # __get__() but any object except None will do.
 | 
						|
    _update(oldcm.__get__(0), newcm.__get__(0))
 | 
						|
    return newcm
 | 
						|
 | 
						|
 | 
						|
def _update_staticmethod(oldsm, newsm):
 | 
						|
    """Update a staticmethod update."""
 | 
						|
    # While we can't modify the staticmethod object itself (it has no
 | 
						|
    # mutable attributes), we *can* extract the underlying function
 | 
						|
    # (by calling __get__(), which returns it) and update it in-place.
 | 
						|
    # We don't have the class available to pass to __get__() but any
 | 
						|
    # object except None will do.
 | 
						|
    _update(oldsm.__get__(0), newsm.__get__(0))
 | 
						|
    return newsm
 |