Python uses not, not in, and is not for different kinds of checks. The logical operator not reverses a Boolean result. The membership operator not in tests whether a value is absent from a container. The identity operator is not checks whether two references are different objects. Choosing the exact form makes a condition easier to read and prevents subtle bugs.
Quick answer
Use not condition to negate a Boolean expression, value not in container to test membership, and value is not None to test identity against the singleton None. Python’s expression reference defines membership test operations, while its Boolean operations describe not.

Use not to reverse a condition
not takes one expression and changes its truth value. It is useful when the positive condition already describes the thing you want to test. Empty strings, empty containers, zero, and None are false in a Boolean context; most other values are true.
is_ready = False
if not is_ready:
print("wait for the service")
Prefer if not value to comparing a value directly with False when any false-like value should trigger the branch. If the distinction between False, 0, and None matters, write an explicit identity or equality check instead.

Use not in for membership
not in is a membership test. It returns True when the left-hand value is not found in the right-hand container, and False when it is found. The spelling communicates absence directly.
allowed = {"python", "numpy", "pandas"}
language = "ruby"
if language not in allowed:
print("unsupported language")
Python also parses not value in allowed as the negation of a membership test, but value not in allowed is clearer and avoids making readers reason about precedence. Use the two-word operator in new code.
Membership depends on the container
For a list or tuple, Python usually searches items from left to right. A set is designed for fast membership tests, subject to normal hashing requirements. Dictionary membership checks keys, not values. If you need to search values, use value in mapping.values() deliberately.
record = {"status": "ready", "count": 3}
print("status" in record)
print("ready" in record)
print("ready" in record.values())
String membership checks for a substring and is case-sensitive. Thus "Py" in "Python" is true, while "py" in "Python" is false. Normalize both sides with a documented policy when a case-insensitive check is intended.

Filter values with not in
A list comprehension is a compact way to keep values that are not in a blocked collection. Put the membership test in the condition and keep the output expression simple.
blocked = {"draft", "deleted"}
statuses = ["ready", "draft", "queued", "deleted"]
visible = [status for status in statuses if status not in blocked]
print(visible)
Use a set for blocked when it is reused for many checks. If the input is a generator, a membership test can consume items until it finds a match or reaches the end. Do not assume that the generator can be iterated from the beginning afterward.
Use is not for identity
Identity asks whether two names refer to the same object. The canonical check for a missing optional value is value is None or value is not None. Do not use == None for this purpose because custom equality methods can change the result.
result = None
if result is not None:
print(result)
else:
print("no result")
Use == and != for value comparison, such as whether two strings have the same contents. Use is and is not only when object identity is the requirement.
Common mistakes
Do not confuse a dictionary key check with a value check, do not forget that string membership is case-sensitive, and do not use not in when the real requirement is an identity check. When a condition becomes long, assign an intention-revealing Boolean variable rather than relying on a dense expression.

Understand evaluation and precedence
Membership and identity comparisons are evaluated before a surrounding Boolean operation is applied. That is why not value in allowed means the negation of the membership result, but the positive spelling is still preferable. Parentheses are useful when a condition combines more than one rule, especially when a reviewer must verify the order of the checks.
value = "python"
allowed = {"python", "numpy"}
blocked = {"legacy"}
is_allowed = value in allowed and value not in blocked
print(is_allowed)
Short-circuit evaluation also matters. In an expression joined with and, Python stops as soon as one part is false. In an expression joined with or, it stops as soon as one part is true. Put inexpensive or safety-related checks first when the later membership operation assumes that an object is available.
Choose a container for repeated tests
Membership syntax is the same across containers, but the cost and semantics are not. Searching a long list repeatedly can become a performance bottleneck because each test may scan many items. Converting stable lookup data to a set can make the intent clearer and avoid repeated linear scans. The values must be hashable, and the set deliberately discards duplicates and ordering.
A dictionary is useful when the membership test and the associated result belong together. Test the key with key in mapping, then read the value with an explicit policy for missing keys. Use mapping.get(key) only when the default cannot be confused with a stored value, or provide a unique sentinel.
settings = {"timeout": 30}
missing = object()
timeout = settings.get("retries", missing)
if timeout is missing:
print("retries was not configured")
For generators and other one-pass iterators, a failed membership test consumes the iterator to the end. A successful test consumes values up to the match. Materialize the input or create a fresh iterator when later processing needs the original sequence.

Support membership in custom objects
Custom classes can define __contains__() to control the meaning of in and not in. If that method is absent, Python can fall back to iteration or indexed access. Document the behavior when a class represents a remote collection, a stream, or a case-normalized view, because a membership test may perform I/O or consume data rather than simply inspect memory.
Make validation intent visible
Validation code is easier to maintain when the condition names the policy. Instead of stacking several negations around a long expression, compute a variable such as is_supported or has_required_field. This gives tests a natural value to assert and makes a later change to the allowed set less likely to invert the logic.
For related control-flow patterns, see the guide to Python’s break statement and the guide to combining Python lists.
Frequently Asked Questions
What does not in mean in Python?
not in is a membership operator that returns True when the left-hand value is absent from the right-hand container.
What is the difference between not and not in?
not reverses the truth value of one expression, while not in checks whether a value is missing from a container.
Does not in check dictionary values?
No. Membership on a dictionary checks keys; use value in mapping.values() when a value search is intended.
When should I use is not None?
Use is not None when the requirement is object identity with the singleton None rather than value equality.