None & Truthiness
Overview
`None` is Python's single null value and its built-in sentinel value — it means 'no value', not zero, not empty, not false. It's a singleton: there is exactly one `None` object in a running process, which is why identity checks (`is None`) work and are preferred over equality checks (`== None`). Truthiness is a separate rule: every object in Python can be evaluated in a boolean context (an `if`, a `while`, a `bool(...)` call), and Python decides `True` or `False` for it even if it's not a boolean. Falsy values are `None`, `False`, `0`, `0.0`, and any empty collection (`""`, `[]`, `{}`, `set()`, `()`). Everything else is truthy — including `0.0001`, `"False"` (a non-empty string!), and `[None]` (a non-empty list containing None). The trap: `if not value:` treats `None`, `0`, and `""` identically. If your code needs to distinguish 'the field is missing' from 'the field was explicitly set to zero/empty', truthiness collapses that distinction and you must check `is None` instead. count = 0 if not count: print("no count provided") # WRONG — fires even when count really is 0 if count is None: print("no count provided") # correct — 0 is a valid count, only None means missing
Backend code constantly needs to tell 'the client didn't send this field' apart from 'the client sent a real but empty/zero value' — request bodies, query params, and DB NULLs all funnel through this exact distinction, and using truthiness instead of an explicit `is None` check is one of the most common silent-bug sources in request handling.
Where used: FastAPI: `Optional[int] = None` as the signal for 'query param not supplied', dict.get(key, None) and `if result is None:` after a lookup, SQLAlchemy: a NULL column deserializes to `None`, distinct from an empty string
Why learn this
- Distinguishing 'missing' from 'empty/zero' is the single most common source of silent backend bugs — a request with `count=0` gets treated as if `count` was never sent.
- Every FastAPI optional field, every dict.get(), and every ORM nullable column funnels through this exact distinction.
Code walkthrough
def summarize(items=None):
if items is None:
items = []
total = len(items)
return f"{total} item(s)"
print(summarize())
print(summarize([]))
print(summarize([1, 2, 3]))
Focus: Watch how `items is None` (not `if not items:`) is the only check that correctly separates 'no argument was passed at all' from 'an empty list was passed on purpose' — both branches would otherwise collapse to the same `if not items:` path.
Common mistakes
- Using `== None` instead of `is None`: `==` calls `__eq__`, which a custom class could override to return True for unrelated objects (e.g. a numpy array raises entirely). `is None` checks identity against the one True singleton — always correct, always fast, and it's the PEP 8 convention linters enforce.
- Collapsing 'missing' and 'falsy' with `if not value:`: `if not value:` is True for `None`, `0`, `""`, and `[]` alike. If zero or empty-string is a legitimate value in your domain (a discount of 0%, a comment of ""), this silently misclassifies real data as absent — check `if value is None:` when you specifically mean 'wasn't provided'.
Glossary
- falsy
- A value that evaluates to `False` in a boolean context even though it isn't the boolean `False` itself — `None`, `0`, `0.0`, `""`, `[]`, `{}`, `set()`. Example: `if not items:`
- sentinel value
- A special placeholder value used to signal 'nothing here' or 'not provided', distinct from any real data value. `None` is Python's built-in sentinel value. Example: `def get(self, key, default=None):`
Recall questions
- Why is `is None` preferred over `== None`?
- Is an empty list `[]` truthy or falsy? Is a list containing one `None`, `[None]`, truthy or falsy?
- A function receives `count=0` from a request body. Why does `if not count:` silently misbehave here?
Understanding checks
What does this print?
falsy truthy
An empty string `""` has length 0, so it's falsy. A string containing a single space `" "` has length 1 — it's non-empty, so it's truthy, even though it looks 'blank' to a human reading it.
This function is supposed to reject requests with no `page` parameter, but it also rejects `page=0`, which should be valid (the first page, zero-indexed). What's wrong?
`if not page:` is True for both `None` (missing) and `0` (a valid page value) — it uses truthiness where it needs an identity check. Fix: `if page is None:`.
Truthiness treats every falsy value the same. To distinguish 'absent' from 'present but zero', you must check identity against `None` specifically, not truthiness.
Practice tasks
Continue learning
Previous: Mutable vs immutable types
Next: None & truthiness