# Adding type hinting to our application
Type hinting can be really useful, particularly in larger applications. It makes it clear what data types arguments to functions should be, and what data types functions are returning. Also, IDEs can greatly benefit from type hinting in order to offer better suggestions and finding potential errors in your code.
If you're not familiar with type hinting in Python, we have an entire section dedicate to it in our Complete Python Course[1], and also a blog post dedicated to it[2].
# Type hinting primer
# Type hinting parameters
This is how we add type hinting to function parameters:
def my_function(name: str, age: int):
print(name, age)
Just add : TYPE
after each parameter, where TYPE
can be one of:
- A built-in data type, like
str
orint
. - Any class.
Often we use the typing
module[3] to let us add more information. For example, if a function accepts a list of strings, we would do this:
from typing import List
def my_function(names: List[str]):
pass
Note the square brackets, List[str]
, are not a typo!
TIP
Starting with Python 3.9, we will no longer need the typing
module for some of the most common builtin types, so we will be able to do list[str]
without having to import anything[4].
# Type hinting return values
To add type hinting for the return value of a function, we use ->
:
def sum(x: int, y: int) -> int:
return x + y
# Adding type hinting to our code
Let's start with database.py
. I'll go through and add type hinting to the functions where possible.
Note that psycopg2
does not support type hinting, so any connection
parameters will be left un-typed.
Functions in database.py
often return relatively complex values, such as rows from a table. These can be type-hinted as tuples.
For example, a row from the polls
table has three values:
id
, an integertitle
, a stringowner_username
, a string
Any function that returns rows from that table should be type-hinted like this:
from typing import List, Tuple
...
def get_polls(connection) -> List[Tuple[int, str, str]]:
with connection:
with connection.cursor() as cursor:
cursor.execute(SELECT_ALL_POLLS)
return cursor.fetchall()
That can get quite tricky to read, so I usually extract compound types to variables at the top of the file.
I'll create a custom type variable for each result set that our database is getting.
Poll = Tuple[int, str, str]
Vote = Tuple[str, int]
PollWithOption = Tuple[int, str, str, int, str, int]
PollResults = Tuple[int, str, int, float]
As a reminder, here's where they're used:
Poll
is returned by theSELECT_ALL_POLLS
query.Vote
is returned by theSELECT_RANDOM_VOTE
query.PollWithOption
is returned by theSELECT_LATEST_POLL_WITH_OPTIONS
andSELECT_POLL_WITH_OPTIONS
queries.PollResults
is returned by theSELECT_POLL_VOTE_DETAILS
query.
Then I'll go through the functions and add the return value type hints:
def get_polls(connection) -> List[Poll]:
...
def get_latest_poll(connection) -> List[PollWithOption]:
...
def get_poll_details(connection, poll_id: int) -> List[PollWithOption]:
...
def get_poll_and_vote_results(connection, poll_id: int) -> List[PollResults]:
...
def get_random_poll_vote(connection, option_id: int) -> Vote:
...
def create_poll(connection, title: str, owner: str, options: List[str]):
...
def add_poll_vote(connection, username: str, option_id: int):
...
Then I'll go over to app.py
and add the type hint in the one function that might use it:
from typing import List
...
def _print_poll_options(poll_with_options: List[database.PollWithOption]):
...