3

I just ran into a weird bug in my code due to a typo where I had used a comma in a conditional rather than an and keyword. See the code below for a watered down example:

def foo():
    s = set([1, 2, 3])
    a = 4
    b = 5
    if (a, b in s):      # Should have been: if (a and b in s):
        print "Foo"

foo()  # prints "Foo"

Why does the conditional evaluate to True? Even if a is None, "Foo" gets printed.

Tonechas
  • 13,398
  • 16
  • 46
  • 80
Joseph
  • 12,678
  • 19
  • 76
  • 115
  • 2
    I think you have created a tuple. It consists of `a` and the result of `b in s` and you are testing the truthiness of the tuple. – Paul Rooney Jan 05 '17 at 03:31

4 Answers4

7

The expression (a, b in s) is a valid Python tuple. It will evaluate to Truth, regardless of its contents, as long as it has at least one element. (And this one has two).

jsbueno
  • 99,910
  • 10
  • 151
  • 209
3

It's because (a, b in s) evaluates to the length-2 tuple (4, False), and in python a tuple is considered to be True if it has nonzero length.

Tom McDermott
  • 221
  • 1
  • 3
2

The reason the code prints 'Foo' regardless of the value of a and wether b is in s is the the line:

(a, b in s)

is actually a tuple. The if statement checks if the tuple exists, and it does so Foo gets printed. When

s=set([1,2,3])
a=None
b=5

the condition checked is

if(None, False):

Which is always true because

(None, False) #is a non empty tuple.
Xero Smith
  • 1,968
  • 1
  • 14
  • 19
0

As others have said, the expression a, b in s creates a tuple, and a non-empty tuple is truthy, i.e., it evaluates to True in a boolean context. Note that the commas create the tuple, not the parentheses, although parentheses are required in some contexts to avoid ambiguity, eg when passing a tuple literal as a single function argument.

BTW, parentheses are not required around the condition expression of an if statement in Python. They don't hurt, but the usual style convention is to omit them as unnecessary clutter.

Note that if a and b in s: does not test if both a and b are elements of the set s. To perform that test you should use the set.issuperset method:

if s.issuperset((a, b)):

Note that we need the extra parentheses here because we are passing the tuple a, b as a single argument.

That statement can also be written using the operator form of .issuperset:

if s >= set((a, b)):

but when using that form both operands must be sets (or frozensets), whereas the full method will accept any iterable as the other arg.

But getting back to your code... The expression

a and b in s

is equivalent to

a and (b in s) 

If a is false-ish (i.e., False, 0, or an empty collection) the expression evaluates to a and the second part of the and expression is ignored. Otherwise (when a is truth-ish) the second part of the and expression is evaluated and that becomes the result.

In other words, if a == 0 then a and b in s results in 0, but if a is any other number then a and b in s will result in True if b is in s, and it will result in False if b is not in s. Please see my answer to Assigning string with boolean expression for further info (with numerous examples).

Community
  • 1
  • 1
PM 2Ring
  • 54,345
  • 6
  • 82
  • 182