FreeCampus Python

Combining Conditions Without Surprises

Turn several facts into readable Boolean rules, use short-circuit evaluation as a safety guard, and avoid collapsing meaningful falsy states.
python-foundations decisions-repetition boolean-logic
Open in Colab
  • Level: Python Foundations · Unit 4
  • Estimated time: 3–4.5 hours
  • You will learn: Name and combine Boolean facts, group mixed rules, guard unsafe operations with short-circuiting, and preserve distinctions such as None versus zero.
  • Practice in: Google Colab, JupyterLab, or a local editor

1. Name the facts before combining the rule

A museum security console unlocks an archive only for an authorized visitor when the room is open and no alarm is active. One dense condition can express that, but named facts let a reader inspect the reasoning:

badge = "researcher"
hour = 14
alarm_active = False

is_authorized = badge in {"researcher", "curator"}
is_open = 9 <= hour < 17
is_safe = not alarm_active
can_enter = is_authorized and is_open and is_safe

print(is_authorized, is_open, is_safe)
print("Unlock archive:", can_enter)

Each comparison or membership test creates a Boolean. The final rule combines facts whose names say what they mean. If entry is unexpectedly denied, the three printed values immediately narrow the cause.

Unit 2 established how comparisons, truthiness, and, or, and not work. Here you will use those rules to design control-flow questions that remain readable at boundaries and safe when some operations are not always valid.

Questions this lesson will answer

  • How do truth tables expose a rule’s complete behavior?
  • Where do parentheses matter when and and or appear together?
  • How can evaluation order prevent an invalid lookup or index?
  • Why do None, 0, "", and [] sometimes need different outcomes?
  • When do all() and any() make a collection of facts clearer?

2. Truth tables turn words into evidence

For two facts, enumerate every combination instead of reasoning from one happy case:

is_authorized is_open is_authorized and is_open is_authorized or is_open
False False False False
False True False True
True False False True
True True True True
  • and requires every combined requirement.
  • or accepts at least one alternative.
  • not reverses one truth value.

Translate policy words deliberately:

Policy wording Typical Boolean shape
“both,” “all,” “must also” A and B
“either,” “at least one” A or B
“unless,” “not,” “no alarm” often includes not A
“exactly one” (A and not B) or (B and not A)

Do not assume everyday “or” means exactly one. Python’s or is inclusive: it is true when one or both sides are true.

has_key = True
knows_code = True

print(has_key or knows_code)
print((has_key and not knows_code) or (knows_code and not has_key))

The first result is true; the second is false because both alternatives are present.

3. Parentheses make mixed rules visible

Python evaluates not before and, and and before or. Therefore:

is_staff = False
has_ticket = True
is_open = False

allowed = is_staff or has_ticket and is_open
print(allowed)

means:

allowed = is_staff or (has_ticket and is_open)

A staff member can enter even while closed. If the intended rule is “the room must be open, and the person must be staff or have a ticket,” write:

allowed = (is_staff or has_ticket) and is_open

The parentheses are not decoration. They identify one grouped idea before it is combined with another. Even when precedence would produce the same answer, use parentheses when the policy has an obvious phrase boundary.

A quick review technique is to substitute Boolean values:

print(False or True and False)
print((False or True) and False)

Both happen to be false here, so also test a case that distinguishes the shapes:

print(True or False and False)    # True
print((True or False) and False)  # False

Checkpoint: translating policies

4. Short-circuiting can protect an unsafe lookup

Python evaluates and from left to right. If the left side is falsy, the whole and expression cannot become truthy, so Python skips the right side:

codes = []

has_primary_code = bool(codes) and codes[0] == "A7"
print(has_primary_code)

bool(codes) is false, so codes[0] is never evaluated. No IndexError occurs. Reverse the operands and the guard arrives too late:

codes = []
has_primary_code = codes[0] == "A7" and bool(codes)

Python attempts the unsafe index first and raises IndexError. A guard must be on the left of the operation it protects.

A dictionary example uses membership before indexing:

visitor = {"name": "Mina"}

has_active_pass = (
    "pass" in visitor
    and visitor["pass"] == "active"
)
print(has_active_pass)

The membership test is false, so the missing key lookup is skipped. This is a control-flow effect inside an expression: not every written operand necessarily runs.

ImportantA safety guard should prove the next operation is valid

data and data[0] protects an index because a truthy sequence has at least one item. "pass" in visitor and visitor["pass"] == "active" protects a dictionary lookup because membership proves the key exists.

5. or also stops when the outcome is known

For or, a truthy left operand already determines success, so Python skips the right side:

has_master_key = True
entered_code = "wrong"

can_open = has_master_key or entered_code == "A7"
print(can_open)

The code comparison is not needed. Short-circuiting is observable when the right side would fail:

manual_override = True
settings = {}

can_start = manual_override or settings["mode"] == "automatic"
print(can_start)

This runs because the lookup is skipped. That does not mean missing settings are always acceptable; it means the policy explicitly permits the override to make the other fact irrelevant.

Do not hide important work behind and or or merely to save lines. Use a normal conditional when the skipped action changes state, needs an explanation, or would surprise a reader.

6. and and or return operands, not forced Booleans

Unit 2 introduced this rule. It matters when a condition also constructs data:

label = ""
fallback = "untitled exhibit"

display_label = label or fallback
print(display_label)

Because label is falsy, or returns the second operand string. If label were "Meteor Map", it would be returned unchanged.

This defaulting pattern is safe only when every falsy first value means “absent.” It is wrong if 0 is meaningful:

