Skip to content

Fix Python Autocomplete for Custom Generic Dictionary Classes

In a Python developer community I participate in, a member recently asked: “I’ve built a custom class that wraps a dictionary to add some extra logic, but whenever I access an item using obj['key'], my IDE (VS Code/PyCharm) has no idea what the type is. How do I get the autocomplete hints to work for items in my generic dictionary class?”

This is a common frustration when building data structures or API wrappers. By default, if you don’t explicitly tell Python’s type system what your class contains, it treats everything as Any, effectively killing your IDE’s ability to help you.

The developer had code that looked roughly like this:

class Storage:
def __init__(self):
self._data = {}
def __getitem__(self, key):
return self._data[key]
def __setitem__(self, key, value):
self._data[key] = value
# Usage
store = Storage()
store["user"] = User(name="Alice")
user = store["user"]
# user. <--- No autocomplete for 'name' here!

To fix this, you must use Generics. By using typing.TypeVar and typing.Generic, you inform the static type checker that the class is a container for specific types defined at instantiation.

Solution (Python 3.9+):

from typing import TypeVar, Generic, Dict
K = TypeVar("K")
V = TypeVar("V")
class Storage(Generic[K, V]):
def __init__(self) -> None:
# Note: Use dict[K, V] in 3.9+, or Dict[K, V] for older versions
self._data: dict[K, V] = {}
def __getitem__(self, key: K) -> V:
return self._data[key]
def __setitem__(self, key: K, value: V) -> None:
self._data[key] = value
# Now autocomplete works!
store: Storage[str, User] = Storage()
store["user"] = User(name="Alice")
user = store["user"]
# user.name -> Autocomplete now suggests 'name'

When you create a custom class, tools like Pylance (VS Code) or the PyCharm inspector perform “static analysis.” They don’t run your code; they read the signatures. Without hints, __getitem__ is assumed to return Any.

TypeVar creates a placeholder (a “type variable”). When you define class Storage(Generic[K, V]), you are telling Python: “This class handles two types, K and V, and they will be decided when the user creates an instance.”

2. Inheriting from Collection Abstractions

Section titled “2. Inheriting from Collection Abstractions”

While the “Immediate Fix” above works for basic access, it doesn’t make your class “behave” like a dictionary in the eyes of the type system (e.g., it won’t have .get(), .keys(), or .items() unless you write them).

For a robust solution, you should inherit from collections.abc.MutableMapping.

Solution for Python 3.11+ (Industry Standard):

from collections.abc import MutableMapping
from typing import TypeVar, Iterator
K = TypeVar("K")
V = TypeVar("V")
class AdvancedStorage(MutableMapping[K, V]):
def __init__(self) -> None:
self._data: dict[K, V] = {}
def __getitem__(self, key: K) -> V:
return self._data[key]
def __setitem__(self, key: K, value: V) -> None:
self._data[key] = value
def __delitem__(self, key: K) -> None:
del self._data[key]
def __iter__(self) -> Iterator[K]:
return iter(self._data)
def __len__(self) -> int:
return len(self._data)
# This version provides hints for .get(), .update(), etc., automatically.
  • Fixed Key Types: If your dictionary will always use strings as keys, you don’t need K. You can simplify it to class Storage(Generic[V]) and hardcode key: str in your methods.
  • The UserDict Alternative: If you don’t want to implement all the abstract methods of MutableMapping, you can inherit from collections.UserDict. However, you still need to apply Generic to ensure the type hints pass through to the underlying data.
  • Performance: Type hints are purely for the IDE and type checkers (like Mypy). They have zero impact on runtime performance.

Does this work with older Python versions?

Section titled “Does this work with older Python versions?”

If you are on Python 3.8 or lower, you cannot use dict[K, V] or list[T]. You must import the capitalized versions from the typing module:

# Python 3.8 and below
from typing import Dict, TypeVar, Generic
K = TypeVar("K")
V = TypeVar("V")
self._data: Dict[K, V] = {}

What if I want to restrict what types can be stored?

Section titled “What if I want to restrict what types can be stored?”

You can constrain your TypeVar. For example, if your storage should only ever hold subclasses of a BaseModel, you can define it like this:

V = TypeVar("V", bound=BaseModel)
class ModelStorage(Generic[V]): ...

This prevents a developer from accidentally putting a string or an integer into a storage container designed for complex objects.

Why isn’t my IDE picking up the types even with Generic?

Section titled “Why isn’t my IDE picking up the types even with Generic?”

Check your IDE settings. In VS Code, ensure “Type Checking Mode” is set to basic or strict in the Pylance settings. In PyCharm, ensure your interpreter is correctly pointed to the virtual environment where your code resides. Sometimes, a simple “Restart Extension Host” or IDE restart is required to refresh the type cache after changing generic signatures.