Package: lazex
lazex(short for "lazy expressions") is a small library that enables your
functions to customize how arguments are given to it. For example, such a
functionfoothat you call with an expression, sayfoo(2 + 3)does not just
get the value5as a parameter, but knows the 5 came from adding2and3.
In fact, it can even prevent evaluation of this expression and do whatever it
wants with it.In order to use this, you need to import the library:import lazexNext, decorate your functions with the decoratorlazex.me. This is an
identity function (the equivalent oflambda x: x):@lazex.medeffoo(something):returnsomething.evaluate()A more realistic function might inspect the arguments and tell you how the
result was obtained:@latex.medefexplain(expr):returnf"The answer to{expr.escaped}is [expr.evaluate()}"If you now callexplain(2+3), it will return you the string "The answer to (2
+ 3) is 5".Here's the catch: In order for this to work, the calling function mustalsohave been decorated withlatex.me. This is sadly a consequence of the fact
that what we're doing here is not usually possible in Python.Even without this decorator, you can still uselazexto define expressions in
a given context that you may or may not want to evaluate later:defouter():x=7y=lazex.Expression('x * 14')inner(y)definner(expr):importrandomifrandom.random()>0.5:print(expr.evaluate())While this toy example may seem silly, you can wrap arbitrarily complexexpressions. For example, you could delay a complex function call that might need to occur:id_='1274897d129d24'doc=complex.framework.get_document_by_id(id_,reticulate_splines='force')delete=lazex.Expression('complex.framework.delete_from_db(''doc, id_, safe_delete=True, cleanup=False,''restore_function=complex.framework.restore_with_trick).confirmation_id')process_document(doc,deleteexpr=delete)Here, theprocess_documentfunction may at some point decide that it needs to
delete the document, and can just calldelete.evaluate(). Sure, this is also
solvable with a simple lambda, but if you were looking for straightforward,
idiomatic solutions you've come to the wrong place.Technical detailsIf you want to know how this works, just look atlazex.py, it's barely more
than a hundred lines. Here's the gist:Register all functions decorated bylazex.meas magical lazex functions,
and replace them with a wrapper.When the wrapper is run, we check if we have already patched the function. If
so, we just call the original function with the given arguments.Otherwise, we patch it:First, we useinspect.getsource()to get the original source code of
the function.Next, we parse the source code withast.parse(). We then walk over all
nodes of the AST, until we find anast.Call(a function call):We use theastunparselibrary to get a string representation
of all expressions that are given as positional and keyword arguments,
and replace the corresponding nodes with yet anotherast.Callnode that
will calllazex.build_expression.Effectively, this will replace a call likefoo(1 + 2, x, bar=bat)withfoo(build_expression("foo", "1 + 2"), build_expression("foo", "x"), bar=build_expression("foo", "bat")).The modified AST is then fed toastunparseto generate source code,
that source code is executed usingcompile()and the resulting code
object is set as the new.__code__attribute of the original function.We then execute the patched function with the original arguments. When we
reach a call tobuild_expression, we useinspect.currentframe().f_backto
get a reference to thepreviousexecution frame, and use it to construct anExpression. The frame helps us retrieve its local and global namespace (the.f_localsand.f_globalsattributes on the frame object).After saving these namespaces on ourExpressioninstance, we can now use it
to access the original source code, as well as the AST for this specific
expression. When we want to evaluate the expression, we can use the
namespaces withevalin order to produce the correct values.After having created theExpressionobjects, we then evaluate the name of
the function that is supposed to be called with (possibly) escaped arguments.
If it isnota registered lazex function, we evaluate the arguments
immediately.Also, we search theExpression's AST for ourbuild_expressionhooks and
remove them: They are unnecessary here and just leak implementation details.