You fail to consider subclasses:
class B(A):
def __bool__(self) -> bool:
return False
reveal_type(foobar(B())) # `int | None` at type checking time,
# but `B` at runtime.
To avoid this pitfall, mark A
as @final
:
from typing import final
@final
class A: ...
As for a better way to write optional_obj and optional_obj.attribute
, see
How do I get Pylance to ignore the possibility of None?.