WasiLearn Code
Python · Control flow

Combining conditions

One if tests one thing. Most real decisions test several at once: old enough and holding a ticket, an admin or the owner, not already booked.

Python spells these operators as English words, which makes conditions read almost like the sentence you would say out loud.

age = 20
has_ticket = True

if age >= 18 and has_ticket:
    print("Come in")
else:
    print("Sorry")
output
Come in

and, or, not

and is true only when both sides are. or is true when at least one is. not flips whatever follows it.

print(True and False)
print(True or False)
print(not True)
print(not 0)
output
False
True
False
True

There is no && or || in Python. Typing them is a syntax error, which is a kindness — it fails immediately rather than doing something strange.

They stop as soon as the answer is known

and stops at the first falsy value; or stops at the first truthy one. Nothing to the right of the deciding value is evaluated at all.

def shout():
    print("shout ran")
    return True

print(False and shout())
print(True or shout())
print(True and shout())
output
False
True
shout ran
True

shout ran once out of three chances. This is short-circuit evaluation, and it is a tool rather than a curiosity — it lets you put a guard on the left and the thing that would fail on the right:

name = None

if name is not None and len(name) > 3:
    print("long name")
else:
    print("no usable name")
output
no usable name

Reverse those two tests and len(None) raises a TypeError. The order is load-bearing.

They return a value, not just True or False

Like the comparison in a spreadsheet, Python's and and or hand back one of the operands rather than a boolean.

print("chai" and "paratha")
print(0 and "paratha")
print("chai" or "paratha")
print(0 or "paratha")
print(None or 0 or "" or "last")
output
paratha
0
chai
paratha
last

and returns the first falsy value, or the last value if none are falsy. or returns the first truthy value, or the last one.

That makes or usable as a default — with the same trap every language has:

chosen_quantity = 0
quantity = chosen_quantity or 1

print(quantity)
output
1

The customer asked for zero. Zero is falsy, so or replaced it. or does not ask "is this missing?", it asks "is this falsy?", and when zero or an empty string is real data those are different questions. Say what you mean:

chosen_quantity = 0
quantity = chosen_quantity if chosen_quantity is not None else 1

print(quantity)
output
0

Chained comparisons

Python lets you write a range the way mathematics does, and it means what it looks like.

marks = 72

print(0 <= marks <= 100)
print(50 <= marks < 80)
output
True
True

0 <= marks <= 100 is genuinely one expression, not two comparisons glued together, and marks is evaluated only once. Most languages have no equivalent — in JavaScript that line silently produces nonsense — so it is worth using where it fits.

Chaining works for equality too, and a == b == c tests all three.

in, instead of repeating yourself

When a value has to match one of several options, or gets repetitive and easy to typo:

day = "Sat"

print(day == "Sat" or day == "Sun")
print(day in ("Sat", "Sun"))
output
True
True

The second reads better and cannot go wrong the way the first can — writing day == "Sat" or "Sun" is a classic bug that is always true, because "Sun" on its own is a non-empty string and therefore truthy.

day = "Wed"

print(day == "Sat" or "Sun")
output
Sun

No error, no warning, and the if around it would have run every single day. in makes that mistake impossible.

in also works on lists, sets, dictionary keys and strings:

print("cha" in "chai")
print(3 in [1, 2, 3])
print("chai" in {"chai": 12, "coffee": 4})
output
True
True
True

For a dictionary, in checks the keys, not the values.

not, and where to put it

booked = []

print(not booked)
print(len(booked) == 0)
output
True
True

Both say "the list is empty". The first is the idiomatic Python and reads well aloud.

Two spellings to prefer:

value = None
items = [1, 2]

print(value is None)
print(3 not in items)
output
True
True

is None rather than not value is None, and not in rather than not (x in y). Both are single operators and both read as English.

Precedence, and when to use brackets

not binds tightest, then and, then or. So this:

is_admin = False
is_owner = True
is_banned = False

print(is_admin or is_owner and not is_banned)
output
True

is read as is_admin or (is_owner and (not is_banned)). That happens to be what was meant, and relying on the reader to know the table is a poor bet. Brackets cost nothing:

is_admin = False
is_owner = True
is_banned = False

print(is_admin or (is_owner and not is_banned))
output
True

When a condition gets long enough that brackets stop helping, give the parts names:

hour = 14
is_weekend = False
staff_on_site = True

within_hours = 8 <= hour < 17
someone_to_serve = not is_weekend or staff_on_site

print(within_hours and someone_to_serve)
output
True

Three short lines instead of one long one, each printable on its own when the answer surprises you. That last property is the real argument: a named sub-condition can be debugged, an inline one cannot.

any and all

For a whole list of conditions, any and all say it in one word:

marks = [72, 45, 88]

print(all(mark >= 40 for mark in marks))
print(any(mark >= 85 for mark in marks))
print(all(mark >= 50 for mark in marks))
output
True
True
False

all is and across everything; any is or. Both short-circuit, so a list of a million items stops at the first failure.

Next: repeating things with loops.

Prefer learning with a teacher?

WasiLearn Academy runs small-group classes covering this material, online and in Karachi.

See the classes