def test_add_new_unbound_positional(): """ Tests that the mechanism provided to support additional functions works, by testing that log can be converted. This test checks the behaviour with positional arguments only """ from mini_lambda import x, _ from math import log, e with pytest.raises(FunctionDefinitionError): log(x) Log = make_lambda_friendly_method(log) complex_identity = _(Log(e ** x)) # first check that with one argument it works assert abs(complex_identity(3.5) - 3.5) < 10e-5 print(complex_identity) # this is the remaining issue: the value of math.e is displayed instead of 'e'. We have to define 'constants' assert str(complex_identity) == "log(" + str(e) + " ** x)" # then for several arguments complex_identity = _(Log(10 ** x, 10)) assert complex_identity(3.5) == 3.5 assert str(complex_identity) == 'log(10 ** x, 10)' complex_constant = _(Log(x ** 10, x)) assert complex_constant(3.5) == 10 assert str(complex_constant) == 'log(x ** 10, x)'
def test_add_class(): """ This test demonstrates how you can convert any class to a lambda-friendly class """ from mini_lambda import x, _ class Temp: def __init__(self, den): self.den = den def divide1(self, dummy, times, dummy2=None, num=None): """ this could be an existing function that you want to convert """ return times * num / self.den @staticmethod def divide2(dummy, times, num, den=None): """ this could be an existing function that you want to convert """ return times * num / den @classmethod def divide3(cls, dummy, times, num, den=None): """ this could be an existing function that you want to convert """ return times * num / den TTemp = make_lambda_friendly_class(Temp) complex_constant = _(TTemp(x).divide1(None, x, num=1)) assert complex_constant(10) == 1 assert complex_constant(-5) == 1 complex_constant = _(TTemp.divide2(None, x, den=x, num=1)) assert complex_constant(10) == 1 assert complex_constant(-5) == 1 complex_constant = _(TTemp.divide3(None, x, den=x, num=1)) assert complex_constant(10) == 1 assert complex_constant(-5) == 1
def test_doc_usage_expressions_2(): """ Tests that the second set of examples in doc/usage in the expressions section works """ from mini_lambda import x, _, L, F # An expression is built using python syntax with a variable my_first_expr = (1 + 1) * x + 1 > 0 assert my_first_expr.evaluate(-1 / 2) is False assert my_first_expr.to_string() == "2 * x + 1 > 0" assert my_first_expr(-1 / 2).to_string() == "(2 * x + 1 > 0)(-0.5)" one = my_first_expr.as_function() # explicit conversion two = _(my_first_expr) # _() does the same thing three = L(my_first_expr) # L() is an alias for _() four = F(my_first_expr) #F too five, six = _(my_first_expr, x) # both accept multiple arguments # you can now use the functions directly assert one(-1 / 2) is False assert two(-1 / 2) is False assert three(-1 / 2) is False assert four(-1 / 2) is False assert five(-1 / 2) is False assert six(-1 / 2) == -0.5 # string representation assert str(one) == "2 * x + 1 > 0" assert str(six) == "x"
def test_add_new_unbound_no_name(): """ Tests that the mechanism provided to support additional functions works with partial and lambda functions, for which the user is asked to provide a name """ from mini_lambda import x, _ from math import log # partial function (to fix leftmost positional arguments and/or keyword arguments) from functools import partial with pytest.raises(ValueError): make_lambda_friendly_method(partial(log, 15)) # we forgot the name Log15BaseX = make_lambda_friendly_method(partial(log, 15), name='log15baseX') complex_identity = _(1 / Log15BaseX(15 ** x)) assert complex_identity(3.5) == 3.5 assert str(complex_identity) == '1 / log15baseX(15 ** x)' # another partial function example is_superclass_of_bool = make_lambda_friendly_method(partial(issubclass, bool), name='is_superclass_of_bool') expr = _(is_superclass_of_bool(x)) assert expr(int) is True assert expr(str) is False assert str(expr) == 'is_superclass_of_bool(x)' # lambda function with pytest.raises(ValueError): make_lambda_friendly_method(lambda x: log(x, 10)) # we forgot the name Log10 = make_lambda_friendly_method(lambda x: log(x, 10), name='log10') complex_identity = _(Log10(10 ** x)) assert complex_identity(3.5) == 3.5 assert str(complex_identity) == 'log10(10 ** x)'
def test_doc_index_2(): """ Tests that the second example in the documentation main page works """ from mini_lambda import s, x, _, Log # this is a dynamic creation hence pycharm does not see it # various lambda functions is_lowercase = _(s.islower()) get_prefix_upper_shebang = _(s[0:4].upper() + ' !') numeric_test_1 = _(-x > x**2) numeric_test_2 = _(((1 - 2 * x) <= -x) | (-x > x**2)) complex_identity = _(Log(10**x, 10)) # use the functions assert is_lowercase('Hello') is False assert get_prefix_upper_shebang('hello') == 'HELL !' assert numeric_test_1(0.5) is False assert numeric_test_2(1) is True assert complex_identity(10) == 10 # string representation print(is_lowercase) # s.islower() print(get_prefix_upper_shebang) # s[0:4].upper() + ' !' print(numeric_test_1) # -x > x ** 2 print(numeric_test_2) # (1 - 2 * x <= -x) | (-x > x ** 2) print(complex_identity) # log(10 ** x, 10) assert str(is_lowercase) == 's.islower()' assert str(get_prefix_upper_shebang) == "s[0:4].upper() + ' !'" assert str(numeric_test_1) == '-x > x ** 2' assert str(numeric_test_2) == '(1 - 2 * x <= -x) | (-x > x ** 2)' assert str(complex_identity) == 'log(10 ** x, 10)'
def test_constants_named(): """ This test demonstrates the possibility to create constants """ from mini_lambda import x, _, C from math import e E = C(e, 'e') assert str(_(x + e)) == 'x + 2.718281828459045' assert str(_(x + E)) == 'x + e' assert str(_(E + E)) == 'e + e'
def test_doc_usage_other_constants(): """ Tests that the example in doc/usage in the others/constants section works """ from mini_lambda import x, _, E, C from math import e assert str(_(x + e)) == 'x + 2.718281828459045' assert str(_(x + E)) == 'x + e' assert str(_(E + E)) == 'e + e' # define the constant E = C(e, 'e') # use it in expressions. The name appears when printed assert str(_(x + E)) == 'x + e'
def test_doc_usage_all_at_once(): """ Tests that the example in doc/usage in the others/anything section works """ from mini_lambda import _, C from mini_lambda.numpy import X import numpy as np import pandas as pd all_at_once = _(C(print)(C(pd.DataFrame)(X).transpose())) all_at_once(np.array([1, 2])) assert str(all_at_once) == 'print(DataFrame(X).transpose())'
def test_add_new_bound_keywords_static_class(): """ Tests that the mechanism provided to support additional bound functions works, by testing that a custom class with a function including positional and keyword arguments can be converted.""" from mini_lambda import x, _ class Temp: def __init__(self, num): self.num = num def divide1(self, dummy, times, dummy2=None, den=None): """ this could be an existing function that you want to convert """ return times * self.num / den @staticmethod def divide2(dummy, times, num, den=None): """ this could be an existing function that you want to convert """ return times * num / den @classmethod def divide3(cls, dummy, times, num, den=None): """ this could be an existing function that you want to convert """ return times * num / den # standard bound method Divide = make_lambda_friendly_method(Temp.divide1) complex_constant = _(Divide(Temp(1), None, x, den=x)) assert complex_constant(10) == 1 assert complex_constant(-5) == 1 # static method Divide2 = make_lambda_friendly_method(Temp.divide2) complex_constant = _(Divide2(None, 1, x, den=x)) assert complex_constant(10) == 1 assert complex_constant(-5) == 1 # class method Divide3 = make_lambda_friendly_method(Temp.divide3.__func__) complex_constant = _(Divide3(Temp, None, 1, x, den=x)) assert complex_constant(10) == 1 assert complex_constant(-5) == 1
def test_evaluator_iterable_all(): """ Iterable + all operator: Checks that the all operator raises an exception but that the All replacement function works """ li = InputVar('li', list) with pytest.raises(FunctionDefinitionError): all(li) all_is_true = _(All(li)) assert all_is_true([True, True, True]) assert not all_is_true([False, True, False])
def test_generated_methods(): """ Tests that equivalent methods generated by the package from various packages (currently, only math) work""" from mini_lambda import x, _, Sin from math import sin sine = _(Sin(x)) assert sine(3.5) == sin(3.5) print(sine) assert str(sine) == "sin(x)"
def test_doc_usage_other_classes(): """ Tests that the example in doc/usage in the others/classes section works """ from mini_lambda import _, make_lambda_friendly_class from mini_lambda.numpy import X import numpy as np import pandas as pd DDataframe = make_lambda_friendly_class(pd.DataFrame) expr = _(DDataframe(X).max().values[0]) assert expr(np.array([1, 2])) == 2 assert str(expr) == "DataFrame(X).max().values[0]"
def test_doc_usage_other_functions_1(): """ Tests that the example in doc/usage in the others/functions section (1) works """ from mini_lambda import x, _ # ** standard class function StartsWith = make_lambda_friendly_method(str.startswith) # now you can use `StartsWith` in your lambda expressions str_tester = _(StartsWith('hello', 'el', x)) # first check that with one argument it works str_tester(0) # False str_tester(1) # True print(str_tester) # "startswith('hello', 'el', x)" # ** static and class functions class Foo: @staticmethod def bar1(times, num, den): return times * num / den @classmethod def bar2(cls, times, num, den): return times * num / den FooBar1 = make_lambda_friendly_method(Foo.bar1) fun1 = _(FooBar1(x, den=x, num=1)) assert fun1(5.5) == 1 FooBar2a = make_lambda_friendly_method( Foo.bar2) # the `cls` argument is `Foo` and cant be changed fun2a = _(FooBar2a(x, den=x, num=1)) assert fun2a(5.5) == 1 FooBar2b = make_lambda_friendly_method( Foo.bar2.__func__) # the `cls` argument can be changed fun2b = _(FooBar2b(Foo, x, den=x, num=1)) assert fun2b(5.5) == 1
def test_doc_usage_other_functions_2(): """ Tests that the example in doc/usage in the others/functions section (2) works """ from mini_lambda import x, _ class Foo: @staticmethod def bar1(times, num, den): return times * num / den @classmethod def bar2(cls, times, num, den): return times * num / den FooBar1 = make_lambda_friendly_method(Foo.bar1) fun1 = _(FooBar1(x, den=x, num=1)) FooBar2a = make_lambda_friendly_method( Foo.bar2) # the `cls` argument is `Foo` and cant be changed fun2a = _(FooBar2a(x, den=x, num=1)) FooBar2b = make_lambda_friendly_method( Foo.bar2.__func__) # the `cls` argument can be changed fun2b = _(FooBar2b(Foo, x, den=x, num=1)) assert fun1(5.5) == 1 # apparently the order may vary: in travis it is reversed assert (str(fun1)) in {'bar1(x, den=x, num=1)', 'bar1(x, num=1, den=x)'} assert fun2a(5.5) == 1 # apparently the order may vary: in travis it is reversed assert (str(fun2a)) in {'bar2(x, den=x, num=1)', 'bar2(x, num=1, den=x)'} assert fun2b(5.5) == 1 # apparently the order may vary: in travis it is reversed assert (str(fun2b)) in { 'bar2(Foo, x, den=x, num=1)', 'bar2(Foo, x, num=1, den=x)' }
def test_donot_add_new_bound_with_constants(): """ This test demonstrates that you may not need to add a bound method if you transform the object into a constant """ from mini_lambda import x, _, C from math import e with pytest.raises(FunctionDefinitionError): 'hello'.startswith('el', x) str_tester = _(C('hello').startswith('el', x)) # first check that with one argument it works assert str_tester(0) is False assert str_tester(1) is True
def test_doc_index_1(): """ Tests that the first example in the documentation main page works """ # import magic variable 's' from mini_lambda import s # write an expression and wrap it with _() to make a function from mini_lambda import _ say_hello_function = _('Hello, ' + s + ' !') # use the function print(say_hello_function('world')) # 'Hello, world !' assert say_hello_function('world') == 'Hello, world !' print(say_hello_function) assert str(say_hello_function) == "'Hello, ' + s + ' !'"
def test_add_new_bound_positional(): """ Tests that the mechanism provided to support additional bound functions works, by testing that str.startswith() can be converted. This test checks the behaviour with positional arguments only """ from mini_lambda import x, _ with pytest.raises(FunctionDefinitionError): 'hello'.startswith('el', x) StartsWith = make_lambda_friendly_method(str.startswith) str_tester = _(StartsWith('hello', 'el', x)) # first check that with one argument it works assert str_tester(0) is False assert str_tester(1) is True assert str(str_tester) == "startswith('hello', 'el', x)"
def test_add_new_unbound_keywords(): """ Tests that the mechanism provided to support additional functions works, by testing that a custom function with positional and keyword arguments can be converted.""" from mini_lambda import x, _ def divide(dummy, times, num, den=None): """ an existing function that you want to convert """ return times * num / den Divide = make_lambda_friendly_method(divide) complex_constant = _(1 + Divide(None, x, den=x, num=1)) assert complex_constant(10) == 2 assert complex_constant(-5) == 2 # apparently the order may vary: in travis it is reversed assert str(complex_constant) in {'1 + divide(None, x, den=x, num=1)', '1 + divide(None, x, num=1, den=x)'}
def test_evaluator_comparable(): """ Comparable Object : tests that lt, le, eq, ne, gt, and ge are correctly supported """ x = InputVar('x', float) is_big = _(x > 4.5) # is_big = is_big.as_function() assert is_big(5.2) assert not is_big(-1.1) is_very_special = ((3.2 <= x) & (x < 4.5) & (x != 4)) | (x == 2) is_very_special = is_very_special.as_function() assert is_very_special(2) assert is_very_special(3.4) assert not is_very_special(-1.1) assert not is_very_special(4)
def create_base_functions(): # Existing - already ok is_finite = isfinite # Existing + Functools Partial is_superclass_of_bool = partial(issubclass, bool) """ Checks that c is a superclass of the bool type """ # User-defined, standard def is_multiple_of_3(x): """ Checks that x is a multiple of 3 """ return x % 3 == 0 # User-defined, Lambda is_lowercase = lambda s: s.islower() is_lowercase.__doc__ = "Checks that s is lowercase" # User-defined, mini-lambda from mini_lambda import _, s starts_with_a = _(s.lower().startswith('a')) # The factory of validator functions def is_multiple_of(ref): """ Returns a validation function to validate that x is a multiple of <ref> :param ref: the reference to compare x to :return: a validation function """ def is_multiple_of_ref(x): return x % ref == 0 is_multiple_of_ref.__doc__ = "Checks that x is a multiple of %s" % ref is_multiple_of_ref.__name__ = "is_multiple_of_" + str(ref) return is_multiple_of_ref # Here we use the factory to create two validator functions is_multiple_of_5 = is_multiple_of(5) is_multiple_of_9 = is_multiple_of(9) return is_multiple_of, is_multiple_of_3
def test_is_subset_is_superset(): """ Checks that is_subset and is_superset work """ a = is_subset({'+', '-'}) a({'+'}) with pytest.raises(ValidationFailure): a({'+', '-', '*'}) b = is_superset({'+', '-'}) b({'+', '-', '*'}) with pytest.raises(ValidationFailure): b({'+'}) Is_subset = make_lambda_friendly_method(is_subset) Is_superset = make_lambda_friendly_method(is_superset) c = _(Is_subset({'+', '-'})(x) & Is_superset({'+', '-'})(x)) c({'+', '-'}) with pytest.raises(ValidationFailure): c({'+', '-', '*'}) with pytest.raises(ValidationFailure): c({'+'})
def test_evaluator_numeric(): """ Numeric-like Object : tests that +,-,*,/,%,divmod(),pow(),**,@,//,<<,>>,-,+,abs,~ work """ x = InputVar('x', int) add_one = _(x + 1) assert add_one(1) == 2 remove_one = _(x - 1) assert remove_one(1) == 0 times_two = _(x * 2) assert times_two(1) == 2 div_two = _(x / 2) assert div_two(1) == 0.5 neg = _(x % 2) assert neg(3) == 1 pos = _(divmod(x, 3)) assert pos(16) == (5, 1) pow_two = _(x**2) assert pow_two(2) == 4 pow_two = _(pow(x, 2)) assert pow_two(2) == 4 # TODO matmul : @... floor_div_two = _(x // 2) assert floor_div_two(1) == 0 lshift_ = _(256 << x) assert lshift_(1) == 512 rshift_ = _(256 >> x) assert rshift_(1) == 128 neg = _(-x) assert neg(3) == -3 pos = _(+x) assert pos(-16) == -16 abs_ = _(abs(x)) assert abs_(-22) == 22 inv = _(~x) assert inv(2) == -3
def test_doc_usage_expressions_3_all_at_once(): """ Tests that the last example in doc/usage in the expressions section works """ from mini_lambda import s, _, Print say_hello = _(Print('Hello, ' + s + ' !')) say_hello('world')
def test_constants_methods_can_be_combined(): a = C(isfinite) # define a constant function (a lambda-friendly function) f = _(a(0) & a(0)) assert f(None)