Python Basic Type Hinting
When Python first added type hinting to the language in version 3.5, you could only add type hints to functions. However, in Python 3.6, the language added the ability to annotate variables as well. Over the following years, Python has steadily added more features to its type hinting capabilities and slowly refined the typing interface.
You will be studying how to add simple types to variables and functions in this article split across the following sections:
Detecting types
Type hinting variables
Type hinting collections
Type hinting functions
Handling multiple types
Optional types
By the time you finish reading this chapter, you will be able to specify type hints for variables, function parameters, return types, and even apply multiple types at once.
Let’s get started!
Detecting Types
Python includes many different built-in functions. One of these built-in functions is type(), which has the following signature:
type(name, bases, dict, /, **kwargs)Most of the time, you will use type() by passing an object to it and type() will return the type object. In other words, you are using the type() function to introspect the objects in your application and discover what they are. This is especially useful in the Python REPL when you are learning a new package or module.
Get the new Python Typing Book
You can use isinstance() to test the type of an object. In fact, isinstance is the preferred way to check for acceptable types, as it will allow subclasses. Use type in the REPL to see what type an object is, and use isinstance in code when checking for types.
Here are a couple of examples of using type() so that you can see how it works:
>>> name = “Mike”
>>> age = 20
>>> type(name)
<class ‘str’>
>>> type(age)
<class ‘int’>Here you can see that type() is telling you what type name and age are. Now that you know what type they are, you can use isinstance() to test for a specific type.
Here are a couple examples to show you how:
>>> isinstance(name, int)
False
>>> isinstance(name, str)
TrueThe isinstance() function returns a Boolean that tells you if the object you passed in is the type that you are looking for. The first line is asking if name is an instance of int, which it is not, so isinstance() returns False. Next, you check if the object is an instance of str, which it is, so isinstance() returns True in that second case.
Now that you know how to detect actual types from Python objects, you are ready to learn how to add type hints.
Type Hinting Variables
Python 3.6 added variable type annotations when it adopted PEP 526. To add a type hint to a variable, you write the variable name followed by a colon and then the data type. Next, you would assign a value to the variable.
Here are a few examples:
name: str = “Mike”
age: int = 20
is_american: bool = TrueHere, you see three variables that are type-hinted as a string, an integer, and a Boolean. Each variable is also initialized with a value.
As mentioned earlier, Python does NOT enforce the type hint, so you can write the following code without getting complaints from Python:
name: int = “Mike”In this case, you have set the type hint for name as an integer, but you assign a string to the variable. While Python won’t complain about this, your IDE might and if you use a Python type checker, it will point out your error.
Before jumping into type-hinting functions, you will learn about adding type hints to some of Python’s more complex built-in data types.
Type Hinting Collections
Python supports more than simple integers and strings. You also have lists, tuples, dictionaries, and sets. These data types are a little more complex than basic integers or strings.
Before Python 3.9, to type hint collections, you had to do the following:
from typing import Dict, List
list_of_names: List[str] = [”Mike”, “John”, “Peter”]You should be using a version newer than Python 3.9, but the code above is a good example of how to add a variable-length list of strings using the typing module. You may see such code in older code bases.
However, in Python 3.9+, you can use any of Python’s collections directly. You can read all about this change in PEP 585, which mentions many more types than are covered here.
Here’s an example that uses the list keyword instead of typing.List:
list_of_names: list[str] = [”Mike”, “John”, “Peter”]Notice the lowercase “l” here. Also note that the Python typing system assumes that all elements in a container are of the same type.
What about dictionaries? In that case, you would write dict[type_of_key, type_of_value].
Here are a couple of examples:
ages: dict[str, int] = {”Mike”: 5, “John”: 3}
locations: dict[str, str] = {”Mike”: “IA”, “John”: “CA”}Take a moment to study these examples and write your own examples in the REPL.
Then move on to the following subsection, which will tell you about the Python tuple, which is a little different from what you have seen so far.
Annotating Tuples
Tuples are different than lists according to the Python documentation. It’s pretty easy to mess this up, but fortunately, most type checkers are forgiving or at least give you a helpful error message.
For Python lists, you cannot do the following:
list_of_names: list[str, int] = [”Mike”, 10]If you run a type checker against this code, you will get an error that the list type hint only supports a single type argument.
But if you want to do a tuple, you should supply ALL the types for each of the elements in the tuple:
# A tuple with a length of one:
age: tuple[int] = (10,)
# A tuple of two elements, with one int and one str
name_and_age: tuple[str, int] = (”Mike”, 10)
# ERROR - The type annotation indicates only one element!
bad_ages: tuple[int] = (1, 2, 3)As you can see, the first two code examples show how to type hint a tuple of 1 or 2 elements correctly. The third example shows an error condition that will appear when you run a type checker: the type hint says the tuple has length 1, but you assign it a tuple with length 3.
But wait! How do you type hint a tuple that could be of any length? In that case, you can use an ellipsis: ..., like this:
good_ages: tuple[int, ...] = (1, 2, 3)
# alternative type hint
more_ages: tuple = (”python”, “is”, “great”)You can also just skip the type of the tuple’s elements and still specify that it is a variable-length tuple by using that second method. However, that method of type hinting is not very explicit and in general, if you follow Python’s style guide, explicit is always better than implicit.
On the other hand, if you wanted to type hint an empty tuple, you would use this syntax instead:
no_age: tuple[()] = ()Of course, that is pretty silly. Why would you need an empty tuple anyway? If you add anything to it, then the type hint is wrong.
Now that you can successfully type hint collections in Python, you can move on to type hinting functions!
Type Hinting Functions
Type hinting a function or method is very similar to type hinting variables, except that you have some new syntax for specifying the return value. You can read all the nitty-gritty details in PEP 484 if you’d like to get the background story.
Otherwise, you can take a look at the following example:
def my_function(param_one: int, param_two: str) -> return_type:
return return_typeNow, obviously return_type is just a placeholder for the real data type that you would return. Here’s a more realistic example:
def adder(a: int, b: int) -> int:
return a + bIn both cases, you will note that the parameters still follow the same kind of syntax as the variable annotation: variable name, colon, and then the data type. However, after you close the parentheses of the function, you add the arrow (->) followed by the data type and then a colon to signify the end of the function or method definition. The arrow tells Python that what follows is the return data type.
If you want to add a default value in your function along with type hints, you would do what you always did and follow the variable name with an assignment operator:
def adder(a: int = 5, b: int = 2) -> int:
return a + bThe updated example above defaults parameter a with the integer 5 and b with the integer 2.
Handling Multiple Types
Sometimes, a variable or parameter in Python needs to accept multiple data types. So far, you have only learned how to add a type hint for a single data type. How do you tell Python that a variable can accept multiple types?
You can do this trick using a union. Before Python 3.10, you had to use typing.Union. Here is an example:
from typing import Union
my_value: Union[int, str, None] = NoneIn the code above, you tell Python that my_value can be any of the following types:
Integer
String
None
Then you set the default value to None.
Starting in Python 3.10, PEP 604 was adopted which allows you to write union types using the pipe (|) character.
Here’s how to rewrite the code above:
my_value: int | str | None = NoneNow, instead of importing Union from the typing module, you can use the pipe to indicate multiple types.
Optional Types
A related topic to multiple types is what to do when a type is considered optional. In other words, the type could be a string or None.
Before Python 3.10, you would use typing.Optional to do this:
from typing import Optional
my_value: Optional[str] = NoneHowever, due to PEP 604, which you learned about in the previous section, you can use the union operator instead of typing.Optional:
my_value: str | None = NoneOnce again, you no longer need to import anything from the typing module, which makes your code cleaner!
Wrapping Up
Adding basic type hints or annotations to your variables and functions is straightforward. With a little practice, you will be able to do it without any need to consult the documentation.
In this chapter, you learned about type hinting using the following sections:
Detecting types
Type hinting variables
Type hinting collections
Type hinting functions
Handling multiple types
Optional types
There are more complex types than what was shown here. To learn more, you should get my book, Python Typing, now available on Kickstarter.


I joined the kickstarter. Typing is one area that needs clarification. I have written debugging utilities for Python and I would like to make other areas clear. If you are interested, my email is: davidjensenusa@gmail.com