3

I have some conditions/comparisons stored as strings. How can I check these conditions? A very simple example is given below. I want the conditions as strings because I want to print them in case they fail.

I think I'll need to make a parser for this but that would be a really stupid idea to make a complete Python parser for a small thing. Any ideas what can be done?

def rev_num(num):
    if num < 0:
        return -int(str(-num)[::-1])
    else:
        return int(str(num)[::-1])

conditions = ['rev_num(-34) != -43', 'rev_num(34) != 43']

for i in conditions:
     if something-needs-to-come-here(i):
           print(i)

I know this is a weird idea but please tag along if you can.


I caused some confusion to user2357112. He pointed out that what I am trying to do is called unit-testing. Thanks for that.

To avoid any further confusion I'll add the code that I am trying to improve. The change that I want to make is to print the condition in the function correctness which made it return False.

def rev_num(num):
    if num < 0:
        return -int(str(-num)[::-1])
    else:
        return int(str(num)[::-1])

if __name__ == "__main__":
    from timeit import Timer
    import random

    def correctness(f):
        print("Correctness Test")
        if f(-34) != -43 or f(34) != 43:
            return False
        print('Correct')
        print('-----------')
        return True

    def timing(f, times):
        def test1(f):
            f(random.randint(1, 1000))
        def test2(f):
            f(random.randint(100000, 1000000))

        print("Timing Test")
        print(Timer(lambda: test1(f)).timeit(number = times))
        print(Timer(lambda: test2(f)).timeit(number = times))
        print('-----------')

    def tests(f,times):
        print(f.__name__)
        print('-----------')
        if correctness(f) is True:
            timing(f, times)

    repeat = 100000
    tests(rev_num, repeat)
