Skip to content

Commit 91e890f

Browse files
Michael0x2a97littleleaf11AlexWaygood
authored
Add info about classes and types to 'getting started' docs (#6557)
Co-authored-by: 97littleleaf11 <[email protected]> Co-authored-by: Jingchen Ye <[email protected]> Co-authored-by: AlexWaygood <[email protected]>
1 parent 269adee commit 91e890f

File tree

4 files changed

+121
-28
lines changed

4 files changed

+121
-28
lines changed

docs/source/class_basics.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
.. _class-basics:
2+
13
Class basics
24
============
35

docs/source/getting_started.rst

Lines changed: 109 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Getting started
44
===============
55

66
This chapter introduces some core concepts of mypy, including function
7-
annotations, the :py:mod:`typing` module, library stubs, and more.
7+
annotations, the :py:mod:`typing` module, stub files, and more.
88

99
Be sure to read this chapter carefully, as the rest of the documentation
1010
may not make much sense otherwise.
@@ -317,28 +317,119 @@ syntax like so:
317317
# If you're using Python 3.6+
318318
my_global_dict: Dict[int, float] = {}
319319
320-
.. _stubs-intro:
321320
322-
Library stubs and typeshed
323-
**************************
321+
Types and classes
322+
*****************
323+
324+
So far, we've only seen examples of pre-existing types like the ``int``
325+
or ``float`` builtins, or generic types from ``collections.abc`` and
326+
``typing``, such as ``Iterable``. However, these aren't the only types you can
327+
use: in fact, you can use any Python class as a type!
328+
329+
For example, suppose you've defined a custom class representing a bank account:
330+
331+
.. code-block:: python
332+
333+
class BankAccount:
334+
# Note: It is ok to omit type hints for the "self" parameter.
335+
# Mypy will infer the correct type.
336+
337+
def __init__(self, account_name: str, initial_balance: int = 0) -> None:
338+
# Note: Mypy will infer the correct types of your fields
339+
# based on the types of the parameters.
340+
self.account_name = account_name
341+
self.balance = initial_balance
342+
343+
def deposit(self, amount: int) -> None:
344+
self.balance += amount
345+
346+
def withdraw(self, amount: int) -> None:
347+
self.balance -= amount
348+
349+
def overdrawn(self) -> bool:
350+
return self.balance < 0
351+
352+
You can declare that a function will accept any instance of your class
353+
by simply annotating the parameters with ``BankAccount``:
354+
355+
.. code-block:: python
356+
357+
def transfer(src: BankAccount, dst: BankAccount, amount: int) -> None:
358+
src.withdraw(amount)
359+
dst.deposit(amount)
360+
361+
account_1 = BankAccount('Alice', 400)
362+
account_2 = BankAccount('Bob', 200)
363+
transfer(account_1, account_2, 50)
364+
365+
In fact, the ``transfer`` function we wrote above can accept more then just
366+
instances of ``BankAccount``: it can also accept any instance of a *subclass*
367+
of ``BankAccount``. For example, suppose you write a new class that looks like this:
368+
369+
.. code-block:: python
370+
371+
class AuditedBankAccount(BankAccount):
372+
def __init__(self, account_name: str, initial_balance: int = 0) -> None:
373+
super().__init__(account_name, initial_balance)
374+
self.audit_log: list[str] = []
324375
325-
Mypy uses library *stubs* to type check code interacting with library
326-
modules, including the Python standard library. A library stub defines
327-
a skeleton of the public interface of the library, including classes,
328-
variables and functions, and their types. Mypy ships with stubs for
329-
the standard library from the `typeshed
330-
<https://github.com/python/typeshed>`_ project, which contains library
331-
stubs for the Python builtins, the standard library, and selected
332-
third-party packages.
376+
def deposit(self, amount: int) -> None:
377+
self.audit_log.append(f"Deposited {amount}")
378+
self.balance += amount
333379
334-
For example, consider this code:
380+
def withdraw(self, amount: int) -> None:
381+
self.audit_log.append(f"Withdrew {amount}")
382+
self.balance -= amount
383+
384+
Since ``AuditedBankAccount`` is a subclass of ``BankAccount``, we can directly pass
385+
in instances of it into our ``transfer`` function:
335386

336387
.. code-block:: python
337388
338-
x = chr(4)
389+
audited = AuditedBankAccount('Charlie', 300)
390+
transfer(account_1, audited, 100) # Type checks!
391+
392+
This behavior is actually a fundamental aspect of the PEP 484 type system: when
393+
we annotate some variable with a type ``T``, we are actually telling mypy that
394+
variable can be assigned an instance of ``T``, or an instance of a *subclass* of ``T``.
395+
The same rule applies to type hints on parameters or fields.
396+
397+
See :ref:`class-basics` to learn more about how to work with code involving classes.
398+
399+
400+
.. _stubs-intro:
401+
402+
Stubs files and typeshed
403+
************************
339404

340-
Without a library stub, mypy would have no way of inferring the type of ``x``
341-
and checking that the argument to :py:func:`chr` has a valid type.
405+
Mypy also understands how to work with classes found in the standard library.
406+
For example, here is a function which uses the ``Path`` object from the
407+
`pathlib standard library module <https://docs.python.org/3/library/pathlib.html>`_:
408+
409+
.. code-block:: python
410+
411+
from pathlib import Path
412+
413+
def load_template(template_path: Path, name: str) -> str:
414+
# Mypy understands that 'file_path.read_text()' returns a str...
415+
template = template_path.read_text()
416+
417+
# ...so understands this line type checks.
418+
return template.replace('USERNAME', name)
419+
420+
This behavior may surprise you if you're familiar with how
421+
Python internally works. The standard library does not use type hints
422+
anywhere, so how did mypy know that ``Path.read_text()`` returns a ``str``,
423+
or that ``str.replace(...)`` accepts exactly two ``str`` arguments?
424+
425+
The answer is that mypy comes bundled with *stub files* from the
426+
the `typeshed <https://github.com/python/typeshed>`_ project, which
427+
contains stub files for the Python builtins, the standard library,
428+
and selected third-party packages.
429+
430+
A *stub file* is a file containing a skeleton of the public interface
431+
of that Python module, including classes, variables, functions -- and
432+
most importantly, their types.
342433

343434
Mypy complains if it can't find a stub (or a real module) for a
344435
library module that you import. Some modules ship with stubs or inline
@@ -349,7 +440,7 @@ the stubs for the ``requests`` package like this:
349440

350441
.. code-block:: shell
351442
352-
python3 -m pip install types-requests
443+
$ python3 -m pip install types-requests
353444
354445
The stubs are usually packaged in a distribution named
355446
``types-<distribution>``. Note that the distribution name may be
@@ -363,17 +454,8 @@ often suggest the name of the stub distribution:
363454
prog.py:1: note: Hint: "python3 -m pip install types-PyYAML"
364455
...
365456
366-
.. note::
367-
368-
Starting in mypy 0.900, most third-party package stubs must be
369-
installed explicitly. This decouples mypy and stub versioning,
370-
allowing stubs to updated without updating mypy. This also allows
371-
stubs not originally included with mypy to be installed. Earlier
372-
mypy versions included a fixed set of stubs for third-party
373-
packages.
374-
375457
You can also :ref:`create
376-
stubs <stub-files>` easily. We discuss ways of silencing complaints
458+
stubs <stub-files>` easily. We discuss strategies for handling errors
377459
about missing stubs in :ref:`ignore-missing-imports`.
378460

379461
Configuring mypy

docs/source/installed_packages.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ you can create such packages.
3333
example), it is recommended that you also pin the versions of all
3434
your stub package dependencies.
3535

36+
.. note::
37+
38+
Starting in mypy 0.900, most third-party package stubs must be
39+
installed explicitly. This decouples mypy and stub versioning,
40+
allowing stubs to updated without updating mypy. This also allows
41+
stubs not originally included with mypy to be installed. Earlier
42+
mypy versions included a fixed set of stubs for third-party
43+
packages.
44+
3645
Using installed packages with mypy (PEP 561)
3746
********************************************
3847

docs/source/running_mypy.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ this error, try:
322322
In some rare cases, you may get the "Cannot find implementation or library
323323
stub for module" error even when the module is installed in your system.
324324
This can happen when the module is both missing type hints and is installed
325-
on your system in a unconventional way.
325+
on your system in an unconventional way.
326326

327327
In this case, follow the steps above on how to handle
328328
:ref:`missing type hints in third party libraries <missing-type-hints-for-third-party-library>`.

0 commit comments

Comments
 (0)