If you are creating custom dictionary classes in Python 3.12+ using the PEP 695 type parameter syntax, you might encounter a frustrating issue: your IDE (such as VS Code with Pylance or Pyright) provides full autocomplete for direct variable attributes, but completely loses type hints when accessing elements via dictionary indexing like my_dict['key'].

The Cause of the Problem

In your implementation, you defined the generic dictionary class as follows:

class Items[K, I](dict):

Although Python allows this syntax, inheriting directly from dict without supplying the generic parameters means you are inheriting from an unparameterized (or raw) dictionary, effectively equivalent to dict[Any, Any]. Because dict itself is generic, type checkers will infer that calling dict.__getitem__ returns Any rather than your specified type parameter I.

The Solution: Parameterize the Base Class

To restore full IDE autocomplete and enable correct static type checking, pass the generic type parameters K and I directly into the base dict[K, I] class definition.

Updated Python 3.12+ Code Solution

from typing import Self

# Parameterize dict with K and I
class Items[K, I](dict[K, I]):
    def add_item(self, key: K, item: I):
        # Additional custom logic can go here
        self[key] = item

class Person(Items[str, 'Person']):
    def __init__(self, name: str, parent: Self | None):
        self.name = name
        self.parent = parent
        if parent is not None:
            parent.add_item(self.name, self)

emily = Person('Emily', None)
hannah = Person('Hannah', emily)

print(hannah.name)          # Autocomplete works
print(emily['Hannah'].name)  # Autocomplete now works correctly!
print(hannah.parent.name)   # Autocomplete works

Backward Compatible Solution (Python 3.11 and Earlier)

If your project supports Python versions prior to 3.12, use typing.TypeVar and inherit from dict[K, I] alongside Generic[K, I]:

from typing import TypeVar, Generic, Optional
from typing_extensions import Self

K = TypeVar('K')
I = TypeVar('I')

class Items(dict[K, I], Generic[K, I]):
    def add_item(self, key: K, item: I) -> None:
        self[key] = item

class Person(Items[str, 'Person']):
    def __init__(self, name: str, parent: Optional[Self] = None):
        self.name = name
        self.parent = parent
        if parent is not None:
            parent.add_item(self.name, self)

Key Takeaways

  • Always supply type parameters to generic base classes (e.g., dict[K, V] or list[T]) when inheriting.
  • Inheriting from raw dict causes key lookups (__getitem__) to evaluate to Any, which disables IntelliSense features in VS Code, PyCharm, and Pyright.