The attributeerror ‘list’ object has no attribute ‘keys’ error means you called .keys() on a list instead of a dictionary in Python.
Why You See AttributeError: ‘List’ Object Has No Attribute ‘Keys’
A quick check helps here. This message comes from Python when code reaches the attribute lookup step and finds a method that does not exist on the current type. A list stores items in order, while a dict offers methods such as keys(), values(), and items(). When Python sees some_list.keys(), it raises the attributeerror ‘list’ object has no attribute ‘keys’ message.
The core issue is a mismatch between what the code expects and the actual data type at runtime. Somewhere in the script a variable that should hold a dictionary instead holds a list. The stack trace points to the line that triggers the problem, but understanding the pattern behind the error turns the fix into a simple adjustment instead of a long debugging session.
It also helps to check whether third party libraries or helper functions return data in a different shape than you assume. A function that once returned a dictionary can start returning a list of dictionaries after a refactor. Old calls to .keys() then fail in a hidden corner of the code until this traceback appears during testing or in production logs.
How To Tell Whether You Have A List Or A Dictionary
A quick check before touching any fix is to confirm the type of the variable that triggers the error. Once you know whether you hold a list, a dictionary, or a nested structure that combines both, you can pick a safe repair pattern.
- Print the type — Add a temporary line such as
print(type(data))right before the failing line to see what you have at runtime. - Inspect a sample value — Use
print(data)or log a slice likedata[:3]to see whether the structure looks like[...]or{...}. - Check the source — Read the function or library call that builds the value and confirm what it promises to return in its documentation or docstring.
Once you know the type, you can adjust the code with confidence. When you truly need a dictionary, you can convert the list or change earlier logic so that it builds a mapping from the start. When the list is the correct shape, you can switch from .keys() to list patterns such as indexing, slicing, or looping over items.
Common Causes Of The Attributeerror ‘List’ Object Has No Attribute ‘Keys’ Error
A quick check across real projects shows that most occurrences of AttributeError: ‘List’ Object Has No Attribute ‘Keys’ fall into a few repeatable patterns. Spotting which pattern matches your code saves a lot of trial and error.
Looping Over A List Of Dictionaries
Many scripts load JSON or rows from a database into a list where each element is a dictionary. The list itself does not have keys(), while each inner dictionary does. The mistake appears when you call data.keys() instead of iterating and calling item.keys() inside the loop.
# data is a list of dictionaries
data = [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25},
]
# Wrong: calling keys() on the list
column_names = data.keys() # AttributeError
# Right: use a single dictionary, or the first element
column_names = data[0].keys()
A frequent variant looks like for name in data.keys() when data is a list. In that case you should loop over the list and handle each dictionary in turn, or build a new mapping that fits how the data will be used.
Reading JSON Where The Top Level Is A List
APIs and configuration files often wrap items in a list at the top level. A quick json.loads() call then gives you a list, not a dictionary. Later code that expects a dictionary of settings or records calls .keys() and triggers the attributeerror ‘list’ object has no attribute ‘keys’ traceback.
import json
payload = '[{"id": 1}, {"id": 2}]'
data = json.loads(payload)
# Wrong: assumes a dict
ids = list(data.keys()) # AttributeError
# Right: treat data as a list
ids = [item["id"] for item in data]
Accidental Indexing Before Calling Keys
Sometimes the structure is a mix, such as a dictionary that contains a list, or a list that contains one dictionary. A small change in indexing can turn a dictionary into a list or the other way round. A snippet like config["users"].keys() may break once config["users"] becomes a list of user dictionaries.
config = {
"users": [
{"name": "Alice"},
{"name": "Bob"},
]
}
# Wrong after schema change
user_keys = config["users"].keys() # AttributeError
# Right: inspect a single user
user_keys = config["users"][0].keys()
Fixing Attributeerror ‘List’ Object Has No Attribute ‘Keys’ In Loops
A quick check once you confirm that you have a list is to adjust how you loop across the data. Instead of calling keys() on the outer container, you operate on each inner dictionary or on a dictionary you build from the list.
Loop Over Each Dictionary In The List
A plain for loop works well when each item is already a dictionary with the same structure. This pattern keeps types aligned and avoids extra conversions.
records = [
{"id": 1, "status": "new"},
{"id": 2, "status": "done"},
]
for record in records:
for field, value in record.items():
print(field, value)
- Use record.items() — When you care about both keys and values in each dictionary,
.items()gives a clear pair for each field. - Use record.keys() — When you only need the field names, call
.keys()on the dictionary inside the loop. - Break early — If all dictionaries share the same fields, get keys from the first item and then break out of the loop.
Build A Dictionary From A List
Sometimes you genuinely need a dictionary at the top level. In that case you can turn the list into a mapping with a clear rule, such as using an id field as the index. Then calling .keys() on the new dictionary makes sense.
users = [
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"},
]
# Build a mapping from id to user
users_by_id = {user["id"]: user for user in users}
user_ids = list(users_by_id.keys())
- Pick a stable field — Choose a field that clearly identifies each element, such as an id or slug.
- Handle duplicates — Decide what should happen if two items share the same id before building the mapping.
- Keep both shapes — In some cases it helps to keep the original list and the new dictionary side by side.
Fixing The Error When Working With JSON Data
A quick check with JSON from web APIs, configuration files, and data feeds shows that the top level structure might change between a list and a dictionary depending on filters or options. That variation explains many real world reports of attributeerror ‘list’ object has no attribute ‘keys’.
Inspect The Raw JSON Payload
Before writing parsing code, print the raw JSON string or save it to a file. Then check the first character. A leading [ means the top level is a list; a leading { means a dictionary.
- Log raw responses — When calling APIs, write the raw body to logs during development so you can see its exact shape.
- Handle both shapes — Some endpoints return a dictionary when a filter matches a single record and a list when multiple records match.
- Document assumptions — Add short comments near parsing code that state whether your code expects a list or a dictionary.
Guard Your Parsing Code
Instead of assuming a fixed shape, add small checks around the JSON object before calling any methods. These checks help you catch shape changes early and print a clear message instead of a long stack trace.
import json
data = json.loads(payload)
if isinstance(data, dict):
keys = list(data.keys())
elif isinstance(data, list) and data and isinstance(data[0], dict):
keys = list(data[0].keys())
else:
raise ValueError("Unexpected JSON schema")
- Use isinstance — The built in
isinstancefunction cleanly distinguishes betweenlistanddict. - Add simple errors — Raising a custom
ValueErrorwith a short message helps later debugging. - Keep sample payloads — Store a few JSON examples in your repository so schema shifts show up during tests.
Using Safer Patterns To Avoid This Error Next Time
A quick check after you fix the immediate attributeerror ‘list’ object has no attribute ‘keys’ issue is to ask which habits could reduce the chance of the same problem returning during a large refactor or library upgrade. Small process tweaks around types and tests pay off over time.
Add Type Hints And Static Checks
Adding type hints to functions clarifies whether a parameter expects a list, a dictionary, or a nested structure. Tools such as mypy or type checkers in editors can then flag suspicious calls to .keys() long before they reach production.
- Annotate function inputs — Declare parameter types like
List[Dict[str, Any]]orDict[str, Any]so expectations stay clear. - Run a type checker — Integrate a tool such as
mypyinto your test run or pre commit setup. - Review warnings — Treat new type warnings as a prompt to scan for shape changes in your data.
Write Small Tests Around Data Shapes
Lightweight tests that load sample responses and assert on types catch many shape related surprises. Short tests pay off.
- Test with lists and dicts — When a function may accept both shapes, add tests for each branch.
- Assert on types — Use
assert isinstance(result, dict)or similar checks right in your tests. - Store fixtures — Keep sample payloads and slices of real data under a
tests/fixturesfolder in your project.
Log Helpful Context Around Shape Errors
A clear log line at the moment of failure can save a lot of guesswork. Instead of logging only the message, add the name of the variable, its type, and a small slice of data. That detail speeds up debugging when a list shows up where code expects a dictionary.
- Include types — Log
type(data)along with the error message so shape mismatches appear at a glance. - Limit size — Print only a small slice of large lists or dictionaries so logs stay readable.
- Group related logs — Use a shared prefix or request id so you can trace one failing call across multiple log lines.
Quick Reference Table For Common Fixes
A quick check of the table below gives frequent causes of attributeerror ‘list’ object has no attribute ‘keys’ and a direct way to repair each one. Use it when triaging new reports or reviewing older scripts.
| Scenario | Problem Pattern | Safer Fix |
|---|---|---|
| List of dictionaries | Call data.keys() on the list |
Loop across items and call item.keys() |
| JSON array response | Assume json.loads() returns a dict |
Treat the result as a list and iterate |
| Schema change | Dictionary field replaced with a list | Update code to index the list before calling .keys() |
| Helper function refactor | Return type changed from dict to list | Check return types and adjust callers |
| Mixed data sources | Sometimes a dict, sometimes a list | Add type guards and handle each branch |
Once you get used to watching the shape of your data, this particular AttributeError turns into a quick fix instead of a blocker. Clear type checks, small helper functions, and short tests around shape changes keep the code base friendly even as new features and integrations arrive.
