barriers module
Python decorators for including/excluding type checks, value/bounds checks, and other code blocks within the compiled bytecode of functions and methods.
- class barriers(*args: Tuple[bool] | None, **kwargs: Dict[str, bool] | None)[source]
Bases:
objectClass for per-module configuration objects that can be used to define (and toggle inclusion of) categories of code blocks, to decorate functions, and to mark code blocks.
Consider the function below. The body of this function contains a code block that raises an exception if either of the two inputs is a negative integer.
>>> def f(x: int, y: int) -> int: ... ... if x < 0 or y < 0: ... raise ValueError('inputs must be nonnegative') ... ... return x + y ... >>> f(1, 2) 3 >>> f(-1, -2) Traceback (most recent call last): ... ValueError: inputs must be nonnegative
Below, an instance of
barriersis introduced.>>> from barriers import barriers >>> example = barriers(False) @ globals()
The
barriersinstanceexampledefined above is a decorator that can remove designated code blocks in the body of a function.The
Falseargument in the expressionbarriers(False)above should be interpreted to mean that this barrier is disabled (i.e., that the marked code blocks in the bodies of functions decorated by this decorator should be removed). The default value for this optional argument isTrue; this should be interpreted to mean that this barrier is enabled (and, thus, that marked code blocks should not be removed from decorated functions).The notation
@ globals()ensures that the namespace returned byglobalsis used when compiling the abstract syntax trees of transformed functions.
A code block can be designated for automatic removal by placing a marker – in this case, the
examplevariable – on the line directly above that code block. Note that in the body of the functionfdefined below, theifblock is immediately preceded by a line that contains the variableexample.>>> @example ... def f(x: int, y: int) -> int: ... ... example ... if x < 0 or y < 0: ... raise ValueError('inputs must be nonnegative') ... ... return x + y
The decorator
@exampleautomatically removes theifblock in the function above. As a result, the function does not raise an exception when it is applied to negative inputs.>>> f(1, 2) 3 >>> f(-1, -2) -3
It is also possible to use the string literal
'example'as a marker.>>> @example ... def g(x: int, y: int) -> int: ... ... 'example' ... if x < 0 or y < 0: ... raise ValueError('inputs must be nonnegative') ... ... return x + y
This may be preferable because string literals appearing as statements do not contribute to the size of the compiled bytecode of a function (as shown below).
>>> def f(x: int, y: int) -> int: ... example ... if x < 0 or y < 0: ... raise ValueError('inputs must be nonnegative') ... return x + y ... >>> def g(x: int, y: int) -> int: ... 'example' ... if x < 0 or y < 0: ... raise ValueError('inputs must be nonnegative') ... return x + y ... >>> from dis import Bytecode >>> len(list(Bytecode(g.__code__))) < len(list(Bytecode(f.__code__))) True
It is also possible to define and use individually named markers (which are created as attributes of the
barriersinstance).>>> from barriers import barriers >>> checks = barriers(types=True, bounds=False) @ globals() >>> @checks ... def f(x: int, y: int) -> int: ... ... checks.types ... if not isinstance(x, int) and not isinstance(y, int): ... raise TypeError('inputs must be integers') ... ... checks.bounds ... if x < 0 or y < 0: ... raise ValueError('inputs must be nonnegative') ... ... return x + y ... >>> f('a', 'b') Traceback (most recent call last): ... TypeError: inputs must be integers >>> f(-1, -2) -3
When one or more named markers are defined, only named markers that have been defined can be used.
>>> @checks ... def h(x: int) -> int: ... ... checks ... if x == 0: ... raise ValueError('value must be nonzero') ... ... return x ... Traceback (most recent call last): ... RuntimeError: cannot use general marker when individual markers are defined >>> @checks ... def h(x: int) -> int: ... ... checks.value ... if x == 0: ... raise ValueError('value must be nonzero') ... ... return x ... Traceback (most recent call last): ... NameError: marker `checks.value` is not defined >>> @checks ... def h(x: int) -> int: ... ... 'checks.value' ... if x == 0: ... raise ValueError('value must be nonzero') ... ... return x ... Traceback (most recent call last): ... NameError: marker `checks.value` is not defined
A statement may have a syntactic form that could be a marker. However, if it makes no reference to a defined instance of
barriers, it is ignored.>>> @checks ... def h(x: int) -> int: ... ... undefined.value ... if x == 0: ... raise ValueError('value must be nonzero') ... ... return x
If a string marker cannot be parsed as an expression, it is ignored.
>>> @checks ... def i(x: int, y: int) -> int: ... ... 'checks!value' ... if x < 0 or y < 0: ... raise ValueError('inputs must be nonnegative') ... ... 'pass' ... if x < 0 or y < 0: ... raise ValueError('inputs must be nonnegative') ... ... return x + y ... >>> i(-1, -2) Traceback (most recent call last): ... ValueError: inputs must be nonnegative
When defining individual markers, setting the status using a single boolean argument is not possible. Also, only a single boolean argument is permitted.
>>> from barriers import barriers >>> checks = barriers(True, types=True, bounds=False) Traceback (most recent call last): ... ValueError: cannot specify general status when defining individual markers >>> from barriers import barriers >>> checks = barriers(True, False) Traceback (most recent call last): ... ValueError: exactly one status argument or one or more named status arguments are required
In order to accommodate the remaining examples, the statement below resets the
barriersinstance to one that does not define distinct, named markers.>>> from barriers import barriers >>> checks = barriers(False) @ globals()
Decorators can be applied to functions that invoke other functions. For example, the definition of the function
fbelow refers to another functiong.>>> def g(x, y): ... return x + y ... >>> @checks ... def f(x: int, y: int) -> int: ... ... checks ... if x < 0 or y < 0: ... raise ValueError('inputs must be nonnegative') ... ... return g(x, y) ... >>> f(1, 2) 3
For completess, the example below demonstrates that marked code blocks are by default (i.e., when no arguments are supplied to the
barriersconstructor) not removed.>>> from barriers import barriers >>> checks = barriers() @ globals() >>> def f(x: int, y: int) -> int: ... ... checks ... if x < 0 or y < 0: ... raise ValueError('inputs must be nonnegative') ... ... return x + y ... >>> f(-1, -2) Traceback (most recent call last): ... ValueError: inputs must be nonnegative
The
>>operator (corresponding to the__rshift__method) can be used to ensure that the decorator stores the transformed version of the decorated function under a specific attribute.>>> from barriers import barriers >>> checks = barriers(False) @ globals() >> 'unsafe'
Note that in the example below, the decorator has no effect on the original function
f. However, the functionf.unsafecorresponds to the transformed version off.>>> def g(x, y): ... return x + y ... >>> @checks ... def f(x: int, y: int) -> int: ... ... checks ... if x < 0 or y < 0: ... raise ValueError('inputs must be nonnegative') ... ... return g(x, y) ... >>> f(-1, -2) Traceback (most recent call last): ... ValueError: inputs must be nonnegative >>> f.unsafe(-1, -2) -3
The
<<operator (corresponding to the__lshift__method) behaves in a complementary manner. After this method has been invoked, the decorater transforms a decorated function but preserves the original function under the specified attribute.- __matmul__(namespace: dict) barriers[source]
Store internally the supplied namespace. This namespace is used during the compilation of transformed abstract syntax trees of functions in the
_transformmethod.>>> from barriers import barriers >>> example = barriers(False) @ globals() >>> @example ... def f(x: int, y: int) -> int: ... ... example ... if x < 0 or y < 0: ... raise ValueError('inputs must be nonnegative') ... ... return x + y ... >>> f(-1, -2) -3
If no namespace is specified, it is possible that instances of markers that appear in the body of a function will not be recognized.
>>> from barriers import barriers >>> example = barriers(False) >>> @example ... def f(x: int, y: int) -> int: ... ... example ... if x < 0 or y < 0: ... raise ValueError('inputs must be nonnegative') ... ... return x + y ... Traceback (most recent call last): ... NameError: name 'example' is not defined
- __rshift__(attribute: str) barriers[source]
Set the attribute (of decorated function objects) under which the transformed versions of functions should be stored. After this method has been invoked, this decorator will not change the decorated function itself.
>>> from barriers import barriers >>> checks = barriers(False) @ globals() >> 'unsafe' >>> @checks ... def f(x: int, y: int) -> int: ... ... checks ... if x < 0 or y < 0: ... raise ValueError('inputs must be nonnegative') ... ... return x + y ... >>> f(-1, -2) Traceback (most recent call last): ... ValueError: inputs must be nonnegative >>> f.unsafe(-1, -2) -3
- __lshift__(attribute: str) barriers[source]
Set the attribute (of decorated function objects) under which the original versions of functions should be stored. After this method has been invoked, this decorator will always store the original (unmodified) function under the specified attribute.
>>> from barriers import barriers >>> checks = barriers(False) @ globals() << 'safe' >>> @checks ... def f(x: int, y: int) -> int: ... ... checks ... if x < 0 or y < 0: ... raise ValueError('inputs must be nonnegative') ... ... return x + y ... >>> f(-1, -2) -3 >>> f.safe(-1, -2) Traceback (most recent call last): ... ValueError: inputs must be nonnegative
- __call__(function: Callable) Callable[source]
Allows this instance to behave as a decorator that parses a function and removes any marked code blocks (as specified within this instance).
>>> from barriers import barriers >>> example = barriers(False) @ globals() >>> @example ... def f(x: int, y: int) -> int: ... ... example ... if x < 0 or y < 0: ... raise ValueError('inputs must be nonnegative') ... ... return x + y ... >>> f(-1, -2) -3