Skip to content
VLSI Mentor

Python Part I · Module 9 · Conditional Statements

Comparison and Logical Conditions

You already own every operator this chapter uses. Module 3 taught comparisons, and, or, not, and membership as machinery for building expressions, and at the time the result was something to print. Now the result selects which code runs, and that raises the stakes on exactly the same syntax: a wrong expression produces a wrong number you can see, while a wrong condition runs the wrong block and produces a plausible answer you cannot. This chapter is about writing conditions that say what you mean — and about three specific conditions that are perfectly valid Python, raise nothing at all, and quietly mean something else.

Foundation16 min readPythonPart IFundamentalsConditionalsBoolean Logic

Part I · Module 9 · Chapter 9.3 · Comparison and Logical Conditions

1. Comparing Two Values

The most common condition compares something to a known value. Chapter 3.4 covered the six operators; here they are doing their real job:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
status = "FAIL"
errors = 4

if status != "PASS":
    print("not a clean run")

if errors >= 4:
    print("at least four errors")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
not a clean run
at least four errors

Nothing has changed about the operators. status != "PASS" produces True, exactly as it would in a print. What is new is that the value is consumed by the if rather than displayed.

Two comparisons are worth pausing on, because both have cost people time.

Equality across numeric types works the way the mathematics does:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
print(1 == 1.0)
print(4210 == int("4210"))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
True
True

Equality between a string and a number does not:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
print("4210" == 4210)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
False
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
seed = "4210"

if seed == 4210:
    print("matched")
else:
    print("did not match")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
did not match

The string "4210" and the integer 4210 are different values, and no amount of looking alike changes that. This matters because text read from a file or a log arrives as a string: a condition comparing it to a number is always false, silently. Chapter 2.11 covered the conversion; the condition is where forgetting it shows up.

2. = Is Not ==

Assignment and comparison are different operators, and using the first where the second belongs is the classic beginner error. Python refuses outright:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
status = "FAIL"

if status = "PASS":
    print("yes")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  File "bad.py", line 3
    if status = "PASS":
       ^^^^^^^^^^^^^^^
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?

Modern Python recognises the mistake and names the likely fix. = binds a name to a value — it is a statement, and a statement cannot be a condition. == compares two values and produces True or False, which is what a condition needs.

3. Combining Conditions

and, or and not combine conditions into one, as Chapter 3.5 established.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
errors = 0
warnings = 3

if errors == 0 and warnings > 0:
    print("clean, but review the warnings")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
clean, but review the warnings
  • A and B is true when both are true.
  • A or B is true when at least one is true.
  • not A is true when A is false.

One precedence fact is worth memorising because it changes answers: and binds more tightly than or, so A or B and C means A or (B and C).

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
print(True or False and False)
print((True or False) and False)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
True
False

Same three values, opposite results. Chapter 3.8 made the general case; the rule in practice is simpler than the precedence table:

4. Short-Circuit: and and or Stop Early

and and or do not always evaluate both sides. and stops at the first operand that is false — the answer cannot change after that. or stops at the first that is true.

This is not a performance footnote. It is the mechanism that makes a guard work.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
denominator = 0

if denominator != 0 and 100 / denominator > 5:
    print("large ratio")
else:
    print("skipped the division")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
skipped the division

denominator != 0 is False. The and can stop there, so 100 / denominator is never evaluated — and that division would have raised.

The proof is to swap the two halves and change nothing else:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
denominator = 0

if 100 / denominator > 5 and denominator != 0:
    print("large ratio")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
ZeroDivisionError: division by zero

Identical operands, identical operator, opposite outcome. In the first version the guard runs first and stops the chain; in the second the dangerous expression runs before anything can protect it.

Put the condition that makes the next one safe on the left. That ordering is the whole technique, and you will use it constantly once Chapter 9.4 pairs it with dictionary lookups.

5. Membership Conditions

in asks whether a value is present in a collection. It reads like English and works on every collection you have met:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
line = "UVM_ERROR axi_smoke"
tests = ["smoke", "reset"]
failed = {"reset", "crc"}

print("ERROR" in line)
print("smoke" in tests)
print("crc" in failed)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
True
True
True
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
line = "UVM_ERROR axi_smoke"

if "UVM_ERROR" in line:
    print("this line reports an error")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
