4

This might sound unreasonable but right now I need to negate a type annotation. I mean something like this

an_int : Not[Iterable]
a_string: Iterable

This is because I wrote an overload for a function and mypy does not understand me. My function looks like this...

@overload
def iterable(o: Iterable) -> Literal[True] : ...

@overload
def iterable(o: Any) -> Literal[False] : ...


def iterable(o: Iterable|Any) -> Literal[True, False] :
    return isinstance(o, Iterable)

But mypy complains that overload 1 overlaps overload 2 and returns incompatible type.

A negating type annotation could easily solve this by using Not[Iterable] instead of Any in overload 2.

Does anyone know how to solve this problem?

Savvii
  • 480
  • 4
  • 9
  • What you name "not iterable" can be such a wide range, why don't you list the acceptable types: like `int | float | str | complex` ? – Florin C. Jan 07 '22 at 10:20
  • Any type is acceptable, but for Iterable types, the return value is always `True` while for non-iterables, the return value is always `False`> – Savvii Jan 07 '22 at 10:57
  • I see no way to do this in Python. In TypeScript it's [possible](https://www.typescriptlang.org/play?#code/CYUwxgNghgTiAEAzArgOzAFwJYHtXwCNYAeAFQD4AKADwC55T4RqMRVgBneVZAWwJAx4Afm4gAboPj1SASnricWYAG4AsAChNRGJQBEACRAQIOPbPUadlANp6oEAA4ALKHoA08PQIxvPegHMoXl43AF0LbVhbCMtrAG8AX0iraPj4OngAFgAmTwBPegB2AGZ4ZLjo3JSgA) to do similar things, e.g. `x: T extends number ? never : T` doesn't allow `x` to be a number, but anything else. Certainly Python typing will evolve, but how far? – md2perpe Jan 09 '22 at 14:00

1 Answers1

0

For your given example you can use Type Guards introduced in Python 3.10.

from typing import TypeGuard, Iterable, Any

def is_iterable(o: Any) -> TypeGuard[Iterable]:
    """
    Check if an object is an iterable

    See https://stackoverflow.com/questions/1952464
    """
    try:
        iter(o)
    except TypeError:
        return False
    return True

a_list = [1,2]
if is_iterable(a_list):
    reveal_type(a_list)  # Revealed type is "typing.Iterable[Any]"
    
a_int = 1
if not is_iterable(a_int):
    reveal_type(a_int)  # Revealed type is "builtins.int"

Check it out at MyPy Playground.

I am still interested in the negative type annotations, but for a use case that can't be solved with TypeGuard.

Kound
  • 1,835
  • 1
  • 17
  • 30