j0ker
j0ker

Reputation: 4139

How to prevent short-circuit evaluation?

This is a problem that occurred to me while working on a Django project. It's about form validation.

In Django, when you have a submitted form, you can call is_valid() on the corresponding form object to trigger the validation and return a Boolean value. So, usually you have code like that inside your view functions:

if form.is_valid():
    # code to save the form data

is_valid() not only validates the form data but also adds error messages to the form object that can afterwards be displayed to the user.

On one page I use two forms together and also want the data to be saved only if both forms contain valid data. That means I have to call is_valid() on both forms before executing the code to save the data. The most obvious way:

if form1.is_valid() and form2.is_valid():
    # ...

won't work because of the short circuit evaluation of logical operators. If form1 is not valid, form2 will not be evaluated and its error messages would be missing.

That's only an example. As far as I know, there is no greedy alternative to and/or as in other languages (i.e. Smalltalk). I can imagine that problem occurring under different circumstances (and not only in Python). The solutions I could think of are all kind of clumsy (nested ifs, assigning the return values to local variables and using them in the if statement). I would like to know the pythonic way to solve this kind of problem.

Upvotes: 31

Views: 9064

Answers (4)

ChrisoLosoph
ChrisoLosoph

Reputation: 607

Besides the existing good answers, there is also the ability to use comparisons as logic operators. In this case, no short circuit behaviour happens.

  • max(a,b) is a disjunction (logic OR)
  • min(a,b) is a conjunction (logic AND)
  • a > b is (a) & (not (b))
  • a < b is (not (a)) & (b)
  • a <= b is the implication a → b
  • == is an xnor or bi-implication a ↔ b
  • != is an xor

I am mentioning this because it can be easier to use than the bitwise &.

Imagine, you are looping over an array of samples sequentially and you try to test for a falling edge in the signal. You could do it with

if is_edge_detected := is_high_level > (is_high_level := signal_value > threshold):
    …

Using the bitwise operator & would be more difficult to write.

if is_edge_detected := is_high_level & (not (is_high_level := signal_value > threshold)):
    …

The main disadvantage is, the operator or function names are not so intuitive to read.

Upvotes: 0

Avinash
Avinash

Reputation: 17

You can use the Infix operators (ActiveState Python recipe) for defining your own Boolean operators:

aand = Infix(lambda x,y: bool(x) and bool(y))
1 |aand| 2  # Will return `True` instead of `1`

Upvotes: 1

mgilson
mgilson

Reputation: 310117

How about something like:

if all([form1.is_valid(), form2.is_valid()]):
   ...

In a general case, a list-comprehension could be used so the results are calculated up front (as opposed to a generator expression which is commonly used in this context). e.g.:

if all([ form.is_valid() for form in (form1,form2) ])  

This will scale up nicely to an arbitrary number of conditions as well ... The only catch is that they all need to be connected by "and" as opposed to if foo and bar or baz: ....

(for a non-short circuiting or, you could use any instead of all).

Upvotes: 42

sloth
sloth

Reputation: 101142

You can simply use the binary & operator, which will do a non-short-circuit logical AND on bools.

if form1.is_valid() & form2.is_valid():
   ...

Upvotes: 30

Related Questions