8

I wrote a defaultdict subclass that can call default_factory with key as argument.

from collections import defaultdict
from typing import TypeVar, Any, Callable, Generic

K = TypeVar('K')


class keydefaultdict(defaultdict, Generic[K]):
    ''' Drop-in replacement for defaultdict accepting key as argument '''

    default_factory: Callable[[], Any] | Callable[[K], Any] | None

    def __missing__(self, key: K) -> Any:
        if self.default_factory is None:
            raise KeyError(key)
        else:
            try:
                ret = self[key] = self.default_factory(key)
            except TypeError:  # try no-key signature
                ret = self[key] = self.default_factory()
            # if failed, let the error propagate as usual

            return ret

mypy complains on the default_factory type hint:

incompatible type in assignment (expression has type "Callable[[], Any] | Callable[[K], Any] | None", base class "defaultdict" defined the type as "Optional[Callable[[], Any]]")

Is there any way to override the type? mypy complains also on this lines self.default_factory(...) - too many arguments (or too few arguments) and in places where this dict is instantiated (incompatible type):

data = keydefaultdict(lambda key: [key])

Argument 1 to "keydefaultdict" has incompatible type "Callable[[Any], list[Any]]"; expected "Optional[Callable[[], Any]]"

0

1 Answer 1

7

This is intended behavior, as what you're describing violates the Liskov substitution principle. See the mypy documentation for more details.

Using defaultdict as a subclass is a bad idea for this reason. But, if you really want to get around this (not recommended), you can use # type: ignore[override], like so:

default_factory: Callable[[], Any] | Callable[[K], Any] | None # type: ignore[override]

This is described in further detail in the mypy documentation if you want more information.

Sign up to request clarification or add additional context in comments.

1 Comment

I know about LSP, sure, I just wanted to be sure that there's no better way than ignore comment. "No, you can't" is also a valid answer, so thanks anyway, accepted. I think I'll stay with the comment anyway, because it's really minor change that affects only one method, rewriting by hands will take more effort (and will be less intuitive IMO), plus it's a drop-in replacement fully compatible with parent.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.