simpleeval icon indicating copy to clipboard operation
simpleeval copied to clipboard

Default Operators are not appended when new operators added

Open Rod8LE opened this issue 7 years ago • 2 comments

Documentation on your simpleeval package (on github) has an explanation on how add ^ operator, but lacks an example of how operators paramater work

is through a dict with lambda, just like functions paramater work? already tested the following and it was the only way I could make it work

s = SimpleEval()
s.operators[ast.BitOr] = op.or_

the problem with this is that Or can not be added as or Also custom functions that are not available in ast package are trickier (could not make it work) for example lets take operator juggler: 1 juggler 7 returns 5, since juggler takes 1 from 7, and since the result is higher that 1, takes another 1 from the result (6) ending in 5

if the example does not make much sense is because I'm forcing it to be a custom operator

This triggers the fact that when any operator is added DEFAULT OPERATORS are ignored: if anything is added through parameter operator= a conditional on the constructor (__init__) makes it imposible to append new operators to the default operators

Is this a feature? an optimization feature? maybe I am not using the package correctly

I thought the documentation could use something like:

adding operator Or would require the following:

simple_eval("True Or False", operators={"Or": lambda x, y: x | y})

returns True

simple_eval("1 juggler 7", operators={"juggler": lambda x, y: 
                                       y - 2 *x if (y - x) > x else y - x })

returns 5

this becomes extremely handy when using complex operators for time series when comparisons are time wise (maybe sent in tuple form)

simple_eval("(t0, t1) crosses_above (s0, s1)", 
            operators={"crosses_above": lambda t, s: t[1] > s[1] if t[0] < s[0] else False})

returns True or False depending on the case when: t[0, 1] = 2, 4 and s[0, 1] = 2.5, 3.5; crosses_above returns True when: t[0, 1] = 2, 3 and s[0, 1] = 2.5, 3.5; crosses_above returns False

When this issue is clear to me I could open a fork and send the PR with the respective changes into the documentation

Also I am impressed at your work, wish you a wonderful day sir

Rod8LE avatar Feb 08 '17 18:02 Rod8LE

Hi Rod, Thanks for your kind words.

Yeah, the operators and functions parameters are a bit ugly, it must be said. They do completely over-ride the defaults, rather than appending. In some ways appending would be frequently more useful, I guess. I wonder if a 'append_defaults' would be a useful parameter to add...?

So you can do:

my_ops = simpleeval.DEFAULT_OPERATORS.copy()
my_ops[ast.BitOr] = operators.or_

s = simpleeval.SimpleEval(expr, operators=my_ops)

just like with functions.

Sadly, Python, and so the ast module that we're using, doesn't allow adding truly custom operators, such as 'crosses_above'. However. Fear not! We can be sneaky.

The BitXor operator '^' is not tooooo bad looking, so we can overload it:

def custop(x, y):
    ''' Overload the ^ BitXor operator to fold functions into inline calls '''
    if callable(x):
        return x(y)
    elif callable(y):
        return lambda z:y(x,z)
    else:
        return x ^ y

s.operators[ast.BitXor] = custop

So now, we can use it to join expressions/function/expression phrases together:

x ^func^ y

gets transformed into:

func(x,y)

(via a little lambda indirection).

Here's a working example:

Custom Operators

You could possibly figure out another way to do it using '.' attribute access to compose things together... I don't know if it would work much better though.

Does that help / make sense?

danthedeckie avatar Feb 09 '17 13:02 danthedeckie

Oh, and I had another thought... If you then want to get rid of the ^func^ Xor wings, since we're now in plain text land, you can use a string .replace to remove them. I've added that to the Example link from above too.

I'm playing with the default settings now... it does make sense to have some kind of append_defaults option, and I think realistically it should be the actual default too. Once I've written tests, and a few extras, I'll do that. It's in the dev branch now. Thanks for raising this.

danthedeckie avatar Feb 09 '17 16:02 danthedeckie