this line reports an error

On a dictionary, in checks the keys — the rule Chapter 8.2 established, arriving where it finally matters:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
status = {"smoke": "PASS", "reset": "FAIL"}

print("reset" in status)
print("FAIL" in status)
print("FAIL" in status.values())
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
True
False
True

"FAIL" in status is False even though "FAIL" is plainly in there — as a value, not a key. If you mean to search the values, say so with .values().

Membership also replaces a chain of equality tests, and reads better than one:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
status = "WARN"

if status in {"PASS", "WARN"}:
    print("acceptable")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
acceptable

That is cleaner than status == "PASS" or status == "WARN", and Section 7 shows what happens when someone tries to shorten it the wrong way.

6. Checking for None

None is the value that means no value yet. Chapter 3.6 introduced is as the identity operator, and None is the case it exists for:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
result = None

if result is None:
    print("no result recorded yet")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
no result recorded yet

Write is None, not == None. There is exactly one None object in a running program, so asking whether a value is that object is both the direct question and the conventional spelling. Every Python reviewer expects is None, and seeing == None is a reliable signal that the author was unsure.

7. The or Trap

Here is a condition that looks like a natural shortening of "status is PASS or WARN", is valid Python, and is wrong:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
status = "FAIL"

if status == "PASS" or "WARN":
    print("accepted")
else:
    print("rejected")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
accepted

The status is "FAIL" and the program accepted it. Worse, it will accept everything — there is no input for which that condition is false.

The reason is that or combines two complete conditions, and the second one here is not a comparison at all. Python reads the line as:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
status = "FAIL"
print((status == "PASS") or "WARN")
print(bool("WARN"))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
WARN
True

The left side is False, so or moves to the right side, which is the bare string "WARN" — and a non-empty string counts as true. The condition is therefore true no matter what status contains. (Chapter 9.5 covers why a string counts as true at all; here it is enough that it does.)

Both repairs state the second condition in full:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
status = "FAIL"

if status == "PASS" or status == "WARN":
    print("accepted")
else:
    print("rejected")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
rejected
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
status = "FAIL"

if status in {"PASS", "WARN"}:
    print("accepted")
else:
    print("rejected")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
rejected

The membership form is the better of the two: one mention of status, and adding a third acceptable value means adding one word.

8. Conditions That Can Never Be True

A compound condition can be contradictory, and Python will evaluate it faithfully forever.

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
temperature = 50

if temperature > 80 and temperature < 20:
    print("outside the safe range")
else:
    print("inside the safe range")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
inside the safe range

The branch is unreachable for every possible temperature: no number is both above 80 and below 20. The author meant outside the range, which is or:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
temperature = 50

if temperature > 80 or temperature < 20:
    print("outside the safe range")
else:
    print("inside the safe range")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
inside the safe range
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
temperature = 95

if temperature > 80 or temperature < 20:
    print("outside the safe range")
else:
    print("inside the safe range")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
outside the safe range

The rule to carry: and narrows, or widens. Two conditions on the same quantity joined by and describe the overlap between them, and if they do not overlap the result is always false. Joined by or they describe everything either one covers.

For inside a range, Python allows the chained form, which is unambiguous and reads like the mathematics:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
temperature = 50

print(20 <= temperature <= 80)
print(80 < temperature < 20)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
True
False

9. not, and Where Negation Goes Wrong

not inverts a condition, and it binds more tightly than and and or but less tightly than a comparison:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
a = 3
b = 4

print(not a == b)
print(a != b)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
True
True

not a == b is read as not (a == b), not as (not a) == b. It agrees with a != b — and a != b is what to write. Negating a comparison when a negative comparison already exists adds a step for the reader with nothing in return.

Where not earns its place is in front of something that is not a comparison:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
rerun_enabled = False

if not rerun_enabled:
    print("reruns are off")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
reruns are off

Reserve not for inverting a whole condition, especially a compound one or a membership test:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tests = ["smoke", "reset"]

if "burst" not in tests:
    print("burst is not in the suite")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
burst is not in the suite

Python spells that not in, as one operator, which reads better than not ("burst" in tests) and means the same thing.

10. A Condition Table

Collected for reference — each row is a question and the condition that asks it:

