Membership Testing
When working with data structures, one of the most common questions is
"Does this value exist in this data structure?" 🔍 Python comes to the rescue here with two
operators: in and not in.
Testing Membership in Sequences
Recall that the sequence types in Python are list and tuples. While
they can contain any data type, the order of the elements is guaranteed.
Testing membership using the in and not in operators combined with an
if statement returns a truthy value
(boolean) to indicate membership:
| Testing Membership in a List |
|---|
| fav_books = ['mastery', 'the signal and the noise', 'the organized mind']
if 'mastery' in fav_books:
print("One of your favourites!")
else:
print("Maybe something new to consider?")
|
Returns:
Using not in works the opposite:
| Negative Testing Membership in a List |
|---|
| fav_books = ['mastery', 'the signal and the noise', 'the organized mind']
if 'discipline is destiny' not in fav_books:
print("Have your read this one? Maybe you should!")
else:
print("Ye ole` favourites once again!")
|
Would result in:
Have your read this one? Maybe you should!
Testing Membership in Dictionaries
Dictionaries (the dict data structure) present a little
bit of a special case in testing membership, the caveat being you must remember that by default,
the test runs against the key of the dictionary, not the entire key/value pair, nor against
the value itself.
| Testing Membership in Dictionary Keys |
|---|
| dream_car = {
"model": "Pinto",
"make": "Ford",
"year": 1971,
"mileage": 400,
}
if 'year' in dream_car:
print("Good to know the year of your dream car!")
else:
print("What year is your dream car?")
|
Would output:
Good to know the year of your dream car!
If you'd like to test to see if a specific value is in a dictionary, remmember to use the
dict.values() method:
| Negative Testing Membership in Dictionary Values |
|---|
| dream_car = {
"model": "Pinto",
"make": "Ford",
"year": 1971,
"mileage": 400,
}
if 'Pinto' not in dream_car.values():
print("Your dream car isn't a Pinto???!??")
else:
print("Nothing quite as sweet as a lovely Pinto!")
|
Which would return:
Nothing quite as sweet as a lovely Pinto!
Testing Membership in Strings
Strings support in for substring checking:
| Substring Testing |
|---|
| message = "Hello, World!"
print("World" in message) # True
print("world" in message) # False — case-sensitive!
print("Hello" in message) # True
print("xyz" not in message) # True
# Case-insensitive check
print("world" in message.lower()) # True
|
This is more readable than using find() or index():
| in vs find() |
|---|
| text = "The quick brown fox"
# Preferred — clean and readable
if "fox" in text:
print("Found it!")
# Works, but less Pythonic
if text.find("fox") != -1:
print("Found it!")
|
Not all membership tests are created equal. The choice of data structure dramatically
affects performance — like the difference between a hand-crafted espresso and that sad
office drip coffee. Both contain caffeine, but one sparks joy. ⚡
| Data Structure |
in Performance |
Notes |
list |
O(n) — slow |
Checks each element sequentially |
tuple |
O(n) — slow |
Same as list |
set |
O(1) — fast! |
Hash-based lookup |
frozenset |
O(1) — fast! |
Same as set |
dict keys |
O(1) — fast! |
Hash-based lookup |
str |
O(n) — depends |
Substring search |
The Practical Impact
| Performance Comparison |
|---|
| import time
# Create test data
large_list = list(range(1_000_000))
large_set = set(range(1_000_000))
# What we're looking for (worst case — at the end or missing)
target = 999_999
# List search — slow!
start = time.time()
_ = target in large_list
print(f"List: {time.time() - start:.6f} seconds")
# Set search — instant!
start = time.time()
_ = target in large_set
print(f"Set: {time.time() - start:.6f} seconds")
# Typical output:
# List: 0.015000 seconds
# Set: 0.000001 seconds
|
When to Convert to Set
If you're checking membership multiple times against the same collection,
convert to a set first:
valid_codes = ["A1", "B2", "C3", "D4", ...] # Original data
valid_set = set(valid_codes) # One-time conversion
# Now all lookups are O(1)
for code in user_inputs:
if code in valid_set:
process(code)
The any() and all() Functions
These built-in functions supercharge membership testing when you need to check
multiple conditions.
any() — At Least One True
Returns True if any element is truthy (or if any condition is met):
| any() Examples |
|---|
| # Basic usage
print(any([False, False, True])) # True
print(any([False, False, False])) # False
print(any([])) # False (empty = no True values)
# With generator expression — more common!
numbers = [1, 3, 5, 7, 8, 9]
has_even = any(n % 2 == 0 for n in numbers)
print(has_even) # True (8 is even)
# Check if any item exists in a collection
search_terms = ["error", "warning", "critical"]
log_message = "Warning: disk space low"
if any(term in log_message.lower() for term in search_terms):
print("Alert triggered!")
|
all() — Every One True
Returns True only if all elements are truthy:
| all() Examples |
|---|
| # Basic usage
print(all([True, True, True])) # True
print(all([True, False, True])) # False
print(all([])) # True (vacuous truth!)
# Check if all values pass a condition
ages = [25, 30, 18, 42]
all_adults = all(age >= 18 for age in ages)
print(all_adults) # True
# Validate all required fields
user = {"name": "Alice", "email": "alice@example.com", "age": 30}
required = ["name", "email"]
is_valid = all(field in user for field in required)
print(is_valid) # True
|
Short-Circuit Evaluation
Both any() and all() short-circuit — they stop as soon as they know the answer:
| Short-Circuit Behavior |
|---|
| def check(n):
print(f"Checking {n}")
return n > 5
# any() stops at first True
print(any(check(n) for n in [1, 2, 6, 3, 4]))
# Checking 1
# Checking 2
# Checking 6 ← stops here!
# True
# all() stops at first False
print(all(check(n) for n in [6, 7, 3, 8, 9]))
# Checking 6
# Checking 7
# Checking 3 ← stops here!
# False
|
Common Patterns
| Practical any()/all() Patterns |
|---|
| # Check if file has any of these extensions
filename = "report.pdf"
valid_extensions = [".pdf", ".doc", ".txt"]
is_valid = any(filename.endswith(ext) for ext in valid_extensions)
# Check if all passwords meet requirements
passwords = ["Abc123!", "SecurePass1", "MyP@ss99"]
all_valid = all(
len(p) >= 8 and any(c.isdigit() for c in p)
for p in passwords
)
# Check if any item in cart is out of stock
cart = [{"item": "widget", "stock": 5}, {"item": "gadget", "stock": 0}]
has_out_of_stock = any(item["stock"] == 0 for item in cart)
|
Combining Membership Tests
| Complex Membership Logic |
|---|
| # Check multiple memberships
required_skills = {"python", "sql", "git"}
nice_to_have = {"docker", "kubernetes", "aws"}
candidate_skills = {"python", "sql", "git", "docker", "react"}
# Has all required?
is_qualified = required_skills.issubset(candidate_skills)
print(is_qualified) # True
# Has any nice-to-have?
has_bonus = bool(nice_to_have & candidate_skills)
print(has_bonus) # True (has docker)
# Count matches
bonus_count = len(nice_to_have & candidate_skills)
print(f"Bonus skills: {bonus_count}") # Bonus skills: 1
|
Key Takeaways
| Concept |
What to Remember |
in operator |
Works on lists, tuples, sets, dicts, strings |
not in |
Negation of membership test |
| Dict membership |
Tests keys by default; use .values() for values |
String in |
Tests for substring, not character |
| Performance |
Sets and dicts are O(1); lists/tuples are O(n) |
any() |
True if at least one element is truthy |
all() |
True only if every element is truthy |
| Generator expressions |
Use with any()/all() for efficiency |
| Short-circuit |
any()/all() stop early when answer is known |