Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-43453: Update and re-add example to typing runtime_checkable #27013

Merged
Merged
Changes from 2 commits
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
8 changes: 6 additions & 2 deletions Doc/library/typing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1232,8 +1232,12 @@ These are not used in annotations. They are building blocks for creating generic

.. note::

:func:`runtime_checkable` will check only the presence of the required methods,
not their type signatures.
:func:`runtime_checkable` will check only the presence of the required
methods, not their type signatures. For example, :class:`ssl.SSLObject`
implements :func:`__init__`, therefore it passes an :func:`issubclass`
akulakov marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

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

Runtime protocol checking is an area which I'm not too familiar with so I may be wrong here, but isn't the requirement for Callable to define __call__, not __init__?

https://docs.python.org/3/library/collections.abc.html#collections.abc.Callable

Copy link
Contributor Author

Choose a reason for hiding this comment

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

__call__ makes the instances callable, not the class itself. In my testing, a class meets the Callable check if it doesn't have __call__ defined, and it works if init is either explicitly defined or implicitly inherited from object. However, this class -- SSLObject -- cannot be called because init raises an exception. So this example illustrates that check for callable will tell you SSLObject is callable when in fact it's not callable in practice due to the dunder method raising an exception.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

[ins] In [89]: class A:
          ...:     def __init__(self):0
          ...:

[ins] In [90]: isinstance(A,Callable)
Out[90]: True

[ins] In [91]: class B:pass

[ins] In [92]: isinstance(B,Callable)
Out[92]: True

Copy link
Member

Choose a reason for hiding this comment

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

Wow, interesting sleuthing. Thanks for investigating!

Copy link
Member

@Fidget-Spinner Fidget-Spinner Jul 4, 2021

Choose a reason for hiding this comment

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

I wonder if this has anything to play in it, but a class implicitly has __call__ from the type object:

>>> class A: ...
>>> A.__call__
<method-wrapper '__call__' of type object at 0x0000028F6E186DE0>
>>> type(A).__call__
<slot wrapper '__call__' of 'type' objects>

>>> type(A).__call__(A)
<__main__.A object at 0x0000028F6DE94280>
>>> A()
<__main__.A object at 0x0000028F6DE8BED0>

As you can see, the last two are equivalent. This corresponds to what the docs say about callables https://docs.python.org/3/reference/datamodel.html#object.__call__. Also mentioned here for callable() https://docs.python.org/3/library/functions.html#callable

The part I'm nitpicky about is saying ssl.SSLObject passes the issubclass check because it defines __init__. I think that's untrue -- it passes the check because all classes are callable (as mentioned in the docs for callable() above). I completely agree with the part about __init__ being used for raising exceptions.
I think the phrasing would be better if it became this:

        :func:`runtime_checkable` will check only the presence of the required
        methods, not their type signatures. For example, :class:`ssl.SSLObject`
        is a class, therefore it passes an :func:`issubclass`
        check against :data:`Callable`.  However, the
        :meth:`ssl.SSLObject.__init__` method exists only to raise a
        :exc:`TypeError` with a more informative message.

Sorry about the bikshedding!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right, good catch! I think your edit is good, but I want to highlight the fact that init as implemented makes this class uncallable, maybe something like this:

        :func:`runtime_checkable` will check only the presence of the required
        methods, not their type signatures. For example, :class:`ssl.SSLObject`
        is a class, therefore it passes an :func:`issubclass`
        check against :data:`Callable`.  However, the
        :meth:`ssl.SSLObject.__init__` method exists only to raise a
        :exc:`TypeError` with a more informative message, therefore making
        it impossible to call (instantiate) :class:`ssl.SSLObject`.

WDYT?

Copy link
Member

Choose a reason for hiding this comment

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

Great!

check against :data:`Callable`. However, the
:meth:`ssl.SSLObject.__init__` method exists only to raise a
:exc:`TypeError` with a more informative message.

.. versionadded:: 3.8

Expand Down