Allow functions to examine and modify the AST of their arguments
Project description
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
function foo
that you call with an expression, say foo(2 + 3)
does not just
get the value 5
as a parameter, but knows the 5 came from adding 2
and 3
.
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 lazex
Next, decorate your functions with the decorator lazex.me
. This is an
identity function (the equivalent of lambda x: x
):
@lazex.me
def foo(something):
return something.evaluate()
A more realistic function might inspect the arguments and tell you how the result was obtained:
@latex.me
def explain(expr):
return f"The answer to {expr.escaped} is [expr.evaluate()}"
If you now call explain(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 must also
have been decorated with latex.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 use lazex
to define expressions in
a given context that you may or may not want to evaluate later:
def outer():
x = 7
y = lazex.Expression('x * 14')
inner(y)
def inner(expr):
import random
if random.random() > 0.5:
print(expr.evaluate())
While this toy example may seem silly, you can wrap arbitrarily complex expressions. 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, the process_document
function may at some point decide that it needs to
delete the document, and can just call delete.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 details
If you want to know how this works, just look at lazex.py
, it's barely more
than a hundred lines. Here's the gist:
- Register all functions decorated by
lazex.me
as 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 use
inspect.getsource()
to get the original source code of the function. - Next, we parse the source code with
ast.parse()
. We then walk over all nodes of the AST, until we find anast.Call
(a function call): - We use the
astunparse
library to get a string representation of all expressions that are given as positional and keyword arguments, and replace the corresponding nodes with yet anotherast.Call
node that will calllazex.build_expression
. - Effectively, this will replace a call like
foo(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 to
astunparse
to 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.
- First, we use
- We then execute the patched function with the original arguments. When we
reach a call to
build_expression
, we useinspect.currentframe().f_back
to get a reference to the previous execution frame, and use it to construct anExpression
. The frame helps us retrieve its local and global namespace (the.f_locals
and.f_globals
attributes on the frame object). - After saving these namespaces on our
Expression
instance, 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 witheval
in order to produce the correct values. - After having created the
Expression
objects, we then evaluate the name of the function that is supposed to be called with (possibly) escaped arguments. If it is not a registered lazex function, we evaluate the arguments immediately. - Also, we search the
Expression
's AST for ourbuild_expression
hooks and remove them: They are unnecessary here and just leak implementation details.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file lazex-0.4.1.tar.gz
.
File metadata
- Download URL: lazex-0.4.1.tar.gz
- Upload date:
- Size: 5.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.1.11 CPython/3.10.0 Darwin/21.6.0
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | f82ab53d81acf9b80c0c9296d1d0e938f485099475e3e6566ea52015ecdc403c |
|
MD5 | 6caca5986917076a00820a1eeabd128a |
|
BLAKE2b-256 | 9d5dd2625cdfe3808aeff5260d73df6ca0a2daed353a81539e2fefc3c6cd7e9c |
File details
Details for the file lazex-0.4.1-py3-none-any.whl
.
File metadata
- Download URL: lazex-0.4.1-py3-none-any.whl
- Upload date:
- Size: 4.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.1.11 CPython/3.10.0 Darwin/21.6.0
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | f4314f6c8bfae23c70a0b89a33d7ace064b65d7500821e4930044dd1b62755ea |
|
MD5 | 45db00c82419c73f152c55270084ec32 |
|
BLAKE2b-256 | 6ca52911464c05c1963e3199f82eb91f24317e50db61dd51fac0799b0bfae5cd |