The “function object is not subscriptable” error means you used square brackets on a function instead of calling it or indexing the value it returns.
You’ll usually hit this in Python when you treat a function like a list, dict, or DataFrame. The code runs until it reaches the brackets, then Python stops and throws the TypeError. The fix is often one character: swap [] for (), or call the function first, then index the result.
This guide walks through the common causes, how to spot the exact line that triggered it, and the cleanest fixes you can reuse. It’s written so you can scan, make the change, and move on.
Why This Error Pops Up
In Python, square brackets mean “get an item from this object.” Lists, tuples, strings, dicts, and many library objects implement that behavior. Functions don’t. A function is a callable object: you run it with parentheses, and it produces a return value.
So when Python sees something like my_func[0], it tries to subscript the function object itself. Since functions don’t define item access, Python raises a TypeError.
- Indexing A Function Name — You wrote
thing[0]butthingis a function, not the value you meant to index. - Forgetting Parentheses — You meant
thing()but typedthing, then indexed it later. - Overwriting A Variable — A name that used to be a list or dict got reassigned to a function.
- Shadowing A Built-In — You named a function
list,dict, orsum, then tried to use the built-in later.
Function Object Is Not Subscriptable In Python Code
The fastest way to win against this error is to read it like a map. Python tells you two things: the type it couldn’t subscript, and the line where it happened. Start with the stack trace, jump to the bottom line that points into your file, and zero in on the expression right before the brackets.
| What You Wrote | What Python Thinks It Is | Fix That Works |
|---|---|---|
get_user[0] |
get_user is a function |
Call it: get_user()[0] |
config["mode"] |
config is a function |
Rename the function or call it: config()["mode"] |
data["col"] |
data is a method reference |
Add parentheses: data()["col"] |
df["age"] |
df got reassigned |
Find the reassignment and undo it |
If you see this message during debugging, it’s worth saying it out loud: “I’m indexing the function, not its result.” That one sentence tends to point your eyes to the missing parentheses or the wrong name.
Read The Traceback Without Guesswork
A traceback can look noisy, yet it’s the shortest path to the fix. Start at the last line of the traceback. That line shows the file, line number, and the expression that raised the TypeError. The earlier lines show how your code got there.
If the traceback points into a library file, don’t panic. Scroll up until you find the last frame that lives in your codebase. That’s the call site you control, and it’s usually where the wrong object got passed in.
- Jump To The Bottom Frame — The final line is where Python stopped; copy that line number into your editor.
- Circle The Brackets — Find the
[]that triggered the error, then read the name immediately to the left. - Trace The Name Back — Follow that name to where it was assigned so you can see when it became a function.
- Confirm The Runtime Type — Print
type(name)right before indexing to verify what you’re holding in that moment.
Callable Vs Subscriptable In Plain Terms
Two built-ins help you think clearly here. callable(x) tells you whether Python can run x(). Subscriptable objects implement item access, which is why x[0] works on lists and strings. You can check that behavior by testing hasattr(x, "__getitem__").
It’s normal for an object to be one but not the other. Functions are callable and not subscriptable. Lists are subscriptable and not callable. Some objects are both, like certain callable classes that also expose items. When you know which side you need, the syntax becomes obvious.
One-Liners That Reveal The Bug
When the failing expression is long, shrink it. Assign each piece to a name, then print the type after each step. This keeps you from guessing.
- Split The Expression — Turn
get_data()[0]["id"]into two lines so you can print after each operation. - Inspect The Return Value — Call the function once, store it, then check what you can index or slice on it.
- Check The Name Collision — If
datais both a function and a variable in different files, rename one to stop accidental reuse.
Quick Checks That Catch It In Seconds
Before you refactor anything, run a few checks at the place that fails. They’ll tell you what your variable name points to at runtime, not what you meant it to be.
- Print The Type — Add
print(type(x))right before the failing line to see whetherxis a function. - Print The Value — Add
print(x); functions show up likeor. - Check For Parentheses — If you expected a dict, list, or DataFrame, ask yourself where
()should be. - Search For Reassignment — Use your editor search for
x =to spot where the name changed type.
When you’re in a notebook, a quick sanity check is x? in IPython. It shows whether you’re holding a callable or a concrete value.
Fixing A Function Object Not Subscriptable Error Cleanly
Most fixes fall into a few patterns. Once you recognize the pattern, you can patch it quickly and keep the code readable.
Call The Function, Then Index The Return Value
This is the classic case. You meant to grab an item from the data produced by a function, not from the function itself.
- Use Parentheses First — Change
users[0]tousers()[0]ifusersis a function that returns a list. - Store The Result — Assign
u = users(), then useu[0]so the call and the indexing are easy to read.
Index A Method Result, Not The Method Reference
Bound methods can look like values in code completion, so it’s easy to forget the call. The giveaway is seeing something like when you print the object.
- Add The Missing Call — Turn
obj.get["x"]intoobj.get()["x"]ifgetreturns a dict. - Pick The Right Attribute — If a library offers both a property and a method with similar names, use the one that returns the data you need.
Stop Overwriting Data Names With Functions
This happens when you reuse a short name like data for two different roles. The code works early in the script, then breaks later after the reassignment.
- Rename One Of The Two — Use
get_datafor the function anddatafor the returned value. - Keep Data And Callables Separate — Adopt a naming habit: verbs for callables, nouns for values.
Undo Shadowing Of Built-Ins
Shadowing is when you assign to a name that Python already uses. It doesn’t crash right away, so it can feel random when the failure appears later.
- Rename Your Function — If you wrote
list = ...ordef list(...), rename it to something specific likeitems_list. - Restart The Runtime — In notebooks, old bindings stick around. Restarting clears the shadowed name.
Common Situations In Real Projects
You’ll see the same root mistake across different libraries. Once you know what the error means, you can spot it even in code you didn’t write.
Pandas And DataFrames
Pandas uses brackets a lot, so this one shows up often. A frequent trigger is assigning a DataFrame-building function to the same name you later treat like a DataFrame.
- Check The DataFrame Name — If
df["col"]fails, printdf. If it prints like a function, find wheredfwas set. - Call Factory Functions — If you wrote
df = build_df, change it todf = build_df().
Flask, Django, And Web Handlers
Web libraries often use decorators, which can blur the line between a function and the value it returns. If you decorate a function and then try to treat that name like a dict, you can trip this TypeError.
- Separate Handler And Data — Keep
get_settings()as the callable and store its result insettings. - Check What The Decorator Returns — Some decorators replace your function with another callable. Print the name to see what you’re holding.
NumPy And Callables That Produce Arrays
NumPy makes it easy to think in arrays. If a helper returns an array, you must call it before slicing.
- Call Then Slice — Change
make_array[:10]tomake_array()[:10]. - Freeze The Output — Store
a = make_array()once, then sliceamany times.
Habits That Stop This From Coming Back
This error is simple, yet it can waste time when it shows up in the middle of a bigger task. A few habits keep it rare.
- Use Descriptive Names — Names like
get_usersandusersmake it obvious which one needs parentheses. - Lean On Type Hints — Even basic annotations help editors flag when you index a callable.
- Add Assertions In Debug Builds — A quick
assert not callable(x)before indexing can catch bad reassignments early. - Write Small Tests For Helpers — If a helper should return a dict, test that return type once, then trust it.
Your editor can catch a lot of these before you run the file. If you use VS Code, PyCharm, or another IDE, turn on type checking and linting for the project. Even light settings can flag “indexing a callable” when it sees [] on a name annotated as a function.
- Run A Type Checker — Tools like
mypycan warn when a function is used like a dict or list. It’s a small habit that saves minutes daily. - Enable Lint Rules — Linters like
rufforpylintcan spot shadowed built-ins and risky reassignments. - Use Auto-Rename — If you rename a function, let the editor rename all references so you don’t leave old names behind.
If you’re still stuck, reduce the failing line to a tiny snippet. Print the object, call it, print the return type, then index. This stepwise check usually reveals where the type changed.
When you see this TypeError again, remember the core rule: square brackets are for values that hold items. Parentheses are for functions that produce those values. That’s the whole game, and it’s why function object is not subscriptable is one of the quickest Python errors to fix once you know what to look for.
One last check: if the error message reads function object is not subscriptable but you swear you used parentheses, scan for a name collision. A helper function and a variable sharing the same name can make the code look right while still indexing a callable.
