Skip to content

Commit b48dc7c

Browse files
author
Sylvain MARIE
committed
Fixed bug with repr_on not correctly propagated. Added corresponding test and readme
1 parent cbf907c commit b48dc7c

File tree

5 files changed

+50
-21
lines changed

5 files changed

+50
-21
lines changed

code_generation/tpl_magic_methods.mako

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class _LambdaExpressionGenerated(_LambdaExpressionBase):
4949

5050
# return a new LambdaExpression of the same type than self, with the new function as inner function
5151
string_expr = '${o.uni_operator}' + get_repr(self, ${o.precedence_level})
52-
return type(self)(fun=_${o.method_name}, precedence_level=${o.precedence_level}, str_expr=string_expr, root_var=self._root_var)
52+
return type(self)(fun=_${o.method_name}, precedence_level=${o.precedence_level}, str_expr=string_expr, root_var=self._root_var, repr_on=self.repr_on)
5353

5454
## -----------------------------
5555
% elif o.pair_operator:

docs/index.md

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
[![Build Status](https://travis-ci.org/smarie/python-mini-lambda.svg?branch=master)](https://travis-ci.org/smarie/python-mini-lambda) [![Tests Status](https://smarie.github.io/python-mini-lambda/junit/junit-badge.svg?dummy=8484744)](https://smarie.github.io/python-mini-lambda/junit/report.html) [![codecov](https://codecov.io/gh/smarie/python-mini-lambda/branch/master/graph/badge.svg)](https://codecov.io/gh/smarie/python-mini-lambda) [![Documentation](https://img.shields.io/badge/docs-latest-blue.svg)](https://smarie.github.io/python-mini-lambda/) [![PyPI](https://img.shields.io/badge/PyPI-mini_lambda-blue.svg)](https://pypi.python.org/pypi/mini_lambda/)[![downloads](https://img.shields.io/badge/downloads%2008%2F18-18k-brightgreen.svg)](https://kirankoduru.github.io/python/pypi-stats.html)
66

7+
!!! success "`repr` is now enabled by default for expressions! More details [here](#new-repr-now-enabled-by-default)"
8+
79
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:
810

911
* 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.
@@ -45,7 +47,8 @@ print(say_hello_function) # "'Hello, ' + s + ' !'"
4547
Most of python syntax can be used in an expression:
4648

4749
```python
48-
from mini_lambda import x, s, _, Log
50+
from mini_lambda import x, s, _
51+
from mini_lambda.symbols.math_ import Log
4952

5053
# various lambda functions
5154
is_lowercase = _( s.islower() )
@@ -72,7 +75,7 @@ print(complex_identity) # log(10 ** x, 10)
7275
If you know python you should feel at home here, except for two things:
7376

7477
* `or` and `and` should be replaced with their bitwise equivalents `|` and `&`
75-
* additional constants, methods and classes need to be made lambda-friendly before use. For convenience all of the [built-in functions](https://docs.python.org/3/library/functions.html) as well as constants, methods and classes from the `math.py` and `decimal.py` modules are provided in a lambda-friendly way by this package, hence the `from mini_lambda import Log` above.
78+
* additional constants, methods and classes need to be made lambda-friendly before use. For convenience all of the [built-in functions](https://docs.python.org/3/library/functions.html) as well as constants, methods and classes from the `math.py` and `decimal.py` modules are provided in a lambda-friendly way by this package, hence the `from mini_lambda.symbols.math_ import Log` above.
7679

7780
Note that the printed version provides the minimal equivalent representation taking into account operator precedence. Hence `numeric_test_2` got rid of the useless parenthesis. This is **not** a mathematical simplification like in [SymPy](http://www.sympy.org/fr/), i.e. `x - x` will **not** be simplified to `0`.
7881

@@ -84,11 +87,32 @@ There are of course a few limitations to `mini_lambda` as compared to full-flavo
8487

8588
Check the [Usage](./usage/) page for more details.
8689

90+
## New: `repr` now enabled by default
91+
92+
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.
93+
94+
So now
95+
96+
```python
97+
>>> from mini_lambda import x
98+
>>> x ** 2
99+
<_LambdaExpression: x ** 2>
100+
```
101+
102+
If you wish to bring back the old exception-raising behaviour, simply set the `repr_on` attribute of your expressions to `False`:
103+
104+
```python
105+
>>> from mini_lambda import x
106+
>>> x.repr_on = False
107+
>>> x ** 2
108+
(...)
109+
mini_lambda.base.FunctionDefinitionError: __repr__ is not supported by this Lambda Expression. (...)
110+
```
87111

88112
## Main features
89113

90114
* More compact lambda expressions for single-variable functions
91-
* As close to python syntax as technically possible: the base type for lambda expressions in `mini_lambda`, `_LambdaExpression`, overrides all operators that can be overriden as of today in [python 3.6](https://docs.python.org/3/reference/datamodel.html). The remaining limits come from the language itself, for example chained comparisons and `and/or` are not supported as python casts the partial results to boolean to enable short-circuits. Details [here](./usage#lambda-expression-syntax).
115+
* As close to python syntax as technically possible: the base type for lambda expressions in `mini_lambda`, `_LambdaExpression`, overrides all operators that can be overriden as of today in [python](https://docs.python.org/3/reference/datamodel.html). The remaining limits come from the language itself, for example chained comparisons and `and/or` are not supported as python casts the partial results to boolean to enable short-circuits. Details [here](./usage#lambda-expression-syntax).
92116
* Printability: expressions can be turned to string representation in order to (hopefully) get interpretable messages more easily, for example when the expression is used in a [validation context](https://github.com/smarie/python-valid8)
93117

94118

mini_lambda/base.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -219,8 +219,7 @@ def evaluate_inner_function_and_apply_object_method(raw_input):
219219
+ ', '.join([arg_name + '=' + get_repr(arg, None) for arg_name, arg in m_kwargs.items()]) + ')'
220220
return type(self)(fun=evaluate_inner_function_and_apply_object_method,
221221
precedence_level=_PRECEDENCE_SUBSCRIPTION_SLICING_CALL_ATTRREF,
222-
str_expr=string_expr,
223-
root_var=root_var)
222+
str_expr=string_expr, root_var=root_var, repr_on=self.repr_on)
224223

225224
@classmethod
226225
def constant(cls,
@@ -303,7 +302,7 @@ def evaluate_all_and_apply_method(input):
303302

304303
return cls(fun=evaluate_all_and_apply_method,
305304
precedence_level=_PRECEDENCE_SUBSCRIPTION_SLICING_CALL_ATTRREF,
306-
str_expr=string_expr, root_var=root_var)
305+
str_expr=string_expr, root_var=root_var, repr_on=first_expression.repr_on)
307306

308307

309308
def _get_root_var(*args, **kwargs):

mini_lambda/main.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ def evaluate_both_inner_functions_and_combine(input):
151151
return _LambdaExpression(fun=evaluate_both_inner_functions_and_combine,
152152
precedence_level=_PRECEDENCE_BITWISE_AND,
153153
str_expr=string_expr,
154-
root_var=root_var)
154+
root_var=root_var, repr_on=self.repr_on)
155155

156156
def __or__(self, other):
157157
"""
@@ -201,7 +201,7 @@ def evaluate_both_inner_functions_and_combine(input):
201201
return _LambdaExpression(fun=evaluate_both_inner_functions_and_combine,
202202
precedence_level=_PRECEDENCE_BITWISE_OR,
203203
str_expr=string_expr,
204-
root_var=root_var)
204+
root_var=root_var, repr_on=self.repr_on)
205205

206206
def __xor__(self, other):
207207
"""
@@ -242,7 +242,7 @@ def evaluate_both_inner_functions_and_combine(input):
242242
return _LambdaExpression(fun=evaluate_both_inner_functions_and_combine,
243243
precedence_level=_PRECEDENCE_BITWISE_XOR,
244244
str_expr=string_expr,
245-
root_var=root_var)
245+
root_var=root_var, repr_on=self.repr_on)
246246

247247
def not_(self):
248248
""" Returns a new _LambdaExpression performing 'not x' on the result of this expression's evaluation """
@@ -299,7 +299,7 @@ def ___getattr__(input):
299299
# return a new LambdaExpression of the same type than self, with the new function as inner function
300300
string_expr = get_repr(self, _PRECEDENCE_SUBSCRIPTION_SLICING_CALL_ATTRREF) + '.' + name
301301
return type(self)(fun=___getattr__, precedence_level=_PRECEDENCE_SUBSCRIPTION_SLICING_CALL_ATTRREF,
302-
str_expr=string_expr, root_var=root_var)
302+
str_expr=string_expr, root_var=root_var, repr_on=self.repr_on)
303303

304304
# Special case for the string representation
305305
def __call__(self, *args, **kwargs):
@@ -320,7 +320,7 @@ def ___call__(input):
320320
+ (', ' if (len(args) > 0 and len(kwargs) > 0) else '')\
321321
+ ', '.join([arg_name + '=' + get_repr(arg, None) for arg_name, arg in kwargs.items()]) + ')'
322322
return type(self)(fun=___call__, precedence_level=_PRECEDENCE_SUBSCRIPTION_SLICING_CALL_ATTRREF,
323-
str_expr=string_expr, root_var=root_var)
323+
str_expr=string_expr, root_var=root_var, repr_on=self.repr_on)
324324

325325
# Special case for the string representation
326326
def __getitem__(self, key):
@@ -338,7 +338,7 @@ def ___getitem__(input):
338338
string_expr = get_repr(self, _PRECEDENCE_SUBSCRIPTION_SLICING_CALL_ATTRREF) \
339339
+ '[' + get_repr(key, None) + ']'
340340
return type(self)(fun=___getitem__, precedence_level=_PRECEDENCE_SUBSCRIPTION_SLICING_CALL_ATTRREF,
341-
str_expr=string_expr, root_var=root_var)
341+
str_expr=string_expr, root_var=root_var, repr_on=self.repr_on)
342342

343343
# Special case for string representation because pow is asymetric in precedence
344344
def __pow__(self, other):
@@ -353,7 +353,8 @@ def ___pow__(input):
353353
# return a new LambdaExpression of the same type than self, with the new function as inner function
354354
string_expr = get_repr(self, _PRECEDENCE_EXPONENTIATION) + ' ** ' \
355355
+ get_repr(other, _PRECEDENCE_POS_NEG_BITWISE_NOT)
356-
return type(self)(fun=___pow__, precedence_level=13, str_expr=string_expr, root_var=root_var)
356+
return type(self)(fun=___pow__, precedence_level=13, str_expr=string_expr, root_var=root_var,
357+
repr_on=self.repr_on)
357358

358359
# Special case for string representation because pow is asymetric in precedence
359360
def __rpow__(self, other):
@@ -368,7 +369,8 @@ def ___rpow__(input):
368369
# return a new LambdaExpression of the same type than self, with the new function as inner function
369370
string_expr = get_repr(other, _PRECEDENCE_EXPONENTIATION) + ' ** ' \
370371
+ get_repr(self, _PRECEDENCE_POS_NEG_BITWISE_NOT)
371-
return type(self)(fun=___rpow__, precedence_level=13, str_expr=string_expr, root_var=root_var)
372+
return type(self)(fun=___rpow__, precedence_level=13, str_expr=string_expr, root_var=root_var,
373+
repr_on=self.repr_on)
372374

373375
# Special case : unbound function call but with left/right
374376
def __divmod__(self, other):
@@ -385,7 +387,7 @@ def ___divmod__(input):
385387
# Note: we use precedence=None for coma-separated items inside the parenthesis
386388
string_expr = 'divmod(' + get_repr(self, None) + ', ' + get_repr(other, None) + ')'
387389
return type(self)(fun=___divmod__, precedence_level=_PRECEDENCE_SUBSCRIPTION_SLICING_CALL_ATTRREF,
388-
str_expr=string_expr, root_var=root_var)
390+
str_expr=string_expr, root_var=root_var, repr_on=self.repr_on)
389391

390392
# Special case : unbound function call but with left/right
391393
def __rdivmod__(self, other):
@@ -402,7 +404,7 @@ def ___rdivmod__(input):
402404
# Note: we use precedence=None for coma-separated items inside the parenthesis
403405
string_expr = 'divmod(' + get_repr(other, None) + ', ' + get_repr(self, None) + ')'
404406
return type(self)(fun=___rdivmod__, precedence_level=_PRECEDENCE_SUBSCRIPTION_SLICING_CALL_ATTRREF,
405-
str_expr=string_expr, root_var=root_var)
407+
str_expr=string_expr, root_var=root_var, repr_on=self.repr_on)
406408

407409
# special case: format(x, args) does not work but x.format() works
408410
def __format__(self, *args):

mini_lambda/tests/test_readme.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,11 +177,15 @@ def test_doc_usage_syntax_2():
177177
expr = 'hello'[0:i] # fails
178178
expr = Get('hello', Slice(0, i)) # OK
179179
# representing: Repr/Str/Bytes/Sizeof/Hash
180-
assert repr(l) == '<_LambdaExpression: l>'
181-
l.repr_on = False
180+
assert repr(x) == '<_LambdaExpression: x>'
181+
x.repr_on = False
182182
with pytest.raises(FunctionDefinitionError):
183-
expr = repr(l) # fails
184-
expr = Repr(l) # OK
183+
expr = repr(x) # fails
184+
expr = Repr(x) # OK
185+
# propagation
186+
with pytest.raises(FunctionDefinitionError):
187+
expr = repr(x ** 2) # fails
188+
expr = Repr(x ** 2) # OK
185189
# formatting with the variable in the args
186190
with pytest.raises(FunctionDefinitionError):
187191
expr = '{} {}'.format(s, s) # fails

0 commit comments

Comments
 (0)