The comments and documentation in your source code can be just as important as the code. The reason is that software is never finished; you’ll always need to make changes, whether you’re adding new features or fixing bugs. But you can’t change code unless you understand it, so it’s important that you keep it in a readable state. As computer scientists Harold Abelson, Gerald Jay Sussman, and Julie Sussman once wrote, “Programs must be written for people to read, and only incidentally for machines to execute.”
Comments, docstrings, and type hints help you maintain your code’s legibility. Comments are short, plain-English explanations that you write directly in the source code and the computer ignores them. Comments offer helpful notes, warnings, and reminders to others who didn’t write the code, or sometimes even to the code’s programmer in the future. Almost every programmer has asked themselves, “Who wrote this unreadable mess?” only to find the answer is, “I did.”
Docstrings are a Python-specific form of documentation for your functions, methods, and modules. When you specify comments in the docstring format, automated tools, such as documentation generators or Python’s built-in help()
module, make it easy for developers to find information about your code.
Type hints are directives you can add to your Python source code to specify the data types of variables, parameters, and return values. This allows static code analysis tools to verify that your code won’t generate any exceptions due to incorrectly typed values. Type hints first appeared in Python 3.5, but because they’re based on comments, you can use them in any Python version.
This chapter focuses on the aforementioned three techniques for embedding documentation inside your code to make it more readable. External documentation, such as user manuals, online tutorials, and reference materials, are important but not covered in this book. If you want to learn more about external documentation, check out the Sphinx documentation generator at https://www.sphinx-doc.org/.
Like most programming languages, Python supports single-line comments and multiline comments. Any text that appears between the number sign #
and the end of the line is a single-line comment. Although Python doesn’t have a dedicated syntax for multiline comments, a triple-quotes multiline string can serve as one. After all, a string value by itself doesn’t cause the Python interpreter to do anything. Look at this example:
# This is a single-line comment.
"""This is a
multiline string that
also works as a multiline comment. """
If your comment spans multiple lines, it’s better to use a single multiline comment than several consecutive single-line comments, which are harder to read, as you can see here:
"""This is a good way
to write a comment
that spans multiple lines. """
# This is not a good way
# to write a comment
# that spans multiple lines.
Comments and documentation are often afterthoughts in the programming process or are even considered by some to do more harm than good. But as “Myth: Comments Are Unnecessary” on page 83 explained, comments are not optional if you want to write professional, readable code. In this section, we’ll write useful comments that inform the reader without detracting from the program’s readability.
Let’s look at some comments that follow good style practices:
1 # Here is a comment about this code:
someCode()
2 # Here is a lengthier block comment that spans multiple lines using
# several single-line comments in a row.
3 #
# These are known as block comments.
if someCondition:
4 # Here is a comment about some other code:
5 someOtherCode() # Here is an inline comment.
Comments should generally exist on their own line rather than at the end of a line of code. Most of the time, they should be complete sentences with appropriate capitalization and punctuation rather than phrases or single words 1. The exception is that comments should obey the same line-length limits that the source code does. Comments that span multiple lines 2 can use multiple single-line comments in a row, known as block comments. We separate paragraphs in block comments using a blank, single-line comment 3. Comments should have the same level of indentation as the code they’re commenting 4. Comments that follow a line of code are called inline comments5 and at least two spaces should separate the code from the comment.
Single-line comments should have one space after the #
sign:
#Don't write comments immediately after the # sign.
Comments can include links to URLs with related information, but links should never replace comments because linked content could disappear from the internet at any time:
# Here is a detailed explanation about some aspect of the code
# that is supplemented by a URL. More info at https://example.com
The aforementioned conventions are matters of style rather than content, but they contribute to the comments’ readability. The more readable your comments are, the more likely programmers will pay attention to them, and comments are only useful if programmers read them.
Inline comments come at the end of a line of code, as in the following case:
while True: # Keep asking player until they enter a valid move.
Inline comments are brief so they fit within the line-length limits set by the program’s style guide. This means they can easily end up being too short to provide enough information. If you do decide to use inline comments, make sure the comment describes only the line of code it immediately follows. If your inline comment requires more space or describes additional lines of code, put it on its own line.
One common and appropriate use of inline comments is to explain the purpose of a variable or give some other kind of context for it. These inline comments are written on the assignment statement that creates the variable:
TOTAL_DISKS = 5 # More disks means a more difficult puzzle.
Another common use of inline comments is to add context about the values of variables when you create them:
month = 2 # Months range from 0 (Jan) to 11 (Dec).
catWeight = 4.9 # Weight is in kilograms.
website = 'inventwithpython.com' # Don't include "https://" at front.
Inline comments should not specify the variable’s data type, because this is obvious from the assignment statement, unless it’s done in the comment form of a type hint, as described in “Backporting Type Hints with Comments” later in this chapter.
In general, comments should explain why code is written the way it is rather than what the code does or how it does what it does. Even with the proper code style and useful naming conventions covered in Chapters 3 and 4, the actual code can’t explain the original programmer’s intentions. If you wrote the code, you might even forget details about it after a few weeks. Present You should write informative code comments to prevent Future You from cursing Past You.
For example, here’s an unhelpful comment that explains what the code is doing. Rather than hinting at the motivation for this code, it states the obvious:
>>> currentWeekWages *= 1.5 # Multiply the current week's wages by 1.5
This comment is worse than useless. It’s obvious from the code that the currentWeekWages
variable is being multiplied by 1.5
, so omitting the comment entirely would simplify your code. The following would be a much better comment:
>>> currentWeekWages *= 1.5 # Account for time-and-a-half wage rate.
This comment explains the intention behind this line of code rather than repeating how the code works. It provides context that even well-written code cannot.
Explaining the programmer’s intent isn’t the only way comments can be useful. Brief comments that summarize several lines of code allow the reader to skim the source code and get a general idea of what it does. Programmers often use a blank space to separate “paragraphs” of code from each other, and the summarizing comments usually occupy one line at the start of these paragraphs. Unlike one-line comments that explain single lines of code, the summarizing comments describe what the code does at a higher level of abstraction.
For example, you can tell from reading these four lines of code that they set the playerTurn
variable to a value representing the opposite player. But the short, single-line comment spares the reader from having to read and reason about the code to understand the purpose of doing this:
# Switch turns to other player:
if playerTurn == PLAYER_X:
playerTurn = PLAYER_O
elif playerTurn == PLAYER_O:
playerTurn = PLAYER_X
A scattering of these summary comments throughout your program makes it much easier to skim. The programmer can then take a closer look at any particular points of interest. Summary comments can also prevent programmers from developing a misleading idea about what the code does. A brief, summarizing comment can confirm that the developer properly understood how the code works.
When I worked at a software company, I was once asked to adapt a graph library so it could handle the real-time updates of millions of data points in a chart. The library we were using could either update graphs in real time or support graphs with millions of data points, but not both. I thought I could finish the task in a few days. By the third week, I was still convinced that I could finish in a few days. Each day, the solution seemed to be just around the corner, and during the fifth week I had a working prototype.
During this entire process, I learned a lot of details about how the graphing library worked and what its capabilities and limitations were. I then spent a few hours writing up these details into a page-long comment that I placed in the source code. I knew that anyone else who needed to make changes to my code later would encounter all the same, seemingly simple issues I had, and that the documentation I was writing would save them literally weeks of effort.
These lessons-learned comments, as I call them, might span several paragraphs, making them seem out of place in a source code file. But the information they contain is gold for anyone who needs to maintain this code. Don’t be afraid to write lengthy, detailed comments in your source code file to explain how something works. To other programmers, many of these details will be unknown, misunderstood, or easily overlooked. Software developers who don’t need them can easily skip past them, but developers who do need them will be grateful for them. Remember that, as with other comments, a lessons-learned comment is not the same as module or function documentation (which docstrings handle). It also isn’t a tutorial or how-to guide aimed at users of the software. Instead, lessons-learned comments are for developers reading the source code.
Because my lessons-learned comment concerned an open source graph library and could be useful to others, I took a moment to post it as an answer to the public question-and-answer site https://stackoverflow.org, where others in a similar situation could find it.
Some software companies or open source projects have a policy of including copyright, software license, and authorship information in comments at the top of each source code file for legal reasons. These annotations should consist of a few lines at most and look something like this:
"""Cat Herder 3.0 Copyright (C) 2021 Al Sweigart. All rights reserved.
See license.txt for the full text."""
If possible, refer to an external document or website that contains the full text of the license rather than including the entire lengthy license at the top of every source code file. It’s tiring to have to scroll past several screen lengths of text whenever you open a source code file, and including the full license doesn’t provide additional legal protection.
At my first software job, a senior co-worker I greatly respected took me aside and explained that because we sometimes released our products’ source code to clients, it was important that the comments maintain a professional tone. Apparently, I had written “WTF” in one of the comments for an especially frustrating part of the code. I felt embarrassed, immediately apologized, and edited the comment. Since that moment, I’ve kept my code, even for personal projects, at a certain level of professionalism.
You might be tempted to add levity or vent your frustrations in your program’s comments, but make it a habit to avoid doing so. You don’t know who will read your code in the future, and it’s easy to misinterpret the tone of a text. As explained in “Avoid Jokes, Puns, and Cultural References” on page 64, the best policy is to write your comments in a polite, direct, and humorless tone.
Programmers sometimes leave short comments to remind themselves about work that remains to be done. This usually takes the form of a codetag: a comment with an all-uppercase label, such as TODO
, followed by a short description. Ideally, you would use project management tools to track these sorts of issues rather than burying them deep in the source code. But for smaller, personal projects that aren’t using such tools, the occasional TODO
comment can serve as a helpful reminder. Here’s an example:
_chargeIonFluxStream() # TODO: Investigate why this fails every Tuesday.
You can use a few different codetags for these reminders:
TODO
Introduces a general reminder about work that needs to be doneFIXME
Introduces a reminder that this part of the code doesn’t entirely workHACK
Introduces a reminder that this part of the code works, perhaps barely, but that the code should be improvedXXX
Introduces a general warning, often of high severityYou should follow these always-uppercase labels with more specific descriptions of the task or problem at hand. Later, you can search the source code for the labels to find the code that needs fixing. The downside is that you can easily forget about these reminders unless you happen to be reading the section of your code that they’re in. Codetags shouldn’t replace a formal issue tracker or bug reporter tool. If you do use a codetag in your code, I recommend keeping it simple: use only TODO
and forgo the others.
You might have seen .py source files with something like the following lines at the top:
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
These magic comments, which always appear at the top of the file, provide interpreter or encoding information. The shebang line 1 (introduced in Chapter 2) tells your operating system which interpreter to use to run the instructions in the file.
The second magic comment is an encoding definition line 2. In this case, the line defines UTF-8 as the Unicode encoding scheme to use for the source file. You almost never need to include this line, because most editors and IDEs already save source code files in the UTF-8 encoding, and Python versions starting with Python 3.0 treat UTF-8 as the defined encoding by default. Files encoded in UTF-8 can contain any character, so your .py source file will be just fine whether it includes English, Chinese, or Arabic letters. For an introduction to Unicode and string encodings, I highly recommend Ned Batchelder’s blog post, “Pragmatic Unicode” at https://nedbatchelder.com/text/unipain.html.
Docstrings are multiline comments that appear either at the top of a module’s .py source code file or directly following a class
or def
statement. They provide documentation about the module, class, function, or method being defined. Automated documentation generator tools use these docstrings to generate external documentation files, such as help files or web pages.
Docstrings must use triple-quoted, multiline comments rather than single-line comments that begin with a hash mark, #
. Docstrings should always use three double quotes for its triple-quoted strings rather than three single quotes. For example, here is an excerpt from the sessions.py file in the popular requests
module:
1 # -*- coding: utf-8 -*-
2 """
requests.session
~~~~~~~~~~~~~~~~
This module provides a Session object to manage and persist settings across
requests (cookies, auth, proxies).
"""
import os
import sys
--snip—
class Session(SessionRedirectMixin):
3 """A Requests session.
Provides cookie persistence, connection-pooling, and configuration.
Basic Usage::
>>> import requests
>>> s = requests.Session()
>>> s.get('https://httpbin.org/get')
<Response [200]>
--snip--
def get(self, url, **kwargs):
4 r"""Sends a GET request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:rtype: requests.Response
"""
--snip--
The sessions.py file’s request
contains docstrings for the module 2, the Session
class 3, and the Session
class’s get()
method 4. Note that although the module’s docstring must be the first string to appear in the module, it should come after any magic comments, such as the shebang line or encoding definition 1.
Later, you can retrieve the docstring for a module, class, function, or method by checking the respective object’s __doc__
attribute. For example, here we examine the docstrings to find out more about the sessions
module, the Session
class, and the get()
method:
>>> from requests import sessions
>>> sessions.__doc__
'\nrequests.session\n~~~~~~~~~~~~~~~~\n\nThis module provides a Session object to manage and persist settings across\nrequests (cookies, auth, proxies).\n'
>>> sessions.Session.__doc__
"A Requests session.\n\n Provides cookie persistence, connection-pooling, and configuration.\n\n Basic Usage::\n\n >>> import requests\n
--snip--
>>> sessions.Session.get.__doc__
'Sends a GET request. Returns :class:`Response` object.\n\n :param url: URL for the new :class:`Request` object.\n :param \\*\\*kwargs:
--snip--
Automated documentation tools can take advantage of docstrings to provide context-appropriate information. One of these tools is Python’s built-in help()
function, which displays the docstring of the object you pass it in a more readable format than the raw __doc__
string directly. This is useful when you’re experimenting in the interactive shell, because you can immediately pull up information on any modules, classes, or functions you’re trying to use:
>>> from requests import sessions
>>> help(sessions)
Help on module requests.sessions in requests:
NAME
requests.sessions
DESCRIPTION
requests.session
~~~~~~~~~~~~~~~~
This module provides a Session object to manage and persist settings
-- More --
If the docstring is too large to fit onscreen, Python displays -- More --
at the bottom of the window. You can press Enter to scroll to the next line, press the spacebar to scroll to the next page, or press Q to quit viewing the docstring.
Generally speaking, a docstring should contain a single line that summarizes the module, class, or function, followed by a blank line and more detailed information. For functions and methods, this can include information about their parameters, return value, and side effects. We write docstrings for other programmers rather than users of the software, so they should contain technical information, not tutorials.
Docstrings provide a second key benefit because they integrate documentation into the source code. When you write documentation separate from code, you can often forget about it entirely. Instead, when you place docstrings at the top of the modules, classes, and functions, the information remains easy to review and update.
You might not always immediately be able to write docstrings if you’re still working on the code it’s meant to describe. In that case, include a TODO
comment in the docstring as a reminder to fill in the rest of the details. For example, the following fictional reverseCatPolarity()
function has a poor docstring that states the obvious:
def reverseCatPolarity(catId, catQuantumPhase, catVoltage):
"""Reverses the polarity of a cat.
TODO Finish this docstring."""
--snip--
Because every class, function, and method should have a docstring, you might be tempted to write only the minimal amount of documentation and move on. Without a TODO
comment, it’s easy to forget that this docstring will eventually need rewriting.
PEP 257 contains further documentation on docstrings at https://www.python.org/dev/peps/pep-0257/.
Many programming languages have static typing, meaning that programmers must declare the data types of all variables, parameters, and return values in the source code. This allows the interpreter or compiler to check that the code uses all objects correctly before the program runs. Python has dynamic typing: variables, parameters, and return values can be of any data type or even change data types while the program runs. Dynamic languages are often easier to program with because they require less formal specification, but they lack the bug-preventing advantages that static languages have. If you write a line of Python code, such as round('forty two')
, you might not realize that you’re passing a string to a function that accepts only int
or float
arguments until you run the code and it causes an error. A statically typed language gives you an early warning when you assign a value or pass an argument of the wrong type.
Python’s type hints offer optional static typing. In the following example, the type hints are in bold:
def describeNumber(number: int) -> str:
if number % 2 == 1:
return 'An odd number. '
elif number == 42:
return 'The answer. '
else:
return 'Yes, that is a number. '
myLuckyNumber: int = 42
print(describeNumber(myLuckyNumber))
As you can see, for parameters or variables, the type hint uses a colon to separate the name from the type, whereas for return values, the type hint uses an arrow ( ->
) to separate the def
statement’s closing parentheses from the type. The describeNumber()
function’s type hints show that it takes an integer value for its number
parameter and returns a string value.
If you use type hints, you don’t have to apply them to every bit of data in your program. Instead, you could use a gradual typing approach, which is a compromise between the flexibility of dynamic typing and the safety of static typing in which you include type hints for only certain variables, parameters, and return values. But the more type hinted your program is, the more information the static code analysis tool has to spot potential bugs in your program.
Notice in the preceding example that the names of the specified types match the names of the int()
and str()
constructor functions. In Python, class, type, and data type have the same meaning. For any instances made from classes, you should use the class name as the type:
import datetime
1 noon: datetime.time = datetime.time(12, 0, 0)
class CatTail:
def __init__(self, length: int, color: str) -> None:
self.length = length
self.color = color
2 zophieTail: CatTail = CatTail(29, 'grey')
The noon
variable has the type hint datetime.time
1 because it’s a time
object (which is defined in the datetime
module). Likewise, the zophieTail
object has the CatTail
type hint 2 because it’s an object of the CatTail
class we created with a class
statement. Type hints automatically apply to all subclasses of the specified type. For example, a variable with the type hint dict
could be set to any dictionary value but also to any collections.OrderedDict
and collections.defaultdict
values, because these classes are subclasses of dict
. Chapter 16 covers subclasses in more detail.
Static type-checking tools don’t necessarily need type hints for variables. The reason is that static type-checking tools do type inference, inferring the type from the variable’s first assignment statement. For example, from the line spam = 42
, the type checker can infer that spam is supposed to have a type hint of int
. But I recommend setting a type hint anyway. A future change to a float
, as in spam = 42.0
, would also change the inferred type, which might not be your intention. It’s better to force the programmer to change the type hint when changing the value to confirm that they’ve made an intentional rather than incidental change.
Although Python supports syntax for type hints, the Python interpreter completely ignores them. If you run a Python program that passes an invalidly typed variable to a function, Python will behave as though the type hints don’t exist. In other words, type hints don’t cause the Python interpreter to do any runtime type checking. They exist only for the benefit of static type-checking tools, which analyze the code before the program runs, not while the program is running.
We call these tools static analysis tools because they analyze the source code before the program runs, whereas runtime analysis or dynamic analysis tools analyze running programs. (Confusingly, static and dynamic in this case refer to whether the program is running, but static typing and dynamic typing refer to how we declare the data types of variables and functions. Python is a dynamically typed language that has static analysis tools, such as Mypy, written for it.)
Although Python doesn’t have an official type-checker tool, Mypy is currently the most popular third-party type checker. You can install Mypy with pip
by running this command:
python –m pip install –user mypy
Run python3
instead of python
on macOS and Linux. Other well-known type checkers include Microsoft’s Pyright, Facebook’s Pyre, and Google’s Pytype.
To run the type checker, open a Command Prompt or Terminal window and run the python –m mypy
command (to run the module as an application), passing it the filename of the Python code to check. In this example, I’m checking the code for an example program I created in a file named example.py:
C:\Users\Al\Desktop>python –m mypy example.py
Incompatible types in assignment (expression has type "float", variable has type "int")
Found 1 error in 1 file (checked 1 source file)
The type checker outputs nothing if there are no problems and prints error messages if there are. In this example.py file, there’s a problem on line 171, because a variable named spam
has a type hint of int
but is being assigned a float
value. This could possibly cause a failure and should be investigated. Some error messages might be hard to understand at first reading. Mypy can report a large number of possible errors, too many to list here. The easiest way to find out what the error means is to search for it on the web. In this case, you might search for something like “Mypy incompatible types in assignment.”
Running Mypy from the command line every time you change your code is rather inefficient. To make better use of a type checker, you’ll need to configure your IDE or text editor to run it in the background. This way, the editor will constantly run Mypy as you type your code and then display any errors in the editor. Figure 11-1 shows the error from the previous example in the Sublime Text text editor.
The steps to configure your IDE or text editor to work with Mypy differ depending on which IDE or text editor you’re using. You can find instructions online by searching for “<your IDE> Mypy configure,” “<your IDE> type hints setup,” or something similar. If all else fails, you can always run Mypy from the Command Prompt or Terminal window.
You might write code that for whatever reason you don’t want to receive type hint warnings about. To the static analysis tool, the line might appear to use the incorrect type, but it’s actually fine when the program runs. You can suppress any type hint warnings by adding a # type: ignore
comment to the end of the line. Here is an example:
def removeThreesAndFives(number: int) -> int:
number = str(number) # type: ignore
number = number.replace('3', '').replace('5', '') # type: ignore
return int(number)
To remove all the 3
and 5
digits from the integer passed to removeThreesAndFives()
, we temporarily set the integer number variable to a string. This causes the type checker to warn us about the first two lines in the function, so we add the # type: ignore
type hints to these lines to suppress the type checker’s warnings.
Use # type: ignore
sparingly. Ignoring warnings from the type checker provides an opening for bugs to sneak into your code. You can almost certainly rewrite your code so the warnings don’t occur. For example, if we create a new variable with numberAsStr = str(number)
or replace all three lines with a single return int(str(number.replace('3', '').replace('5', '')))
line of code, we can avoid reusing the number
variable for multiple types. We wouldn’t want to suppress the warning by changing the type hint for the parameter to Union[int, str]
, because the parameter is meant to allow integers only.
Python’s variables, parameters, and return values can have multiple data types. To accommodate this, you can specify type hints with multiple types by importing Union
from the built-in typing
module. Specify a range of types inside square brackets following the Union
class name:
from typing import Union
spam: Union[int, str, float] = 42
spam = 'hello'
spam = 3.14
In this example, the Union[int, str, float]
type hint specifies that you can set spam
to an integer, string, or floating-point number. Note that it’s preferable to use the from typing import X
form of the import
statement rather than the import typing
form and then consistently use the verbose typing.X
for type hints throughout your program.
You might specify multiple data types in situations where a variable or return value could have the None
value in addition to another type. To include NoneType
, which is the type of the None
value, in the type hint, place None
inside the square brackets rather than NoneType
. (Technically, NoneType
isn’t a built-in identifier the way int
or str
is.)
Better yet, instead of using, say, Union[str, None]
, you can import Optional
from the typing
module and use Optional[str]
. This type hint means that the function or method could return None
rather than a value of the expected type. Here’s an example:
from typing import Optional
lastName: Optional[str] = None
lastName = 'Sweigart'
In this example, you could set the lastName
variable to None
or a str
value. But it’s best to make sparing use of Union
and Optional
. The fewer types your variables and functions allow, the simpler your code will be, and simple code is less bug prone than complicated code. Remember the Zen of Python maxim that simple is better than complex. For functions that return None
to indicate an error, consider raising an exception instead. See “Raising Exceptions vs. Returning Error Codes” on page 178.
You can use the Any
type hint (also from the typing
module) to specify that a variable, parameter, or return value can be of any data type:
from typing import Any
import datetime
spam: Any = 42
spam = datetime.date.today()
spam = True
In this example, the Any
type hint allows you to set the spam
variable to a value of any data type, such as int
, datetime.date
, or bool
. You can also use object
as the type hint, because this is the base class for all data types in Python. But Any
is a more readily understandable type hint than object
.
As you should with Union
and Optional
, use Any
sparingly. If you set all of your variables, parameters, and return values to the Any
type hint, you’d lose the type-checking benefits of static typing. The difference between specifying the Any
type hint and specifying no type hint is that Any
explicitly states that the variable or function accepts values of any type, whereas an absent type hint indicates that the variable or function has yet to be type hinted.
Lists, dictionaries, tuples, sets, and other container data types can hold other values. If you specify list
as the type hint for a variable, that variable must contain a list, but the list could contain values of any type. The following code won’t cause any complaints from a type checker:
spam: list = [42, 'hello', 3.14, True]
To specifically declare the data types of the values inside the list, you must use the typing
module’s List
type hint. Note that List
has a capital L, distinguishing it from the list
data type:
from typing import List, Union
1 catNames: List[str] = ['Zophie', 'Simon', 'Pooka', 'Theodore']
2 numbers: List[Union[int, float]] = [42, 3.14, 99.9, 86]
In this example, the catNames
variable contains a list of strings, so after importing List
from the typing
module, we set the type hint to List[str]
1. The type checker catches any call to the append()
or insert()
method, or any other code that puts a nonstring value into the list. If the list should contain multiple types, we can set the type hint using Union
. For example, the numbers
list can contain integer and float values, so we set its type hint to List[Union[int, float]]
2.
The typing
module has a separate type alias for each container type. Here’s a list of the type aliases for common container types in Python:
List
is for the list
data type.Tuple
is for the tuple
data type.Dict
is for the dictionary (dict
) data type.Set
is for the set
data type.FrozenSet
is for the frozenset
data type.Sequence
is for the list
, tuple
, and any other sequence data type.Mapping
is for the dictionary (dict
), set
, frozenset
, and any other mapping data type.ByteString
is for the bytes
, bytearray
, and memoryview
types.You’ll find the full list of these types online at https://docs.python.org/3/library/typing.html#classes-functions-and-decorators.
Backporting is the process of taking features from a new version of software and porting (that is, adapting and adding) them to an earlier version. Python’s type hints feature is new to version 3.5. But in Python code that might be run by interpreter versions earlier than 3.5, you can still use type hints by putting the type information in comments. For variables, use an inline comment after the assignment statement. For functions and methods, write the type hint on the line following the def
statement. Begin the comment with type:
, followed by the data type. Here’s an example of some code with type hints in the comments:
1 from typing import List
2 spam = 42 # type: int
def sayHello():
3 # type: () -> None
"""The docstring comes after the type hint comment."""
print('Hello!')
def addTwoNumbers(listOfNumbers, doubleTheSum):
4 # type: (List[float], bool) -> float
total = listOfNumbers[0] + listOfNumbers[1]
if doubleTheSum:
total *= 2
return total
Note that even if you’re using the comment type hint style, you still need to import the typing
module 1, as well as any type aliases that you use in the comments. Versions earlier than 3.5 won’t have a typing
module in their standard library, so you must install typing
separately by running this command:
python –m pip install --user typing
Run python3
instead of python
on macOS and Linux.
To set the spam
variable to an integer, we add # type: int
as the end-of-line comment 2. For functions, the comment should include parentheses with a comma-separated list of type hints in the same order as the parameters. Functions with zero parameters would have an empty set of parentheses 3. If there are multiple parameters, separate them inside the parentheses with commas 4.
The comment type hint style is a bit less readable than the normal style, so use it only for code that might be run by versions of Python earlier than 3.5.
Programmers often forget about documenting their code. But by taking a little time to add comments, docstrings, and type hints to your code, you can avoid wasting time later. Well-documented code is also easier to maintain.
It’s tempting to adopt the opinion that comments and documentation don’t matter or are even a disadvantage when writing software. (Conveniently, this view allows programmers to avoid the work of writing documentation.) Don’t be fooled; well-written documentation consistently saves you far more time and effort than it takes to write it. It’s more common for programmers to stare at a screen of inscrutable, uncommented code than to have too much helpful information.
Good comments offer concise, useful, and accurate information to programmers who need to read the code at a later time and understand what it does. These comments should explain the original programmer’s intent and summarize small sections of code rather than state the obvious about what a single line of code does. Comments sometimes offer detailed accounts of lessons the programmer learned while writing the code. This valuable information might spare future maintainers from having to relearn these lessons the hard way.
Docstrings, a Python-specific kind of comment, are multiline strings that appear immediately after the class
or def
statement, or at the top of the module. Documentation tools, such as Python’s built-in help()
function, can extract docstrings to provide specific information about the purpose of the class, function, or module.
Introduced in Python 3.5, type hints bring gradual typing to Python code. Gradual typing allows the programmer to apply the bug-detection benefits of static typing while maintaining the flexibility of dynamic typing. The Python interpreter ignores type hints, because Python doesn’t have runtime type checking. Even so, static type-checking tools use type hints to analyze the source code when it isn’t running. Type checkers, such as Mypy, can ensure that you aren’t assigning invalid values to the variables you pass to functions. This saves you time and effort by preventing a broad category of bugs.