Python Part I · Module 3 · Operators and Expressions
Membership and Identity: in, not in, is
Two operators that answer questions the ones so far cannot. The first, in, asks whether something appears inside something else — whether a marker is present in a line of text, for instance — and it is one of the most useful things in Python precisely because it reads exactly like the English word. The second, is, looks deceptively similar to the equality comparison from Chapter 3.4 and asks something genuinely different. That difference causes real bugs when people treat the two as interchangeable, so this chapter takes a deliberately narrow position on it: there is one everyday use of is that you should adopt as a habit, and outside that one case the honest advice for now is to use the equality comparison instead.
Foundation9 min readPythonPart IFundamentalsMembershipIdentity
Part I · Module 3 · Chapter 3.6 · Membership and Identity: in, not in, is
1. Does This Appear Inside That?
line = "0 errors, 3 warnings"
print("errors" in line)
print("FATAL" in line)True
Falsein asks whether something appears inside something else, and produces a bool — so it is an expression like every other one in this module.
Read it as the English word. Is "errors" in line? Yes. Is "FATAL" in line? No.
There is a negative form that reads just as naturally:
line = "0 errors, 3 warnings"
print("FATAL" not in line)Truenot in is one operator, written as two words. It is the same question with the answer flipped, and it reads better than wrapping the whole thing in not.
2. What Counts as Inside
For text, in looks for the characters appearing in sequence anywhere in the larger piece of text.
print("err" in "0 errors")
print("rror" in "0 errors")
print("sorre" in "0 errors")True
True
False"err" and "rror" both appear as runs of characters. "sorre" uses the same letters but not in that order, so it does not.
It is also case-sensitive, exactly as == was in Chapter 3.4 §4:
print("ERRORS" in "0 errors")False3. Why This Is Worth Having
A single readable line replaces a surprising amount of work:
line = "UVM_ERROR @ 1200 ns: scoreboard mismatch"
print("UVM_ERROR" in line)
print("UVM_FATAL" in line)True
FalseThat is the core of every log checker ever written. In Part II it appears inside a loop over a file, but the test itself is exactly this line and nothing more.
4. A Different Question: is
Now the second half, and it needs care.
Chapter 3.4 taught ==, which asks do these two values compare equal?
is asks something else: are these two names referring to the very same value? Not an equal one — the same one.
That sounds like a fine distinction and usually it does not matter, which is exactly why people reach for is when they mean == and get away with it until they do not.
5. The One Use You Should Adopt
Here is the rule worth taking away:
To check whether something is None, use is None.
first_error = None
print(first_error is None)
first_error = "timeout"
print(first_error is None)
print(first_error is not None)True
False
Trueis not is the negative form, written as two words like not in.
Why this rather than == None? Because there is exactly one None in Python — Chapter 2.8 §5 said it is the only value of its type. So "is this the None value" is precisely an identity question, and is None says exactly what is meant.
== None usually gives the same answer, but it asks a subtly different question, and is None is what every Python codebase you will read uses.
6. Do Not Use is for Anything Else Yet
Being direct about this, because the alternative is a habit that produces intermittent bugs.
a = 4210
b = 4210
print(a == b)
print(a is b)True
TrueBoth answered True, which looks like confirmation that the two operators are interchangeable. They are not. That second answer depends on how Python happened to produce those two values, and it is not something to rely on.
Here is the same question where the value is worked out while the program runs rather than written down directly:
a = 500000
b = 499999
b += 1
print(a == b)
print(a is b)True
Falsea and b now hold the same number — == says so. But is says False, because they are not the same value; they are two equal ones, arrived at by different routes.
The same thing happens with text:
name_1 = "axi_smoke"
name_2 = "axi_"
name_2 += "smoke"
print(name_1 == name_2)
print(name_1 is name_2)True
FalseTwo pieces of text containing exactly the same characters, and is still says no.
Nothing is broken in any of these. is was simply never asking the question you wanted — and notice that the first example, where both were written out directly, gave True and would have let the mistake pass unnoticed.
7. The Four Together
| Operator | Asks | Example | Result |
|---|---|---|---|
in | does this appear inside that? | "err" in "0 errors" | True |
not in | does it not appear? | "FATAL" not in line | True |
is | is this the very same value? | first_error is None | True |
is not | is it not the same value? | first_error is not None | False |
All four are expressions producing a bool, so all four combine with and, or and not from Chapter 3.5:
line = "0 errors, 0 warnings"
first_error = None
print("errors" in line and first_error is None)True8. Predict, Run, Observe
Predict each, then run.
line = "TEST DONE: 3 errors"
status = None
print("DONE" in line)
print("done" in line)
print("PASS" not in line)
print(status is None)
print(status is not None)
print("errors" in line and status is None)
print(type("DONE" in line))True
False
True
True
False
True
<class 'bool'>The second line is the check: "done" is not in the text because membership is case-sensitive, even though "DONE" is.
9. Exercise
Exercise 1 — check a line. Given line = "UVM_WARNING @ 500 ns", write three lines that print whether "UVM_ERROR" appears, whether "UVM_WARNING" appears, and whether "UVM_FATAL" does not appear.
Exercise 2 — the case trap. Predict both, then run:
line = "0 Errors"
print("errors" in line)
print("Errors" in line)Explain the difference in one sentence.
Exercise 3 — None or not. Start report_path at None, print whether it is None, then assign it "report.txt" and print again. Use is None both times.
Exercise 4 — pick the operator. For each, say whether you would use ==, is, or in:
(a) has the run produced a first failure yet; (b) is the status word exactly "PASS"; (c) does this line mention a fatal; (d) is the error count zero.
Exercise 5 — the unreliable comparison. Predict what each prints, then run:
a = 300000
b = 299999
b += 1
print(a == b)
print(a is b)The two answers differ. Say in one sentence which operator you should have used to ask "are these the same number", and why the other one answered as it did.
10. Summary
inasks whether something appears inside something else and produces abool.not inis the negative form.- For text,
inlooks for the characters in sequence, and it is case-sensitive. "UVM_ERROR" in lineis the core of every log checker — Part II builds on exactly this.isasks whether two names refer to the very same value, which is a different question from==.- Use
is Noneandis not None. That is the one placeisbelongs in everyday code, and it is the universal Python convention. - Do not use
ison numbers or text. It agrees with==sometimes and not others, and the difference is not something to rely on. ==compares values;isis forNone. Module 16 explains why; the rule holds without it.- This is a first look at
in. Module 4 covers text membership fully; Modules 5 to 8 extend it to collections.
One operator family remains, and for a semiconductor engineer it is the one that matters most.
Where this fits
Part of the Python curriculum.
