Search code examples
pythonpython-typingtypeguards

How can I perform a type guard on a property of an object in Python


PEP 647 introduced type guards to perform complex type narrowing operations using functions. If I have a class where properties can have various types, is there a way that I can perform a similar type narrowing operation on the property of an object given as the function argument?

class MyClass:
    """
    If `a` is `None` then `b` is `str`
    """
    a: Optional[int]
    b: Optional[str]
    # Some other things

def someTypeGuard(my_obj: MyClass) -> ???:
    return my_obj.a is not None

I'm thinking it might be necessary for me to implement something to do with square brackets in type hints, but I really don't know where to start on this.


Solution

  • TypeGuard annotations can be used to annotate subclasses of a class. If parameter types are specified for those classes, then MyPy will recognise the type narrowing operation successfully.

    class MyClass:
        a: Optional[int]
        b: Optional[str]
        # Some other things
    
    # Two hidden classes for the different types
    class _MyClassInt(MyClass):
        a: int
        b: None
    class _MyClassStr(MyClass):
        a: None
        b: str
    
    
    def my_class_has_a(my_obj: MyClass) -> TypeGuard[_MyClassInt]:
        """Check if my_obj's `a` property is NOT `None`"""
        return my_obj.a is not None
    
    def my_class_has_b(my_obj: MyClass) -> TypeGuard[_MyClassStr]:
        """Check if my_obj's `b` property is NOT `None`"""
        return my_obj.b is not None
    

    Sadly failure to narrow to one type doesn't automatically narrow to the other type, and I can't find an easy way to do this other than an assert my_class_has_b(obj) in your else block.

    Even still this seems to be the best solution.