QuestionCondition
are these equalstatus == "PASS"
are these differentstatus != "PASS"
is it at or past a thresholdcoverage >= 90
is it inside a range20 <= temp <= 80
is it outside a rangetemp < 20 or temp > 80
are both trueerrors == 0 and finished
is either truefatal or errors > 0
is it one of several valuesstatus in {"PASS", "WARN"}
is it present in a collection"UVM_ERROR" in line
is it absent"burst" not in tests
is it a key of this dictionary"reset" in status
is there no value yetresult is None

11. Predict, Run, Observe

Four conditions. Predict each, then read the output. Two of them are bugs.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# A
errors = 0
warnings = 0
print(errors == 0 and warnings == 0)

# B
seed = "4210"
print(seed == 4210)

# C
status = "FAIL"
print(status == "PASS" or "PASS")

# D
runtime = 3600
print(runtime > 7200 and runtime < 1800)

The verified output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
True
False
PASS
False

A — both halves are true, so the result is True. Nothing surprising; it is here as the baseline.

B — False, because a string is never equal to an integer. If this were a condition, its branch would never run.

C — not True or False at all. or returns the first operand that counts as true, and that is the string "PASS" itself. Used as a condition it would behave as true for any status — the Section 7 bug, printed rather than branched on.

D — False, and false for every possible runtime. Nothing is both above 7200 and below 1800.

12. Exercises

Exercise 1 — write the condition. Write the if line only, for each: (a) the log line contains UVM_FATAL (b) coverage is below 90 (c) the run finished and recorded no errors (d) the status is one of FAIL, FATAL or TIMEOUT (e) "reset" has no entry in the status dictionary (f) result has not been set yet.

Exercise 2 — find the always-true condition. One of these is true for every input. Identify it, explain why in one sentence, and repair it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if tool == "vcs" or "xrun":
    ...
if errors > 0 or warnings > 0:
    ...
if status in {"PASS", "FAIL"}:
    ...

Exercise 3 — find the never-true condition. Same three-part task for this set.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if width >= 8 and width <= 4:
    ...
if name != "clk" and name != "rst":
    ...
if "FAIL" in {"smoke": "FAIL"}:
    ...

Exercise 4 — order the guard. values is a list and index an integer. Write a condition that is true when index is a valid position in values and the item there equals "FAIL". Then write it in the wrong order and run both with values = [] and index = 0. Record what each does.

Exercise 5 — the string-number trap. seed_text = "4210". Write two conditions testing whether the seed is 4210: one that is always false, and one that works. Explain the difference in one sentence.

Exercise 6 — parenthesise. For fatal = False, errors = 2, reviewed = False, evaluate fatal or errors > 0 and reviewed by hand using the precedence rule, then verify. Then write the two parenthesised versions and give the result of each.

Exercise 7 — key or value. For status = {"smoke": "PASS", "reset": "FAIL"}, predict each of "smoke" in status, "PASS" in status, "PASS" in status.values() and "smoke" in status.values(), then verify all four and explain the pattern in one sentence.

Exercise 8 — simplify. Rewrite each more directly, and say what you removed: (a) if not count == 0: (b) if status == "PASS" or status == "WARN" or status == "INFO": (c) if not ("burst" in tests): (d) if result == None:

13. Summary

  • A condition is an expression, and every operator from Module 3 works unchanged inside one.
  • A string is never equal to a number. "4210" == 4210 is False, which makes conditions over text read from a file silently never match.
  • = cannot appear in a condition. Python raises a SyntaxError and suggests ==.
  • and binds more tightly than or. Parenthesise any condition that mixes them.
  • and stops at the first false operand, or at the first true one. Swapping the halves of a guard turns a clean skip into a ZeroDivisionError.
  • Put the condition that makes the next one safe on the left.
  • in checks membership, and on a dictionary it checks keys — "FAIL" in status is false when "FAIL" is a value.
  • status in {"PASS", "WARN"} beats a chain of ==, and extends by one word.
  • Use is None, not == None.
  • if x == "A" or "B": is always true. Each side of an or must be a complete condition on its own.
  • and narrows, or widens. Two non-overlapping conditions joined by and can never be true.
  • Prefer a != b to not a == b, and not in to a negated in.

The next chapter puts two decisions together, and asks when a nest is the right shape and when one combined condition is clearer.

Where this fits

Part of the Python curriculum.