assignhooks
allows you to instrument your python code so that assignments to a variable can execute an action before and/or after the fact. For instance:
class SampleClass():
name = ''
def __assignpre__(self, lhs_name, rhs_name, rhs):
print('PRE: assigning %s = %s' % (lhs_name, rhs_name))
# modify rhs if needed before assignment
rhs.name = lhs_name
return rhs
def __assignpost__(self, lhs_name, rhs_name):
print('POST: lhs', self)
print('POST: lhs_name', lhs_name)
print('POST: rhs_name', rhs_name)
print('POST: assigning %s = %s' % (lhs_name, rhs_name))
self.name = lhs_name
b = SampleClass()
c = b
The first assignment would generate a call to __assignpost__ as b still has no contents so there is no information on its class and hence no way to find out if it has an __assignpre__ method. __assignpost__ allows b to find out its name in this case. In the example above b contains a SampleClass instance whose name is initialized to b (the variable name)
In the second assignment, b is being assigned on the right-hand-side. As b is already existing we can check if its class provides an __assignpre__ implementation. b knows is being assigned before the fact so we can used __assignpre__ to check on the assignment or to provide the suitable value to be assigned (the return from __assignpre__ method)
- Can be used to add automatically names to created objects, for debug for instance:
class MyClass:
def __init__(self, val, name=None):
self.val = val
self.name = name
def __assignpost__(self, lhs_name, rhs_name):
if self.name is None:
self.name = lhs_name
# will assign x.name as 'x' automatically
x = MyClass(val=4)
assert x.name == 'x'
- Can be used to control what gets assigned when a specific instance of a class is assigned
Checkout test.py
and testmod.py
under examples directory. test.py
contains an example of how to instrument and use testmod.py
module (the module being object of instrumentation).
- Automatic patch
Suppose that there is a testmod.py that contains a module to instrument. Importing 'assignhooks' gives access to 'assignhooks.instrument.start()'. It should be called before the imports you want to instrument. To stop this behavior invoke assignhooks.instrument.stop()
Assuming testmod.py contains:
<testmod.py>
class T():
def __assignpre__(self, lhs_name, rhs_name, rhs):
print('%s has been copied to %s' % (rhs_name, lhs_name))
return rhs
b = T()
c = b
And <test.py>
import assignhooks
assignhooks.instrument.start() # instrument from now on
import testmod
assignhooks.instrument.stop() # stop instrumenting
If executed:
$ python3 ./test.py
will produce
b has been copied to c
- manually patch
from assignhooks.patch import patch_module
import testmod
patch_module(test)
Just do:
pip3 install assignhooks
Internally assignhooks.magic replaces __import__ by a new version that after the import patches the module AST tree. It performs the following transformations:
x = T()
gets replaced by:
if True:
x = T()
if hasattr(x, '__assignpost__'):
x.__assignpost__('x', 'T')
and
x = y
gets replaced by:
if True:
if hasattr(y, '__assignpre__'):
x = y.__assignpre__('x', 'y', y)
else:
x = y
if hasattr(x, '__assignpost__'):
x.__assignpost__('x', 'y')
NOTE: that if True:
is only used to group the statements while doing node replacements in the AST.
- Tested with Python 3.8.7 on MacOS
Based on original code from assign module from ryankung. Mainly adapted for my needs to handle object creation. Changes include:
- Original had an
__assign__
overload only valid on existing objects (like__assignpre__
). Added__assignpost__
to handle object creation cases - Renamed
__assign__
to__assignpre__
and added extra parameters
- Won't work under REPL
- May slow down import operation. The effect in run-time is the replacement of raw assignments with conditional + assignment + hook
- May fail when patching standard modules