Search code examples
pythonpython-typingduck-typing

Asserting a variable has certain properties if it passes certain tests


Using the python typing system, I sometimes know the type of a variable or an expression because I have installed runtime checking to verify it. Is there a way to denote this with annotations?

For example, suppose I have a function combop which returns True if its argument is of class Combination. And further suppose that the class Combination has a field called tds.

if combop(x):
   return f(x.tds)
else:
   return f([])

In this example, I'd like to promise the checker that within the then part of the if, that x has type Combination, and x.tds makes sense.

An example of such a situation is line 172 of here python-rte. The IDE complains about d.tds and also n.s. I am supposing that there is a way to explain my intentions with type annotations, and the IDE would accept or verify my claim if possible.

The way this is done in some functional languages (e.g. Scala) is with pattern matching. Within a clause of a pattern match the type inferencer is able to restrict the type of certain variables. In some other languages (e.g. Common Lisp) there is a declare syntax which allows the programmer to give hints to the type inferencer which may or may not contribute a run-time overhead, depending on compiler settings.


Solution

  • Python 3.10 will introduce the new TypeGuard feature, which might provide you with what you're looking for. (The feature is also available through the typing_extensions module for Python <= 3.9.) A TypeGuard allows you to assert to the type-checker that an object is of a certain type. So, you could write a function like this:

    from typing import TypeGuard, Any
    
    def combop(obj: Any) -> TypeGuard[Combination]:
        # Some code that returns `True` if the object is of type `Combination`, else `False`.
    

    If you're more interested in the properties of your object (the "structural type" as opposed to the "nominal type" of your object), then you could use a TypeGuard to assert that your object conforms to a certain protocol.

    from typing import Protocol, TypeGuard, Any
    
    class CombinationsProto(Protocol):
        tds: list[Any]
    
    
    def combops(obj: Any) -> TypeGuard[CombinationsProto]:
        # Some code here that returns `True` if the object has a `tds` attribute, else `False`.
    

    As this is a new feature in the python-typing world, Python's major type-checkers are still working on support for this feature, especially with respect to some complex corner cases.