29

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.

martineau
  • 119,623
  • 25
  • 170
  • 301
j0ker
  • 4,069
  • 4
  • 43
  • 65

3 Answers3

40

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).

mgilson
  • 300,191
  • 65
  • 633
  • 696
  • 2
    It took me a few seconds to come up with this. It's a corner case I hadn't considered before (I often work in Fortran which doesn't guarantee short-circuiting, but permits it) and I'm always trying to figure out how to make sure my expressions are short-circuited. Figuring this out was a bit backwards for me :). – mgilson Sep 05 '12 at 12:40
  • Yes the `all` is the way to go here, but where did you use list-comprehensions? I only see a simple list on your example. – rantanplan Sep 05 '12 at 12:49
  • @rantanplan -- you're right. (oops). I was thinking in the general case a list comprehension could be used. I'll fix that. – mgilson Sep 05 '12 at 12:51
  • Also note that the above form can be combined with the solution provided by @BigYellowCactus since `any` and `all` return boolean values. – mgilson Sep 05 '12 at 13:16
27

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():
   ...
sloth
  • 99,095
  • 21
  • 171
  • 219
  • 6
    More specifically, it will do a bitwise `and` on integers. Since bools happen to be derived from integers with `True == 1` and `False == 0`, this works. It won't (necessarily) work for other types or for functions which return things which aren't boolean. Still, it's a good tool to have around (+1) – mgilson Sep 05 '12 at 12:50
  • 1
    Definitely simpler than the solution by mgilson. Thanks for that! The other one might be more helpful for other people reading the code. Here, I guess you could think I just confused `and` and `&`. – j0ker Sep 05 '12 at 12:53
  • @mgilson The `all` function was also the first solution that came into my mind (I would use it for exactly the reason j0ker mentioned), but I discoverd the question to late for that answer (+1, btw) :-) – sloth Sep 05 '12 at 12:55
  • 6
    One advantage over my solution is that you aren't limited to just `and` **if all your results are boolean** (`True`,`False`,`1` or `0`). You can write an expression like `if foo() & baz() | bar()` which should behave as expected. – mgilson Sep 05 '12 at 13:12
  • I completely agree with the sentiment that this is generally wrong and works only in special cases, which is very different from the expected behaviour of logic operators in Python. – frnhr Jul 03 '18 at 21:34
  • 5
    Contrary to what some of the other comments here say, this answer is completely correct. In fact, this is specifically designed and intended behavior of bools. `bool` even has its own `bool.__and__` that returns `True` instead of `1` for `True & True`, and similarly for the other bitwise operators, so it's not just inheriting the int implementation (though it is compatible with the int behavior). – user2357112 Oct 09 '18 at 04:20
  • 2
    There *is* a potential pitfall with this, which `all` doesn't have - it only works if the return values are actually booleans, it won't test the "truthiness" of non-boolean values. For example, `all([1, 2])` is `True` because `1` and `2` are both truthy, but `1 & 2` is falsy. In some other cases `&` will raise a `TypeError`. – kaya3 May 20 '21 at 15:02
1

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`
martineau
  • 119,623
  • 25
  • 170
  • 301
Avinash
  • 17
  • 2