23

I wrote the following function:

def _clean_dict(d):
    return {k: v for k, v in d.items() if v is not None}

I want to add type annotations to the function:

def _clean_dict(d: Dict[Any, Any]) -> Dict[Any, Any]:                           
    return {k: v for k, v in d.items() if v is not None}

However, I want to explicitly define that the values inside the returned dictionary cannot be None.

Is there a way to say "Any type, except NoneType" or "Every possible value but None"?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Yam Mesicka
  • 6,243
  • 7
  • 45
  • 64
  • You don't actually enforce return type via type hinting. Type hinting exists so when you reference code from elsewhere, you can discover what to expect the function to be returning. It's up to whoever writes the code to make sure the function returns an appropriate value (if it returns anything at all). – MurrayW Sep 09 '19 at 13:21
  • 2
    I didn't asked about *enforcing* the type. Just want the annotation to explicitly show that there should be no `None`/`NoneType` in the values of the returned dictionary. – Yam Mesicka Sep 09 '19 at 13:25
  • Ah, my mistake. You could add this as commentary via a function docstring? – MurrayW Sep 09 '19 at 13:26
  • 2
    I can, but I really do think there should be a way to do it using type hinting, just like you can use `Optional` to show something can be None. – Yam Mesicka Sep 09 '19 at 13:28
  • 1
    It seems obvious from the code that the values cannot be `None` since it is explicitly stated in the dictionary comprehension. As @MurrayW says, a comment or docstring would be what one would use. – QuantumChris Sep 09 '19 at 13:30
  • Related: [PEP 484: exclusive type for type hint](https://stackoverflow.com/q/46313872/7851470) – Georgy Aug 02 '20 at 14:16

2 Answers2

7

Python type hinting can't exclude types. You can't exclude Nones, strs or any another type.

The only thing you can use to try to emulate None exclusion is to use Union and write every type you are actually using in the dictionary.

vurmux
  • 9,420
  • 3
  • 25
  • 45
7

Given that you are willing to fix the types of keys and values when the function is called you can use generics to make this explicit. This still potentially allows instances of V to be None, but it makes the intent pretty clear. Note that you have to use Mapping because of variance issues. However, this is preferable anyway.

from typing import *


K = TypeVar("K")
V = TypeVar("V")


def _clean_dict(d: Mapping[K, Optional[V]]) -> MutableMapping[K, V]:
    return {k: v for k, v in d.items() if v is not None}

With this definition mypy correctly turns optional into non-optional types.

# clean_dict.py

d = {"a": 1, "b": 2, "c": None}
reveal_type(d)
reveal_type(_clean_dict(d))

$ mypy clean_dict.py

note: Revealed type is 'builtins.dict[builtins.str*, Union[builtins.int, None]]'
note: Revealed type is 'typing.MutableMapping[builtins.str*, builtins.int*]'

Jarno
  • 6,243
  • 3
  • 42
  • 57