Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions Doc/library/typing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2486,14 +2486,14 @@ types.

.. warning::

Runtime-checkable protocols are known to be unsafe in several ways.
Runtime-checkable protocols are known to be unsound in several ways.
You should only use them for simple protocols, and even then only use
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about suggesting that they should only be used for method-only protocols? Or just for protocols with dunder methods -- since dunder methods typically have a well-understood signature, so the check is more likely to do the right thing at runtime?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't safe on even just dunder methods. An example of this is numpy arrays and __bool__, see other discussion about type checkers making unsafe assumptions related to this here

them with care.

One issue is that an :func:`isinstance` or :func:`issubclass` check
against a runtime-checkable protocol will only check for the presence of
the protocol's methods or attributes on the object at runtime, paying no
attention to a method's type signature or an attribute's type. This is
against a runtime-checkable protocol will only check for the *presence*
of the protocol's methods or attributes on the object at runtime, paying
no attention to a method's type signature or an attribute's type. This is
problematic given how type checkers often apply type narrowing when they
see these checks:

Expand All @@ -2510,13 +2510,14 @@ types.

def f(obj: object) -> None:
if isinstance(obj, HasX):
# type checker assumes that `obj.x` is an `int`
# because the `isinstance()` check passed
# a type checker may assume that `obj` has an `x` attribute of
# type `int` in this branch, because the `isinstance()` check
# passed
print(f"obj.x + 2 is {obj.x + 2}")
else:
print("obj doesn't have an `x` attribute")

# raises an exception that may not be caught by a type checker because
# this raises an exception that may not be caught by a type checker because
# the `x` attribute on `Foo` objects is a `str`, not an `int`,
# but the `isinstance()` check against the runtime-checkable protocol
# will still pass:
Expand Down
Loading