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

1

12

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.

Spandrel answered 8/2, 2022 at 7:15 Comment(0)
S
10

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.

Spandrel answered 24/2, 2022 at 12:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.