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.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 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
astunparselibrary 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 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
astunparseto 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_backto 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_localsand.f_globalsattributes on the frame object). - After saving these namespaces on our
Expressioninstance, 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 the
Expressionobjects, 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_expressionhooks and remove them: They are unnecessary here and just leak implementation details.
Metadata
Release files for lazex 0.4.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| lazex-0.4.1.tar.gz | 5.3 kB | Without provenance | Details for lazex-0.4.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| lazex-0.4.1-py3-none-any.whl | Python 3 | none | any | Details for lazex-0.4.1-py3-none-any.whl |
Total release size: 10.1 kB
Release files / lazex-0.4.1.tar.gz
| Download URL | lazex-0.4.1.tar.gz |
|---|---|
| Size | 5.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f82ab53d81acf9b80c0c9296d1d0e938f485099475e3e6566ea52015ecdc403c
|
|
BLAKE2b-256 checksum How to use checksums |
9d5dd2625cdfe3808aeff5260d73df6ca0a2daed353a81539e2fefc3c6cd7e9c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/1.1.11 CPython/3.10.0 Darwin/21.6.0
|
Release files / lazex-0.4.1-py3-none-any.whl
| Download URL | lazex-0.4.1-py3-none-any.whl |
|---|---|
| Size | 4.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f4314f6c8bfae23c70a0b89a33d7ace064b65d7500821e4930044dd1b62755ea
|
|
BLAKE2b-256 checksum How to use checksums |
6ca52911464c05c1963e3199f82eb91f24317e50db61dd51fac0799b0bfae5cd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/1.1.11 CPython/3.10.0 Darwin/21.6.0
|
Security for lazex 0.4.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.