Lesson 15 - Type System and Type Hints in Python
In the last lesson, Datetime Library for Python, we explained the date and time.
Despite that Python is dynamically typed, it's still a strongly typed language that requires assignment of data types. Although this usually happens automatically, there are still cases where it's appropriate to specify types in Python manually and explicitly. For example, by specifying types, our IDE can check the accuracy of our code better. This practice is very useful for larger projects.
Python itself ignores the types we specify and always assigns
them itself, no matter what we explicitly state. This statement really only
serves as information for us, for the IDE or mypy
tool that validates
types.
Basic Types in Python and Their Use
The basic types in Python are:
int
for an integerfloat
for a real numberbool
forTrue
/False
valuesstr
for stringsNone
forNone
If we want to explicitly declare a variable type, we use the colon operator
:
:
text: str = "Hello world!"
It's now clearly stated that text
is of type str
.
Here it would be equally recognizable by the value of
"Hello world!"
, so this specification is a little unnecessary. But
there are a lot of places in the program where it's no longer so clear. E.g. we
can also type parameters of functions and their return values:
def generate_hello(name: str) -> str: return "Hello, " + name + "!"
With the already mentioned tools, it's then possible to check if everything's OK, or to generate documentation for the functions, including types.
Other Types in Python
Of course, these are not all the types that can appear in Python. Surely you
can name many more - list
, dict
, etc. A variable of
type list
can be marked with:
my_list: list = [1, 2, 3]
But this wouldn't be very useful to us. Although we'd know that the given
variable is a list
, we wouldn't know of what type the elements in
this list are.
The typing
Library
This is exactly what the typing
library will help us with:
from typing import List, Set, Dict, Optional, Callable
Typing Lists and Sets
The first two imported items are certainly intuitive, the type of elements stored in them is determined by brackets:
my_list: List[int] = [1, 2, 3] my_set: Set[int] = set(my_list)
Dictionaries
If we want to type a dictionary, one such parameter is not enough. So we'll need two parameters - the first for the key type and the second for the value type:
my_dictionary: Dict[int, str] = {1: 'one', 2: 'two', 3: 'three'}
Of course, we can use more complex types as parameters than just the primitive ones:
my_dictionary2: Dict[int, List[str]] = {1: ['one', 'uno'], 2: ['two', 'dos'], 3: ['three', 'tres']}
Optional
But when can the Optional
type come in handy?
Optional
in short means that a given variable can take values of
the type of the first parameter or None
. This is useful, for
example, if we want to create a division function and want to prevent division
by zero:
def division(a: float, b: float) -> Optional[float]: if b == 0: return None # cannot divide by zero return a / b
Callable
The last type we imported is called Callable
. As the name
suggests, this is a type of something that can be called - a function:
def add_one(a: int) -> int: return a + 1 operation: Callable = add_one print(operation(5)) # prints 6
Creating Custom Types
There are three ways to create Python types:
- type aliases
- classes
- named dictionary
Type Aliases
If we use some complicated type often, we can create a type alias, which is created by simply assigning the original type to a variable. For example, this can be useful when defining matrices:
my_matrix: List[List[int]] = [[1, 2], [3, 4]]
can be written, thanks to creating a type alias, as:
Matrix = List[List[int]] my_matrix: Matrix = [[1, 2], [3, 4]]
Classes
Each class is also considered its own type, whether we import it or create it ourselves:
class MyClass: pass my_variable: MyClass = MyClass() from queue import Queue my_queue: Queue = Queue()
The problem we might encounter would be that we'd like to use a type that's declared later in the file (or the type of the given class itself). Therefore, the parser doesn't know it at the time of reading the given line. We can prevent this by enclosing its name in quotation marks as a string instead of a direct reference to the class. E.g.:
from typing import Optinal, Any class LinkedList: def __init__(self): self.value: Optional[Any] = Noneelements self.next: Optional["LinkedList"] = None
Typed Dictionary
The last kind of types we can create in Python that we'll discuss today is
the creation of a typed dictionary (TypedDict
), which first
appeared in Python 3.8. This is a kind of abstraction over the classic
dictionary, but we'll explicitly state which keys with what type of values can
be found in the dictionary:
from typing import TypedDict class HouseAddress(TypedDict): city: str street: str house_number: int HouseAddress = TypedDict('HouseAddress', city=str, street=str, house_number=int) HouseAddress = TypedDict('HouseAddress', {'city': str, 'street': str, 'house_number': int}) my_address: HouseAddress = {'city': 'Nottingham', 'street': 'Meadway', 'house_number': 52}
Alternative Typing System
This system, which we have shown, has been available in Python since version
3.5, so if you're using Python 3.4 and older, we don't have anything from the
typing
library or the colon operator available. Nevertheless, we
can still type - using comments. So, for example, we'd declare a string variable
as follows:
my_text = "Hello" # type: str
This system can also be used in Python 3.5 and later, and in some specific situations it is still the only possible way of typing without the interpreter reporting a piece of code that it cannot parse.
Further Studying
In this lesson, we've shown how to use basic typing in Python, which is enough for the vast majority of common cases. Recent versions of Python have proven that typing is something the language wants to deal with more and more, and is still evolving. If we'd like to find the most up-to-date typing possibilities, it's best to consult the official documentation.