1.5 Type Hinting Variables
Working with Variables
Type hinting is a way to indicate the expected data type of a variable in Python. Remember, Python is dynamically typed, meaning that you don’t have to declare the type of a variable when you create it. In many ways this makes it easy, but in other ways, it can introduce bugs that are hard to track down. Type hinting helps mitigate this by allowing you to specify the expected type of a variable, function parameter, or return value. This can make your code more readable and maintainable, as it provides additional context about how the variable is intended to be used. Moreover, type hinting does not impact the runtime behavior of your code, but it can be used by tools like linters and IDEs to provide better code analysis and suggestions.
Type hinting acts as a way to catch errors before they happen. Your IDE will flag locations where you are using a variable in a way that does not match the type hint. For instance, if you have a variable that is type hinted as an integer, but you try to assign a string to it, your IDE will flag this as an error. This can help you catch bugs early in the development process. In this way, type hinting will catch errors before they happen, rather than after the fact. It is a way to proactively debug your code and ensure that you are using variables correctly.
Nevertheless, type hinting is always optional and can be used in a gradual manner. You can start by adding type hints to your function signatures and then expand to variable annotations as needed.
Basic Type Hints
To add type hints to a variable, you can use the : syntax followed by the type. The basic data types (str, int, float, bool) are built in and do not need to be imported. Here is an example of how to use type hints with variables:
x: int = 5
y: float = 3.14
name: str = "Alice"
is_active: bool = TrueIn this example, we have four variables: x, y, name, and is_active. Each variable is type hinted with its expected data type. The variable x is expected to be an integer, y a float, name a string, and is_active a boolean. This provides clarity on what type of data each variable should hold.
Type Hints for Complex Data Types
Python also allows you to use type hints for more complex data types, such as lists, dictionaries, and tuples. But you have to import these (List, Dict, Tuple, Set) from the typing module. You can specify the type of the elements in these collections with brackets that include the type of the data existing in the collection. For instance, if you have a list of integers, you can type hint it as follows:
from typing import List
numbers: List[int] = [1, 2, 3, 4, 5]You can also use type hints for dictionaries, where you specify the types of both the keys and values. We can accomplish this with the same bracket syntax as above, but with a comma separating the key and value types (Dict[key_type, value_type]). For example, if you have a dictionary where the keys are strings and the values are integers, you can type hint it like this:
from typing import Dict
user_info: Dict[str, int] = {"Alice": 25, "Bob": 30}Any Types
Sometimes, you may not know the exact type of a variable or you want to allow multiple types. In this case, you can use the Any type from the typing module. The Any type allows a variable to hold any type of value, effectively disabling type checking for that variable. Here is an example:
from typing import Any
data: Any = "This can be anything"Optional Types
If you want to indicate that a variable can be of a certain type or None, you can use the Optional type from the typing module. This is useful when a variable may not always have a value. For example, if you have a variable that can either be an integer or None, you can type hint it like this:
from typing import Optional
value: Optional[int] = NoneOptional values are important when we are dealing with functions that may not always return a value or when we want to indicate that a variable may not have been set yet. This allows us to handle cases where a variable may be None without causing runtime errors. Moreover, by setting the typing of such variables, it will force you to handle the None case in your code, which can help prevent bugs.
Union Types
Sometimes, you may want a variable to possible hold values of different types. In this case, you can use the Union type from the typing module. Union allows you to specify multiple possible types for a variable. For example, if you want a variable to be either an integer or a string, you can do the following:
from typing import Union
value: Union[int, str] = "Hello"
value = 42In this example, the variable value can hold either an integer or a string. The IDE will not throw an error if you assign a value of either type to it. But because of the type hint, it will flag an error if you try to assign a value of a different type, such as a list or a dictionary.
Unions become particularly useful when we are working with collections. For instance, if we are creating a dictionary for a person’s profile where we want to store their name, age, and height, we can use a union type to indicate that the age can be either an integer or a string (in case we want to allow for non-numeric ages, such as “unknown”):
from typing import Dict, Union
profile: Dict[str, Union[int, str]] = {
"name": "Alice",
"age": 30,
"height": 1.65
}The TypedDict
If you want to create a dictionary with specific keys and types for each key, you can use TypedDict from the typing module. TypedDict provides a more granular level of control of the types in a dictionary. It allows you to define a dictionary with a fixed set of keys, each with its own type. Unlike other types, we must declare the object (technically creating a new class that inherits from TypedDict, but we will discuss this more in chapter 6). In other words, you do not type out a TypedDict inline as you would the other types. Here is an example:
from typing import TypedDict
class UserTypes(TypedDict): # creating the names of the keys and their types
name: str
age: int
email: str
user: UserTypes = {
"name": "Alice",
"age": 30,
"email": "alice@example.com"
}Throughout the code, the TypedDict allows for more explicit type checking and can help catch errors early in the development process. By defining the structure of the dictionary upfront, you can ensure that all required fields are present and have the correct types. This can be especially useful in larger codebases where data structures can become complex and difficult to manage.