I think it does for arbitrary try/except where the appropriate error (keyerror, in the case of typeddict) is caught. In any case where
non_total_dict: SomeTypedDict
# type of value for key: "key" is int, but total=False
try:
# [arbitrary expressions here]
x = non_total_dict["key"]
# [arbitrary expressions here]
except KeyError:
# no assumption about x's type allowed
else:
# x must be an int
and also:
try:
# [arbitrary expressions here]
x = non_total_dict["key"]
# [arbitrary expressions here]
except KeyError:
# [ some arbitrary statements, maybe logging, idk]
raise # or return
# x must be an int
determining that x must not be unbound here would be an equivalent means of determining that x have a value, and that the type of that value must be int
if this provides a better way for type checkers to implement this.
For completeness, in a finally
block, we also shouldn’t assume based on EAFP patterns.
I needed to give this more thought and double-check what we can know statically from context managers. I don’t think the behavior I gave for else holds for context managers which indicates that they suppress exceptions (via their return value), as statically we only know that if we continue executing from a context manager there either was an exception that the context manager could handle, or that there wasn’t an exception.
We can’t know which exceptions were handled statically or if there wasn’t an exception, but the intent was to handle a specific class of exception, so context managers that suppress some exceptions can’t be statically known to be being used for this pattern.
If the examples above were modified for contextmanager use (including anything from open
to contextlib.supress(KeyError)
and everything inbetween), x
could be unbound.
For context managers which indicate that they don’t handle exceptions, they also wouldn’t be acting as a guard on partial structural access, and aren’t participating in EAFP-like patterns.