例外

一部の型チェック動作(型の絞り込みや到達可能性の分析など)では、型チェッカーがコードフローを理解する必要があります。 コードフローは通常、ある文から次の文へと進行しますが、forwhilereturn などの一部の文はコードフローを変更することがあります。 同様に、try/except/finally 文はコードフローに影響を与えるため、型評価にも影響を与える可能性があります。 例:

x = None
try:
    some_function()
    x = 1
except NotImplementedError:
    pass

# この時点での `x` の型は、`some_function` が例外を発生させた場合は None であり、発生させなかった場合は `Literal[1]` である可能性があるため、型チェッカーはこの分析に基づいてその型を絞り込むことを選択する場合があります。
reveal_type(x)  # Literal[1] | None

コンテキストマネージャー

コンテキストマネージャーは、__exit__ メソッドから True``(または他の真値)を返すことで、例外を「抑制」することができます。 このようなコンテキストマネージャーが使用される場合、``with ブロック内で発生し、他にキャッチされない例外はコンテキストマネージャーによってキャッチされ、制御は with ブロックの直後に続きます。 コンテキストマネージャーが例外を抑制しない場合(通常はそうです)、with ブロック内で発生し、他にキャッチされない例外は with ブロックを超えて伝播します。

コードフロー分析を行う型チェッカーは、これらの 2 つのケースを区別できる必要があります。 これは、コンテキストマネージャーの __exit__ メソッドの戻り型注釈を調べることで行われます。

__exit__ メソッドの戻り型が特に bool または Literal[True] である場合、型チェッカーは例外が抑制される*可能性がある*と仮定する必要があります。 他の戻り型の場合、型チェッカーは例外が抑制され*ない*と仮定する必要があります。 例としては、AnyLiteral[False]None、および bool | None があります。

この慣習は、ほとんどのコンテキストマネージャーが例外を抑制しないため選ばれました。 そして、彼らの __exit__ メソッドは bool | None を返すように注釈されることが一般的です。 例外を抑制するコンテキストマネージャーは比較的まれであり、特別なケースと見なされます。

たとえば、次のコンテキストマネージャーは例外を抑制します:

class Suppress:
    def __enter__(self) -> None:
        pass

    def __exit__(self, exc_type, exc_value, traceback) -> bool:
        return True

with Suppress():
    raise ValueError("この例外は抑制されます")

# 例外が抑制されるため、この行は到達可能です。
print("コードは到達可能です")