_livepatch module
livepatch/xreload: Alternative to reload().
xreload performs a “live patch” of the modules/classes/functions/etc that have already been loaded in memory. It does so by executing the module in a scratch namespace, and then patching classes, methods and functions in-place. New objects are copied into the target namespace.
This addresses cases where one module imported functions from another module.
For example, suppose m1.py contains:
from m2 import foo
def print_foo():
return foo()
and m2.py contains:
def foo():
return 42
If you edit m2.py and modify foo
, then reload(m2) on its own would not do
what you want. You would also need to reload(m1) after reload(m2). This is
because the built-in reload affects the module being reloaded, but references
to the old module remain. On the other hand, xreload() patches the existing
m2.foo, so that live references to it are updated.
In table form:
Undesired effect: reload(m2)
Undesired effect: reload(m1); reload(m2)
Desired effect: reload(m2); reload(m1)
Desired effect: xreload(m2)
Desired effect: xreload(m1); xreload(m2)
Desired effect: xreload(m2); xreload(m1)
Even with just two modules, we can see that xreload() is an improvement. When working with a large set of interdependent modules, it becomes infeasible to know the precise sequence of reload() calls that would be necessary. xreload() really shines in that case.
This implementation of xreload() was originally based the following mailing-list post by Guido van Rossum:
Customizing behavior
If a class/function/module/etc has an attribute __livepatch__, then this function is called instead of performing the regular livepatch mechanism.
The __livepatch__() function is called with the following arguments:
old
: The object to be updated with contents ofnew
new
: The object whose contents to put intoold
do_livepatch
: A function that can be called to do the standardlivepatch, replacing the contents of
old
withnew
. If it’s not possible to livepatchold
, it returnsnew
. Thedo_livepatch
function takes no arguments. Calling thedo_livepatch
function is roughly equivalent to callingpyflyby.livepatch(old, new, modname=modname, heed_hook=False)
.
modname
The module currently being updated. Recursively calledupdates should keep track of the module being updated to avoid touching other modules.
These arguments are matched by name and are passed only if the
__livepatch__
function is declared to take such named arguments or it takes
**kwargs. If the __livepatch__
function takes **kwargs, it should ignore
unknown arguments, in case new parameters are added in the future.
If the object being updated is an object instance, and __livepatch__
is a
method, then the function is bound to the new object, i.e. the self
parameter is the same as new
.
If the __livepatch__
function successfully patched the old
object, then
it should return old
. If it is unable to patch, it should return new
.
Examples
By default, any attributes on an existing function are updated with ones from the new function. If you want a memoized function to keep its cache across xreload, you could implement that like this:
def memoize(function):
cache = {}
def wrapped_fn(*args):
try:
return cache[args]
except KeyError:
result = function(*args)
cache[args] = result
return result
wrapped_fn.cache = cache
def my_livepatch(old, new, do_livepatch):
keep_cache = dict(old.cache)
result = do_livepatch()
result.cache.update(keep_cache)
return result
wrapped_fn.__livepatch__ = my_livepatch
return wrapped_fn
XXX change example b/c cache is already cleared by default XXX maybe global cache
- class MyObj(…):
- def __livepatch__(self, old):
self.__dict__.update(old.__dict__) return self
- class MyObj(…):
- def __init__(self):
self._my_cache = {}
- def __livepatch__(self, old, do_livepatch):
keep_cache = dict(old._my_cache) result = do_livepatch() result._my_cache.update(keep_cache) return result
XXX test
- exception pyflyby._livepatch.UnknownModuleError
- pyflyby._livepatch._format_age(t)
- pyflyby._livepatch._get_definition_module(obj)
Get the name of the module that an object is defined in, or
None
if unknown.For classes and functions, this returns the
__module__
attribute.For object instances, this returns
None
, ignoring the__module__
attribute. The reason is that the__module__
attribute on an instance just gives the module that the class was defined in, which is not necessarily the module where the instance was constructed.- Return type:
str
- pyflyby._livepatch._get_module_py_file(module)
- pyflyby._livepatch._interpret_module(arg)
- pyflyby._livepatch._livepatch__class(oldclass, newclass, modname, cache, visit_stack)
Livepatch a class.
This is similar to _livepatch__dict(oldclass.__dict__, newclass.__dict__). However, we can’t just operate on the dict, because class dictionaries are special objects that don’t allow setitem, even though we can setattr on the class.
- pyflyby._livepatch._livepatch__dict(old_dict, new_dict, modname, cache, visit_stack)
Livepatch a dict.
- pyflyby._livepatch._livepatch__function(old_func, new_func, modname, cache, visit_stack)
Livepatch a function.
- pyflyby._livepatch._livepatch__method(old_method, new_method, modname, cache, visit_stack)
Livepatch a method.
- pyflyby._livepatch._livepatch__module(old_mod, new_mod, modname, cache, visit_stack)
Livepatch a module.
- pyflyby._livepatch._livepatch__object(oldobj, newobj, modname, cache, visit_stack)
Livepatch a general object.
- pyflyby._livepatch._livepatch__setattr(oldobj, newobj, name, modname, cache, visit_stack)
Livepatch something via setattr, i.e.:
oldobj.{name} = livepatch(oldobj.{name}, newobj.{name}, ...)
- pyflyby._livepatch._xreload_module(module, filename, force=False)
Reload a module in place, using livepatch.
- Parameters:
module (
ModuleType
) – Module to reload.force – Whether to reload even if the module has not been modified since the previous load. If
False
, then do nothing. IfTrue
, then reload.