This lesson covers four small but important operators: in, not in, is, and is not.
Membership: in and not in
in asks “is this value inside that collection?”. It works with strings, lists, sets, dictionaries — anything you can iterate over.
# in a string — substring check
text: str = "Hello, Python!"
print("Python" in text) # True
print("python" in text) # False — case matters
print("xyz" not in text) # True
# in a list — item check
fruits: list[str] = ["apple", "banana", "cherry"]
print("apple" in fruits) # True
print("grape" not in fruits) # True
# in a dictionary — KEY check
prices: dict[str, float] = {"apple": 0.5, "banana": 0.3}
print("apple" in prices) # True — apple is a key
print(0.5 in prices) # False — 0.5 is a value, not a key
not in is the negation. x not in coll is the same as not (x in coll) — the first form reads more naturally.
Why this matters
in is the cleanest way to check if a value is “one of these”. Compare:
status: str = "approved"
# clumsy
if status == "approved" or status == "pending" or status == "in_review":
...
# Pythonic
if status in ("approved", "pending", "in_review"):
...
The second form is shorter, easier to extend, and reads almost like English.
Identity: is and is not
is asks a different question from ==. We touched on this in the comparison lesson — let’s go deeper.
==asks: do these two values look the same?isasks: are these two variables pointing to the same object in memory?
a: list[int] = [1, 2, 3]
b: list[int] = [1, 2, 3]
c: list[int] = a
print(a == b) # True — same contents
print(a is b) # False — different objects in memory
print(a is c) # True — c was assigned from a, so they're the same object
Most of the time you want ==. The one place where is is the right answer:
if value is None:
...
None is a singleton — there’s only ever one of it in the whole program. So is None is faster and idiomatic.
You’ll see linters (including Ruff) flag == None as a warning and suggest is None instead.
A small mutable vs immutable mystery
Try this in the REPL:
>>> a = 256
>>> b = 256
>>> a is b
True
>>> a = 257
>>> b = 257
>>> a is b
False # on some Python versions
Why? Python caches small integers (-5 to 256) as singletons for speed. Two variables holding 256 point to the same object. Two variables holding 257 may or may not, depending on the implementation.
The moral: don’t use is to compare numbers, strings, or other values. Use ==. Reserve is for None.
Putting it together
A real-world example using both in and is:
user_role: str | None = get_role() # may return None
if user_role is None:
print("Please log in")
elif user_role in ("admin", "moderator"):
print("Welcome, staff")
else:
print("Welcome, user")
is Nonechecks for the absence of a value.in (...)checks if the value is one of a set of options.
What’s next
You’ve met every operator that produces a boolean. Next, the ternary operator — a one-line if/else for assignments.