3

Is there a way (using eval or whatever) to evaluate eagerly boolean expressions in python?

Let's see this:

>>> x = 3
>>> 5 < x < y
False

Yikes! That's very nice, because this will be false regardless of y's value. The thing is, y can be even undefined, and I'd like to get that exception. How can I get python to evaluate all expressions even if it knows the result beforehand?

Hope I made myself clear! Thanks,
Manuel

Edit: Please bear in mind that the expression must not be modified, just the evaluation technique.

AstroCB
  • 12,337
  • 20
  • 57
  • 73
Manuel Araoz
  • 15,962
  • 24
  • 71
  • 95

6 Answers6

6
(5 < x) & (x < y)

By using the bit-and operator, &, you get no short-circuiting behavior (as you get with and, or, chaining, all/any). Short-circuiting is normally deemed desirable (fast &c) but it's not hard to do without it if you really want;-).

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • 1
    I am not sure this is the best solution. If I read this I would think "This person doesn't know how to use `and` and `&`, let alone about chained comparison operators", not "Ohhhh, whoever wrote this was forcing evaluation of both arguments," except maybe right after my coffee. – Mike Graham Feb 16 '10 at 06:48
  • 3
    But, I suppose any answer to this question is going to be horrible. – Glenn Maynard Feb 16 '10 at 07:05
  • 3
    No, some answers are more horrible than others. This is very subtle, and the intent isn't clear on inspection (it just looks like a misuse of bitwise operators), which makes it far more horrible than, say, Mike Graham's answer, or in particular too much php's answer (clever [in a good way], if not general-case!) – Devin Jeanpierre Feb 16 '10 at 07:25
5
all([5 < x, x < y])
Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • 1
    Nope, that short-circuits too. – Alex Martelli Feb 16 '10 at 06:22
  • 3
    @Alex: Even when creating the list? – Ignacio Vazquez-Abrams Feb 16 '10 at 06:23
  • 2
    No, `all` short-circuits but list-comprehension is eager (genexp is the lazy equivalent). So `x < y` _is_ going to get evaluated -- if _it_ raises an exception that propagates -- but the implied `and` is still not going to use its result (when `x` is 3), so any exceptions coming from _that_ use (for peculiar `y`s indeed;-) are still, potentially, going to be hidden. A non-short-circuiting operator like `&` (bitwise-and) gets 100% of the way there (getting any exception that might possibly be coming your way), while your approach might come up (not by much) slightly short. – Alex Martelli Feb 16 '10 at 06:29
  • 1
    Creation of the list forces every single element to be evaluated. The only step that will be skipped is that of calling `__nonzero__()` or `__bool__()` on the results now stored in each element. This has no side effects with regards to `bool`, so we can ignore this minor difference for relational operators. – Ignacio Vazquez-Abrams Feb 16 '10 at 06:33
  • I couldn't really understand that explanation. Do you mean, for example, if x.__lt__ raises an exception? – Jimmy Feb 16 '10 at 06:35
  • @Jimmy What happens is, first a list is created. This list contains `5 < x` as the first element; `x < y` as the second. So it's a list of bools: if x or y is undefined, an exception will be raised here. Then all() walks through this list one item at a time until it has either exhausted the list (it returns True), or it encounters a false value (it returns False). It short circuits in that it doesn't consume all the iterable, which may skip side effects (like exceptions) if they occur during iteration or when checking whether an element is false. Lists and bools don't have such side effects. – Devin Jeanpierre Feb 16 '10 at 07:33
  • Mike Graham's response below is much preferred over this one in Python because it is explicit, whereas the effect you are intending it to perform is not explicitly understood here. – Nathan Garabedian Feb 22 '13 at 23:06
5

The most natural way would probably be to evaluate the expressions on prior lines.

a = foo()
b = bar()
if a and b:
    ...

as solutions like all([5 < x, x < y]) hide that the side effects are important and solutions using bitwise and (&) seem subtle and misusing—both of these would require a comment in your code to make it obvious you are forcing evaluation and will cause people reading your code to think What was he thinking???. Putting important calculations on their own lines makes more sense than hiding them within subtle, at-first-glance ugly code.

Though my solution doesn't prevent a NameError if b does not exist (i.e., you have a typo) and a is false, this is something you should be able to figure out by reading your code and using a bugfinder if you choose.

Mike Graham
  • 73,987
  • 14
  • 101
  • 130
  • 1
    In my opinion, this answer is the most pythonic. You are explicitly defining operations you want to occur and then evaluating them. – Nathan Garabedian Feb 22 '13 at 23:07
  • This seems like the best solution. Perhaps this is why many languages do not include support for eager operator(see https://en.wikipedia.org/wiki/Short-circuit_evaluation#Support_in_common_programming_and_scripting_languages) – dariusz gorczynski Jan 24 '22 at 18:50
3
>>> x = 3
>>> y > x > 5
Traceback (most recent call last):
  File "", line 1, in 
NameError: name 'y' is not defined
too much php
  • 88,666
  • 34
  • 128
  • 138
2

If it's just the possibility of programmer-error you want to preclude, eagerly evaluating expressions won't do much. For instance, mistakenly doing x or y() instead of x() or y() won't be detected. Perhaps you're actually looking for tools like pylint, pyflakes or pychecker.

Thomas Wouters
  • 130,178
  • 23
  • 148
  • 122
  • Yes, but I need to do this dynamically, and this tools seem to work like command-line executables. Any framwork/module you know that does the same given a ´str´? – Manuel Araoz Feb 16 '10 at 07:44
1

If you are receiving the statement from the user and want to execute it with your own semantics, you should parse it yourself with a tool such as pyparsing. It is messy and insecure to evaluate someone else's code in the middle of yours, mixing their results with yours and it is confusing to evaluate what looks to be Python code but with different semantics.

Mike Graham
  • 73,987
  • 14
  • 101
  • 130