How to Handle 'NoneType' object has no attribute 'strip' in Python
AttributeError: 'NoneType' object has no attribute 'strip' means you called a string method on None. With a database, this almost always comes from a NULL column: psycopg2 turns NULL into None, and one such row is enough to crash a whole loop. Treat None as an empty string where that is correct, or catch the error per row so one bad record cannot break the full result.
The symptom
A CSV export endpoint worked for months. Then new customer records were added,
and now every call returns 500 Internal Server Error. The JSON endpoint for
the same data still works. The log shows:
File "app.py", line 41, in export_csv
phone = row["phone"].replace("-", "").replace(" ", "")
AttributeError: 'NoneType' object has no attribute 'replace'
The same error appears with .strip(), .lower(), .upper() or .split() -
any string method called on None.
Why it happens
The table allows NULL in some columns:
CREATE TABLE customers (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL,
phone VARCHAR(50), -- nullable
company VARCHAR(200) -- nullable
);
psycopg2 returns a SQL NULL as Python None. The export loop assumes every
value is a string:
for row in rows:
name = row["name"].strip()
email = row["email"].strip().lower()
phone = row["phone"].replace("-", "").replace(" ", "")
company = row["company"].strip().upper()
writer.writerow([row["id"], name, email, phone, company])
The JSON endpoint does not fail because jsonify writes None as null
without calling any string method on it. Find the rows that cause the crash:
SELECT * FROM customers WHERE phone IS NULL OR company IS NULL;
Option 1: treat None as an empty string
If a missing phone number should simply be an empty cell, say so in the code:
for row in rows:
name = (row["name"] or "").strip()
email = (row["email"] or "").strip().lower()
phone = (row["phone"] or "").replace("-", "").replace(" ", "")
company = (row["company"] or "").strip().upper()
writer.writerow([row["id"], name, email, phone, company])
value or "" returns "" when the value is None (or any other false value,
such as an empty string). Every row is exported, and empty fields stay empty.
This is usually the best choice when the column is optional by design.
You can also do it in the query with COALESCE:
SELECT id, name, email,
COALESCE(phone, '') AS phone,
COALESCE(company, '') AS company
FROM customers ORDER BY id;
Option 2: skip the bad row and log it
Sometimes a row with missing data should not be exported at all. Then catch the error for that one row, log it, and continue:
exported, skipped = 0, 0
for row in rows:
try:
name = row["name"].strip()
email = row["email"].strip().lower()
phone = row["phone"].replace("-", "").replace(" ", "")
company = row["company"].strip().upper()
except (AttributeError, TypeError) as e:
app.logger.warning("Skipping customer %s: %s", row.get("id"), e)
skipped += 1
continue
writer.writerow([row["id"], name, email, phone, company])
exported += 1
app.logger.info("CSV export: %d rows, %d skipped", exported, skipped)
Keep the try block small and the except specific. A bare except: would
also hide real bugs, such as a typo in a column name. Log the id of each
skipped row so someone can correct the data later.
Which option to choose
| Situation | Choice |
|---|---|
| The column is optional and empty is a valid value | value or "" / COALESCE |
| A row without the value is not useful in the output | skip and log |
| The value must always exist | add NOT NULL to the column and clean the data |
The third row is the long-term answer for required data. If phone must
always be present, a NOT NULL constraint stops bad rows at insert time
instead of at export time.
Confirm the export
curl -s -o /dev/null -w "%{http_code}\n" localhost:5000/export
# 200
curl -s localhost:5000/export | wc -l
The line count should be the number of exported rows plus one header line. If
you chose to skip rows, compare it with SELECT count(*) FROM customers and
check the log for the skipped ids.
The general rule
Data from outside your code - database rows, CSV files, API responses - can
always contain missing values. Before you call a method on a field, decide
what None should mean for it, and write that decision into the code. One
unexpected row should never fail a request that serves thousands of good ones.
Do this project yourself. It's ready in a live cloud workspace in your browser - when you finish, it lands on your portfolio.
Start this project - freeWhat you'll practice
- Tracing an AttributeError on None back to a NULL database column
- Using value or "" and COALESCE to handle optional fields
- Catching errors per row so one bad record does not fail the whole export
- Deciding between defaulting, skipping and adding a NOT NULL constraint
FAQ
What does 'NoneType' object has no attribute 'strip' mean?
You called .strip() on a variable whose value is None, not a string. Find where the value comes from - often a NULL database column, a missing dict key read with .get(), or a function that returned nothing.
How do I strip a string that might be None in Python?
Use (value or "").strip(). If value is None, the expression becomes "".strip(), which returns an empty string instead of raising an error.
How do I replace NULL with an empty string in Postgres?
Use COALESCE(column, '') in the SELECT. It returns the first value that is not NULL, so a NULL becomes an empty string before the data reaches Python.
Should I skip bad rows or fail the whole export?
If one row is wrong, skipping it and logging its id is usually better than failing the whole export. Fail the whole request only when partial output would be misleading, for example in a financial report that must be complete.
Keep learning
Do this project yourself. It's ready in a live cloud workspace in your browser - when you finish, it lands on your portfolio.
Start this project - free