22

In Python, is it possible to declare a type hint that excludes certain types from matching?

For example, is there a way to declare a type hint that is "typing.Iterable except not str" or the like?

Julia Meshcheryakova
  • 3,162
  • 3
  • 22
  • 42
fluffy
  • 5,212
  • 2
  • 37
  • 67
  • What about `io.StringIO("a\nb\nc")` or similar `str`-like objects that implement `__iter__`? They are also of type `Iterable` and give you similar results when being iterated over. You'd probably need to write a longer list of excluded types up to the point that you notice that your goal isn't practical. – user511 Dec 30 '22 at 09:14
  • 1
    FWIW this is exactly what I dislike about Python type hints - for anything that isn't absolutely trivial it's difficult to express what you *really* mean and you can tie yourself in knots writing something that ends up not really matching reality (or is so complex as to be unreadable) – DavidW Dec 30 '22 at 22:13

3 Answers3

6

Python type hinting does not support excluding types, however, you can use Union type to specify the types you want to get.

So something like:

def x(x: Iterable[Union[int, str, dict]]):
    pass

x([1]) # correct
x([1, ""]) # correct
x([None]) # not correct

A way to make Union[] shorter if you want to get all types except something you can do:

expected_types = Union[int, str, dict]

def x(x: Iterable[expected_types]):
    pass

This works just like the above code.

Julia Meshcheryakova
  • 3,162
  • 3
  • 22
  • 42
Glyphack
  • 876
  • 9
  • 20
  • 2
    That's what I was afraid of. The problem is I want to support any `Iterable` (containing any sort of object), but *not* `str` (which is iterable) itself. Doing a `Union` of the types I do want to support would be potentially infinitely long. – fluffy Feb 27 '20 at 03:38
  • @fluffy How about using the constant EVERYTYPE_EXCEPT_STR if your functions work like this? – Glyphack Feb 27 '20 at 05:25
  • 2
    I mean I've defined an annotation type that contains the most common iterable types that I want to support (except str) but that's pretty unwieldy. And anyway I was hoping for a more general answer that doesn't necessarily handle the exact situation I described in the question anyway. I was trying to ask something more generic and I'm not intending for the responses to be hung up on my specific requirement. – fluffy Feb 27 '20 at 06:16
  • You can combine this code with a inspect.getmembers( sys.modules[_.__name___], inspect.isclass ) to get a list of all the data types in that specific code piece and remove str from it and then check against a Union of that list. I don't know how well it would work out or any nuances about it but it's a possible solution. – SomeSimpleton Dec 28 '22 at 07:58
  • 1
    @SomeSimpleton the question is about type hints, I don't see how that could possibly be a solution. but furthermore, that would only check global variables – juanpa.arrivillaga Dec 29 '22 at 04:20
-2

Python does not natively support excluding types, but you could add an if statement and then use raise just inside the function that displays an error if a certain type is passed in.

def function_except_str(x: Iterable):
 if type(x) is str:
  raise Exception("Invalid argument passed into function")
 else:
  pass
Silvio Mayolo
  • 62,821
  • 6
  • 74
  • 116
  • 7
    the question is about type hints – juanpa.arrivillaga Dec 29 '22 at 04:18
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Dec 29 '22 at 07:57
  • 3
    This would work perfectly fine at runtime, but is useless for static type checking like what people use type hints for. (Also [`isinstance(x, str)`](https://stackoverflow.com/questions/152580/whats-the-canonical-way-to-check-for-type-in-python) is a more robust check than `type(x) is str`.) – CrazyChucky Dec 30 '22 at 02:57
-2

Yes, it is possible. You can use TypeVar class. Refer to this solution.

from typing import TypeVar, Union

T = TypeVar('T', bound=Iterable)

def func(arg: Union[T, str]) -> T:
    if isinstance(arg, str):
        raise ValueError("arg must not be a str")
    return arg

func will take argument arg in parameter that should be iterable or a String. But type String will throw a value error, this will ensure that input is an iterable.