Skip to content

Commit 02c4480

Browse files
author
Sylvain MARIE
committed
Improved readme and added corresponding tests.
1 parent 7571bbb commit 02c4480

File tree

3 files changed

+174
-27
lines changed

3 files changed

+174
-27
lines changed

docs/index.md

Lines changed: 100 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,11 @@
66

77
[![Documentation](https://img.shields.io/badge/doc-latest-blue.svg)](https://smarie.github.io/python-mini-lambda/) [![PyPI](https://img.shields.io/pypi/v/mini-lambda.svg)](https://pypi.python.org/pypi/mini-lambda/) [![Downloads](https://pepy.tech/badge/mini-lambda)](https://pepy.tech/project/mini-lambda) [![Downloads per week](https://pepy.tech/badge/mini-lambda/week)](https://pepy.tech/project/mini-lambda) [![GitHub stars](https://img.shields.io/github/stars/smarie/python-mini-lambda.svg)](https://github.com/smarie/python-mini-lambda/stargazers)
88

9-
!!! success "`repr` is now enabled by default for expressions! More details [here](#new-repr-now-enabled-by-default)"
9+
!!! success "`repr` is now enabled by default for expressions and functions! More details [here](#new-repr-now-enabled-by-default)"
1010

11-
This idea initially comes from the [valid8](https://smarie.github.io/python-valid8/) validation library. I ended up understanding that there were two complementary ways to provide users with easy-to-use validation functions:
11+
`mini_lambda` allows developers to write simple expressions with a subset of standard python syntax, without the `lambda x:` prefix. These expressions can easily be transformed into functions. It is possible to get a string representation of both.
1212

13-
* either to provide a very exhaustive catalog of functions to cover most use cases (is greater than, is between, etc.). *Drawback*: we need to reinvent all functions that exist already.
14-
* or to provide users with the capability to write custom validation functions, in particular using lambdas. *Drawback*: the `lambda x:` prefix has to be present everywhere, and users still have to write explicit exception messages for validation failures.
15-
16-
17-
The `mini_lambda` library provides an answer to the second item: it allows developers to write simple functions with a subset of standard python syntax, without the `lambda x:` prefix. It is possible to get a string representation of these functions in order for example to automatically generate validation exception messages as done in [valid8](https://smarie.github.io/python-valid8/). But it can also be used for other use cases: indeed, although initially developed in the context of validation, this library is now fully independent.
13+
Among many potential use cases, the original motivation came from [valid8](https://smarie.github.io/python-valid8/) where we want to let users provide their own validation functions, while still being able to raise user-friendly exceptions "showing" the formula that failed.
1814

1915

2016
## Installing
@@ -25,11 +21,35 @@ The `mini_lambda` library provides an answer to the second item: it allows devel
2521

2622
## Usage
2723

24+
### a- Principles
25+
2826
Three basic steps:
2927

30-
* import or create a 'magic variable' such as `x`, `s`, `l`, `df`...
31-
* write an expression using it.
32-
* transform the expression into a function by wrapping it with `_()` or its aliases `L()` and`F()`.
28+
* import or create a 'magic variable' (an `InputVar`) such as `x`, `s`, `l`, `df`...
29+
* write an *expression* using it.
30+
* transform the *expression* into a *function* by wrapping it with `_()`, `L()`, or `F()` (3 aliases), or by calling `as_function()` on it.
31+
32+
For example with a numeric variable:
33+
34+
```python
35+
# -- expressions --
36+
from mini_lambda import x
37+
my_expr = x ** 2
38+
my_expr # <LambdaExpression: x ** 2>
39+
40+
my_expr(12) # beware: calling an expression is still an expression !
41+
# <LambdaExpression: (x ** 2)(12)>
42+
43+
# -- functions --
44+
from mini_lambda import _
45+
my_func = _(x ** 2)
46+
my_func # <LambdaFunction: x ** 2>
47+
48+
assert my_func(12) == 144 # calling a function executes it as expected
49+
```
50+
51+
52+
Or with a string variable:
3353

3454
```python
3555
# create or import a magic variable, here we import 's'
@@ -46,6 +66,20 @@ say_hello_function('world') # Returns "Hello, world !"
4666
print(say_hello_function) # "'Hello, ' + s + ' !'"
4767
```
4868

69+
### b- Capabilities
70+
71+
The variable can represent anything, not necessarily a primitive. If you wish to use another symbol just define it using `InputVar`:
72+
73+
```python
74+
from mini_lambda import InputVar
75+
z = InputVar('z')
76+
77+
from logging import Logger
78+
l = InputVar('l', Logger)
79+
```
80+
81+
Note that the type information is optional, it is just for your IDE's autocompletion capabilities.
82+
4983
Most of python syntax can be used in an expression:
5084

5185
```python
@@ -89,16 +123,18 @@ There are of course a few limitations to `mini_lambda` as compared to full-flavo
89123

90124
Check the [Usage](./usage/) page for more details.
91125

92-
## New: `repr` now enabled by default
126+
### New: `repr` now enabled by default
93127

94-
Starting in version 2.0.0, the representation of lambda expressions does not raise exceptions anymore by default. This behaviour was a pain for developers, and was only like this for the very rare occasions where `repr` was used in the expression.
128+
Starting in version 2.0.0, the representation of lambda expressions does not raise exceptions anymore by default. This behaviour was a pain for developers, and was only like this for the very rare occasions where `repr` was needed in the expression itself.
95129

96130
So now
97131

98132
```python
99-
>>> from mini_lambda import x
133+
>>> from mini_lambda import x, F
100134
>>> x ** 2
101135
<LambdaExpression: x ** 2>
136+
>>> F(x ** 2)
137+
<LambdaFunction: x ** 2>
102138
```
103139

104140
If you wish to bring back the old exception-raising behaviour, simply set the `repr_on` attribute of your expressions to `False`:
@@ -111,6 +147,57 @@ If you wish to bring back the old exception-raising behaviour, simply set the `r
111147
mini_lambda.base.FunctionDefinitionError: __repr__ is not supported by this Lambda Expression. (...)
112148
```
113149

150+
### c- How to support mini-lambda expressions in your libraries.
151+
152+
You may wish to support mini-lambda *expressions* (not *functions*) directly into your code. That way, your users will not even have to convert their expressions into functions - this will bring more readability and ease of use for them.
153+
154+
You can do this with `as_function`: this will convert expressions to functions if needed, but otherwise silently return its input.
155+
156+
```python
157+
from mini_lambda import _, s, as_function
158+
159+
def call_with_hello(f):
160+
"""An example custom method that is lambda_friendy"""
161+
162+
# transform mini-lambda expression to function if needed.
163+
f = as_function(f)
164+
165+
return f('hello')
166+
167+
# it works with a normal function
168+
def foo(s):
169+
return s[0]
170+
assert call_with_hello(foo) == 'h'
171+
172+
# with a mini-lambda *Function* (normal: this is a function)
173+
assert call_with_hello(_(s[0])) == 'h'
174+
175+
# and with a mini-lambda *Expression* too (this is new and easier to read)
176+
assert call_with_hello(s[0]) == 'h'
177+
```
178+
179+
In addition a `is_mini_lambda_expr` helper is also provided, if you wish to perform some reasoning:
180+
181+
```python
182+
from mini_lambda import x, is_mini_lambda_expr, as_function
183+
184+
# mini lambda: true
185+
assert is_mini_lambda_expr(x ** 2)
186+
187+
# standard lambda: false
188+
assert not is_mini_lambda_expr(lambda x: x)
189+
190+
# standard function: false
191+
def foo():
192+
pass
193+
assert not is_mini_lambda_expr(foo)
194+
195+
# mini lambda as function: false
196+
f = as_function(x ** 2)
197+
assert not is_mini_lambda_expr(f)
198+
```
199+
200+
114201
## Main features
115202

116203
* More compact lambda expressions for single-variable functions

mini_lambda/tests/test_mini_lambda.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
import sys
33

44
from mini_lambda import InputVar, Len, Str, Int, Repr, Bytes, Sizeof, Hash, Bool, Complex, Float, Oct, Iter, \
5-
Any, All, _, Slice, Get, Not, FunctionDefinitionError, Format, C, And, Or, Round, as_function, is_mini_lambda_expr
5+
Any, All, _, Slice, Get, Not, FunctionDefinitionError, Format, C, And, Or, Round, as_function, \
6+
is_mini_lambda_expr, x
67
from math import cos
78
from numbers import Real
89

@@ -804,8 +805,6 @@ def test_constants_methods_can_be_combined():
804805
def test_is_mini_lambda_expr():
805806
"""Tests that `is_mini_lambda_expr` works"""
806807

807-
from mini_lambda import x
808-
809808
# mini lambda: true
810809
assert is_mini_lambda_expr(x ** 2)
811810

@@ -826,7 +825,6 @@ def test_as_function():
826825
"""Tests that `as_function` works"""
827826

828827
# it transforms mini-lambda exprs...
829-
from mini_lambda import x
830828
f = as_function(x ** 2)
831829
assert f(2) == 4
832830

mini_lambda/tests/test_readme.py

Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,61 @@
99
from mini_lambda.main import LambdaExpression
1010

1111

12-
def test_doc_index_1():
12+
def test_doc_index_0(capsys):
13+
""""""
14+
15+
with capsys.disabled():
16+
from mini_lambda import x, F
17+
18+
# this is the world of expressions, they can be combined etc.
19+
my_expr = x ** 2
20+
21+
print(repr(my_expr))
22+
print(repr(my_expr(12))) # beware: calling an expression is still an expression !
23+
24+
with capsys.disabled():
25+
# expressions can be transformed into functions
26+
my_func = F(x ** 2)
27+
28+
print(repr(my_func))
29+
print(repr(my_func(12))) # calling a function executes it as expected
30+
31+
captured = capsys.readouterr()
32+
33+
with capsys.disabled():
34+
print(captured.out)
35+
assert captured.out == """<LambdaExpression: x ** 2>
36+
<LambdaExpression: (x ** 2)(12)>
37+
<LambdaFunction: x ** 2>
38+
144
39+
"""
40+
41+
42+
def test_doc_index_1(capsys):
1343
""" Tests that the first example in the documentation main page works """
1444

15-
# import magic variable 's'
16-
from mini_lambda import s
45+
with capsys.disabled():
46+
47+
# import magic variable 's'
48+
from mini_lambda import s
1749

18-
# write an expression and wrap it with _() to make a function
19-
from mini_lambda import _
20-
say_hello_function = _('Hello, ' + s + ' !')
50+
# write an expression and wrap it with _() to make a function
51+
from mini_lambda import _
52+
say_hello_function = _('Hello, ' + s + ' !')
53+
54+
# use the function
55+
print(say_hello_function('world')) # 'Hello, world !'
56+
assert say_hello_function('world') == 'Hello, world !'
2157

22-
# use the function
23-
print(say_hello_function('world')) # 'Hello, world !'
24-
assert say_hello_function('world') == 'Hello, world !'
2558
print(say_hello_function)
26-
assert str(say_hello_function) == "'Hello, ' + s + ' !'"
59+
60+
captured = capsys.readouterr()
61+
62+
with capsys.disabled():
63+
print(captured.out)
64+
assert captured.out == "'Hello, ' + s + ' !'\n"
65+
66+
assert str(say_hello_function) == "'Hello, ' + s + ' !'"
2767

2868

2969
def test_doc_index_2():
@@ -178,6 +218,7 @@ def test_doc_usage_syntax_2():
178218
expr = Get('hello', Slice(0, i)) # OK
179219
# representing: Repr/Str/Bytes/Sizeof/Hash
180220
assert repr(x) == '<LambdaExpression: x>'
221+
assert repr(x.as_function()) == '<LambdaFunction: x>'
181222
x.repr_on = False
182223
with pytest.raises(FunctionDefinitionError):
183224
expr = repr(x) # fails
@@ -365,3 +406,24 @@ def test_doc_optional():
365406
from mini_lambda import df
366407

367408
from mini_lambda.vars.pandas_ import df
409+
410+
411+
def test_doc_as_function():
412+
from mini_lambda import _, s, as_function
413+
414+
def call_with_hello(f):
415+
"""An example custom method that is lambda_friendy"""
416+
f = as_function(f)
417+
return f('hello')
418+
419+
# it works with a normal function
420+
def foo(s):
421+
return s[0]
422+
423+
assert call_with_hello(foo) == 'h'
424+
425+
# with a mini-lambda function (normal: this is a function)
426+
assert call_with_hello(_(s[0])) == 'h'
427+
428+
# and with a mini-lambda expression too (this is new)
429+
assert call_with_hello(s[0]) == 'h'

0 commit comments

Comments
 (0)