Aseem Bansal
  • 6,722
  • 13
  • 46
  • 84
  • possible duplicate of [Python: make eval safe](http://stackoverflow.com/questions/3513292/python-make-eval-safe) – Marcin Jul 19 '13 at 17:39
  • @Marcin It would have been a duplicate if I had known about `eval`. I think anyone asking this question wouldn't know about `eval`. If anyone knew about the existence of this then a simple google search would have sufficed. – Aseem Bansal Jul 19 '13 at 17:41
  • What's your point? The answer is the same, and keeping this open serves no purpose. – Marcin Jul 19 '13 at 17:42
  • If others haven't driven home the point yet, you really shouldn't use eval. – roippi Jul 19 '13 at 17:46
  • @roippi I understood that. The point here is that in this case I will be the only one writing the conditions. But now I know the keywords I'll read the documentation as well as do a google search to find out more before making a decision about what to use. – Aseem Bansal Jul 19 '13 at 17:48
  • @Marcin The point is that there can be other solutions to this problem. `eval` isn't the only way to do this. See the answer by A. Rhodes and Marcin. They gave a different solution. I don't care how to make `eval` safe. I want to know how to evaluate the conditions in whatever way possible. Anyone asking this question in future will agree with me. – Aseem Bansal Jul 19 '13 at 17:53

7 Answers7

3

You could use eval, but I wouldn't suggest to do so. If you already know that you want to perform several calls to rev_num(x) != y, just create an auxiliary function and use a list of tuples to store the arguments:

def check_condition(x, y):
    return rev_num(x) != y

conditions = [(-34, -43), (34, 43)]

for i in conditions:
    if check_condition(*i):
        print('rev_num({}) != {}'.format(*i))
A. Rodas
  • 20,171
  • 8
  • 62
  • 72
1

You can do that using eval(cond_string):

for i in conditions:
     if eval(i):
           print(i)

Edit: yes, as several have pointed out, eval can be dangerous if you can't be absolutely certain about the content of the strings you're evaluating. For that reason, using eval is often seen as bad general practice, even though it may be the simplest way to achieve what you're aiming for here.

If your purpose is to perform sanity checks for code maintenance purposes, you could also take a look at the unittest module.

ali_m
  • 71,714
  • 23
  • 223
  • 298
1

If you want to print the condition for debugging, raise an exception instead. That'll give you a full stack trace, including the line number where the exception was raised. It gives all the benefits you want with none of the downsides.

If it's nonfatal, raise a warning. That'll still give you a stack trace and a line number, but it won't halt the program, and you can turn off the warning when you ship.

If you want to show the condition to the user? You don't. That error message is useless to the user. Show something more informative.

user2357112
  • 260,549
  • 28
  • 431
  • 505
  • Actually I was using this for maintaining my codes. I sometimes change my functions based on performance only later to find that they don't work for all cases. I want to use this before I do performance testing. – Aseem Bansal Jul 19 '13 at 17:45
  • @AseemBansal: Then you probably want a warning. I've extended my answer. – user2357112 Jul 19 '13 at 17:52
  • My bad. I wasn't clear enough. I'll rephrase my earlier comment's last sentence. I want to do performance testing only if the conditions are true. Anyways, thanks for the tips. I'll definitely look at your answer again before using anything. – Aseem Bansal Jul 19 '13 at 17:59
  • Oh, wait, are you doing [unit testing](http://en.wikipedia.org/wiki/Unit_testing)? I was operating under the assumption that this was something like asserting invariants and preconditions inside a function. You might want to look at the [unittest](http://docs.python.org/2/library/unittest.html) module. – user2357112 Jul 19 '13 at 18:00
  • Wikipedia's definition fits exactly what I am trying to do. I don't know the technical terms so it is sometimes difficult to explain exactly the situation. I'll update the question with complete code to make it easier for people to understand what I am doing. – Aseem Bansal Jul 19 '13 at 18:04
1

While eval is useful, it wouldn't be my first choice (what happens if one of your conditions is blow_up()? Then eval(blow_up()) would be disastrous)

Instead, I'd recommend a slightly heavier, though powerful and extensible solution:
First, represent your conditions as tuples (function, parameter, comparison operator, comparison value)

conditions = [(rev_num, -34, opoerator.ne, 43), (rev_num, 34, opoerator.ne, 43)]

This can then be used as follows:

for func, param, op, num in conditions:
    op(func(param), num):
        print("%s(%s) %s %s" %(func.__name__, param, op.__name__, val))

The great thing about this representation is that you can extend it very easily to accommodate any comparator, any function, and any number of parameters for this function:

conditions = [(my_func, (-34, 35), opoerator.ne, 43)]
for func, params, comparator, val in conditions:
    if op(func(*params), val):
        print(something)

I should note: one downside here is that 'ne' is printed out instead of '!=', as may be desired. If this is for debugging purposes only, then you might not care too much. However, if this is part of being feature complete, then you might need to create a dict that maps between the funky operator.x function names and what you want their string representations to be

Hope this helps

inspectorG4dget
  • 110,290
  • 27
  • 149
  • 241
1

Answer for people that are in a situation where they can not use eval: Use a math library for python called sympy. To solve your example:

conditions = ['rev_num(-34) != -43', 'rev_num(34) != 43']

You could write this as:

condition = [str(rev_num(-34)) + ' != -43), str(rev_num(34)) + ' != 43]

then using sympy you can evalute if these statements are true or false:

for s in conditions:
  if not sympify(s)
    print(s) #prints if the condition is false

What is kinda neat about sympy though is that you can chain conditions using and/or written as | for or and & for and. so you could also do something like this:

conditions = '(' + str(rev_num(-34)) + ' != -43) & (' + str(rev_num(34)) + ' != 43)'  
sympify(conditions) #returns true or false depending on the conditions

You can also use make symbols/variables for sympy and use them in your conditions. (x != -43) & (y != 43) looks a bit nicer.

Zelnoth
  • 164
  • 7
0

If this is in a performance-critical part of your code and if this set of conditions is fixed (not user-given), I would duplicate the effort and normally code these conditions and log their corresponding string representation when they fail.

Dr. Jan-Philip Gehrcke
  • 33,287
  • 14
  • 85
  • 130
0

ast.literal_eval http://docs.python.org/2/library/ast.html?highlight=literal#ast.literal_eval

If that doesn't work for you, the ast module is a complete parsing infrastructure for python.

If your conditions all have a specific form, you could also just store the components separately.

Marcin
  • 48,559
  • 18
  • 128
  • 201