Skip to content

Commit b7ae424

Browse files
authored
Clarify documentation of Liskov substitution principle (python#8563)
Make things more explicit. Also did some style changes.
1 parent 11c6888 commit b7ae424

File tree

1 file changed

+50
-28
lines changed

1 file changed

+50
-28
lines changed

docs/source/common_issues.rst

Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,12 @@ flagged as an error.
7272
e.g. the :py:func:`pow` builtin returns ``Any`` (see `typeshed issue 285
7373
<https://github.com/python/typeshed/issues/285>`_ for the reason).
7474

75-
- **:py:meth:`__init__ <object.__init__>` method has no annotated
75+
- **:py:meth:`__init__ <object.__init__>` method has no annotated
7676
arguments or return type annotation.** :py:meth:`__init__ <object.__init__>`
77-
is considered fully-annotated **if at least one argument is annotated**,
77+
is considered fully-annotated **if at least one argument is annotated**,
7878
while mypy will infer the return type as ``None``.
7979
The implication is that, for a :py:meth:`__init__ <object.__init__>` method
80-
that has no argument, you'll have to explicitly annotate the return type
80+
that has no argument, you'll have to explicitly annotate the return type
8181
as ``None`` to type-check this :py:meth:`__init__ <object.__init__>` method:
8282

8383
.. code-block:: python
@@ -93,7 +93,7 @@ flagged as an error.
9393
class B():
9494
def __init__(self): # No argument is annotated, considered as untyped method
9595
foo(1) # No error!
96-
96+
9797
class C():
9898
def __init__(self) -> None: # Must specify return type to type-check
9999
foo(1) # error: Argument 1 to "foo" has incompatible type "int"; expected "str"
@@ -751,38 +751,60 @@ Mypy has both type aliases and variables with types like ``Type[...]`` and it is
751751
752752
def fun1(x: Alias) -> None: ... # This is OK
753753
def fun2(x: tp) -> None: ... # error: Variable "__main__.tp" is not valid as a type
754-
754+
755755
Incompatible overrides
756756
------------------------------
757757

758-
It's unsafe to override a method with a more specific argument type, as it violates
759-
the `Liskov substitution principle <https://stackoverflow.com/questions/56860/what-is-an-example-of-the-liskov-substitution-principle>`_. For return types, it's unsafe to override a method with a more general return type.
758+
It's unsafe to override a method with a more specific argument type,
759+
as it violates the `Liskov substitution principle
760+
<https://stackoverflow.com/questions/56860/what-is-an-example-of-the-liskov-substitution-principle>`_.
761+
For return types, it's unsafe to override a method with a more general
762+
return type.
763+
764+
Other incompatible signature changes in method overrides, such as
765+
adding an extra required parameter, or removing an optional parameter,
766+
will also generate errors. The signature of a method in a subclass
767+
should accept all valid calls to the base class method. Mypy
768+
treats a subclass as a subtype of the base class. An instance of a
769+
subclass is valid everywhere where an instance of the base class is
770+
valid.
760771

761-
Here is an example to demonstrate this
772+
This example demonstrates both safe and unsafe overrides:
762773

763774
.. code-block:: python
764775
765776
from typing import Sequence, List, Iterable
766777
767778
class A:
768779
def test(self, t: Sequence[int]) -> Sequence[str]:
769-
pass
770-
771-
# Specific argument type doesn't work
772-
class OverwriteArgumentSpecific(A):
773-
def test(self, t: List[int]) -> Sequence[str]:
774-
pass
775-
776-
# Specific return type works
777-
class OverwriteReturnSpecific(A):
778-
def test(self, t: Sequence[int]) -> List[str]:
779-
pass
780-
781-
# Generic return type doesn't work
782-
class OverwriteReturnGeneric(A):
783-
def test(self, t: Sequence[int]) -> Iterable[str]:
784-
pass
785-
786-
mypy won't report an error for ``OverwriteReturnSpecific`` but it does for ``OverwriteReturnGeneric`` and ``OverwriteArgumentSpecific``.
787-
788-
We can use ``# type: ignore[override]`` to silence the error (add it to the line that genreates the error) if type safety is not needed.
780+
...
781+
782+
class GeneralizedArgument(A):
783+
# A more general argument type is okay
784+
def test(self, t: Iterable[int]) -> Sequence[str]: # OK
785+
...
786+
787+
class NarrowerArgument(A):
788+
# A more specific argument type isn't accepted
789+
def test(self, t: List[int]) -> Sequence[str]: # Error
790+
...
791+
792+
class NarrowerReturn(A):
793+
# A more specific return type is fine
794+
def test(self, t: Sequence[int]) -> List[str]: # OK
795+
...
796+
797+
class GeneralizedReturn(A):
798+
# A more general return type is an error
799+
def test(self, t: Sequence[int]) -> Iterable[str]: # Error
800+
...
801+
802+
You can use ``# type: ignore[override]`` to silence the error. Add it
803+
to the line that generates the error, if you decide that type safety is
804+
not necessary:
805+
806+
.. code-block:: python
807+
808+
class NarrowerArgument(A):
809+
def test(self, t: List[int]) -> Sequence[str]: # type: ignore[override]
810+
...

0 commit comments

Comments
 (0)