requested_floor = 0
floor = requested_floor or 1
print(floor)  # 1, although floor 0 was requested

Preserve the distinction explicitly:

floor = 1 if requested_floor is None else requested_floor
assert floor == 0

When you need a real Boolean for state or output, use a comparison or bool(...) instead of relying on the returned operand.

Checkpoint: evaluation order and guards

7. De Morgan’s laws help invert a complete rule

Suppose entry is allowed when a visitor is authorized and the alarm is not active:

can_enter = is_authorized and not alarm_active

The denial condition is the negation of that whole expression:

must_deny = not (is_authorized and not alarm_active)

De Morgan’s laws give an equivalent form:

must_deny = (not is_authorized) or alarm_active

The two transformations are:

  • not (A and B) is equivalent to (not A) or (not B);
  • not (A or B) is equivalent to (not A) and (not B).

Notice that the connective changes as each fact is negated. Test all Boolean combinations when an inversion controls access, safety, payment, or another important policy.

for authorized in [False, True]:
    for alarm in [False, True]:
        original = not (authorized and not alarm)
        rewritten = (not authorized) or alarm
        assert original == rewritten

This small exhaustive check is possible because two Boolean facts have only four combinations.

8. Impossible ranges reveal a broken rule

Some conditions can never be true:

age = 20
impossible = age < 13 and age >= 18
print(impossible)

No one value can be below 13 and at least 18 simultaneously. Perhaps the intended condition was age < 13 or age >= 18, describing values outside 13 through 17.

Other rules are always true:

always_true = age >= 13 or age < 18

Every number satisfies at least one side, including the overlapping middle. Writing a few boundary values—12, 13, 17, and 18—usually reveals the problem. Lesson 3 expands this technique into decision tables.

9. all() and any() combine a collection of facts

When facts already exist as a collection, built-ins can communicate the rule:

checks = [True, True, False]

print(all(checks))
print(any(checks))
  • all(checks) is true only if every item is truthy.
  • any(checks) is true if at least one item is truthy.

They also short-circuit while visiting the collection: all() can stop at the first falsy item; any() can stop at the first truthy item.

The empty cases are deliberate:

assert all([]) is True
assert any([]) is False

“All zero checks passed” is true because no failing check exists. “At least one of zero checks passed” is false because no successful check exists. Whether an empty collection should be accepted by your application is a separate policy; you can require both non-emptiness and all(checks):

checks = []
ready = bool(checks) and all(checks)
assert ready is False

Checkpoint: complete Boolean reasoning

10. Build a museum security console

Use these inputs and create named facts rather than one unreadable condition:

visitor = {"role": "researcher", "pass": "active"}
hour = 16
alarm_active = False
restricted_exhibit = True
escort_present = True

access = None
reason = None

The policy is:

  1. The museum is open from hour 9 through 16; hour 17 is closed.
  2. The visitor is authorized when the role is "curator" or "researcher".
  3. A pass is valid only if the "pass" key exists and equals "active".
  4. No one enters while the alarm is active.
  5. A restricted exhibit additionally requires either a curator or an escort.
  6. Assign Boolean access and one specific reason. Check general failures in this order: closed museum, alarm, authorization/pass, restricted access.
  7. Do not index the pass before proving its key exists.

Run these checks for the supplied case:

assert is_open is True
assert is_authorized is True
assert has_active_pass is True
assert restriction_satisfied is True
assert access is True
assert reason == "access granted"

Then try a missing pass, hour = 17, an active alarm, and a researcher at the restricted exhibit without an escort. For each case, predict the first failing fact and the resulting reason.

Hint: build facts from simple to dependent

Create is_open, is_authorized, and has_active_pass separately. The pass fact should use membership on the left of and. Define the restricted-exhibit fact so that unrestricted exhibits pass without needing an escort. Use a branch chain to choose the most useful reason.

Show one complete solution after attempting the lab
visitor = {"role": "researcher", "pass": "active"}
hour = 16
alarm_active = False
restricted_exhibit = True
escort_present = True

is_open = 9 <= hour < 17
is_authorized = visitor["role"] in {"curator", "researcher"}
has_active_pass = (
    "pass" in visitor
    and visitor["pass"] == "active"
)
restriction_satisfied = (
    not restricted_exhibit
    or visitor["role"] == "curator"
    or escort_present
)

if not is_open:
    access = False
    reason = "museum closed"
elif alarm_active:
    access = False
    reason = "alarm active"
elif not (is_authorized and has_active_pass):
    access = False
    reason = "authorization failed"
elif not restriction_satisfied:
    access = False
    reason = "escort required"
else:
    access = True
    reason = "access granted"

The branch chain reports one precedence-ordered reason. The named facts preserve which part of the policy each expression represents.

11. Explain the choices

  1. Why are named Boolean facts easier to debug than one dense condition?
  2. How can parentheses change a policy that mixes and and or?
  3. What must a left-hand guard prove before Python reaches an unsafe right side?
  4. Why is value or default unable to preserve every valid zero?
  5. What do all([]) and any([]) mean, and when might a policy additionally require a non-empty input?

Key points

TipKey points
  • Name meaningful facts, then combine those names into the policy.
  • Parenthesize mixed and/or rules according to their logical phrases.
  • and skips its right operand after a falsy left operand; or skips it after a truthy left operand. Put safety guards before the operations they protect.
  • Truthiness can collapse meaningful states such as zero and None; compare explicitly when the distinction matters.
  • De Morgan’s laws invert a whole rule, and boundary cases reveal impossible or always-true ranges.
  • all() and any() combine collections of facts and have deliberate empty behavior.

References

Back to top