Welcome to this lesson on Advanced Typing in Python! Today, we'll explore how to make our Python code more robust and readable using advanced typing features. These features help make code clearer, catch errors early, and improve overall quality.
Ready to dive in? Let's go!
Sometimes, a function might return a value, or it might return nothing (None
). In such cases, we use Optional
from the typing
module. This makes our intentions clear.
Here’s a function that tries to find a string in a list. If it finds the string, it returns it; if not, it returns None
.
Python1from typing import Optional 2 3def find_string(strings: list[str], target: str) -> Optional[str]: 4 for string in strings: 5 if string == target: 6 return string 7 return None 8 9if __name__ == "__main__": 10 my_strings = ["apple", "banana", "cherry"] 11 target = "banana" 12 result = find_string(my_strings, target) 13 14 if result: 15 print("Found:", result) # Found: banana 16 else: 17 print("String not found") # String not found
The return type Optional[str]
shows the function could return a str
or None
.
Sometimes, a value can be more than one type. For example, a function parameter might be an int
or a float
. We use Union
to handle this.
Here's a function that takes a number, which can be either an int
or a float
, processes it, and returns a float
.
Python1from typing import Union 2 3def process_number(value: Union[int, float]) -> float: 4 return value * 2.5 5 6if __name__ == "__main__": 7 num = 10 8 print("Processed Value:", process_number(num)) # Processed Value: 25.0 9 10 num = 10.5 11 print("Processed Value:", process_number(num)) # Processed Value: 26.25
Using Union[int, float]
makes it clear the function accepts either type.
Another useful typing feature is Tuple
. Sometimes, a function needs to return multiple values as a single compound value. This can be done using a Tuple
from the typing
module.
Here's an example where a function returns a pair of values, a str
and an int
:
Python1from typing import Tuple 2import random 3 4def get_random_name_and_age() -> Tuple[str, int]: 5 name = random.choice(["Alice", "Bob", "Ann", "John"]) 6 age = random.randint(18, 40) 7 return name, age 8 9if __name__ == "__main__": 10 name, age = get_random_name_and_age() 11 print(f"Name: {name}, Age: {age}") # Example output: Name: Alice, Age: 30
The return type Tuple[str, int]
makes it clear that the function returns a tuple containing a str
and an int
.
We often use lists and dictionaries to store collections of data. The typing
module lets us specify the types of elements in these collections. Let's see how to use List
.
We specify that a list contains elements of a particular type using List
. Here's how:
Python1from typing import List 2 3my_strings: List[str] = ["apple", "banana", "cherry"] 4 5if (__name__ == "__main__"): 6 for fruit in my_strings: 7 print(fruit)
The output will be:
1apple 2banana 3cherry
This tells us my_strings
is a list of str
.
Similarly, we use Dict
to specify the types of keys and values in a dictionary.
Python1from typing import Dict 2 3# Example dictionary of fruits and their prices 4fruit_prices: Dict[str, float] = { 5 "apple": 1.2, 6 "banana": 0.5, 7 "cherry": 2.5 8} 9 10if __name__ == "__main__": 11 for fruit, price in fruit_prices.items(): 12 print(f"The price of {fruit} is ${price}")
This shows fruit_prices
is a dictionary with str
keys and float
values.
The output will be:
1The price of apple is $1.2 2The price of banana is $0.5 3The price of cherry is $2.5
You might wonder why we use List
and Dict
from the typing
module when we can just use list
and dict
directly, such as list[int]
or dict[str, int]
. The typing
module's List
and Dict
were introduced to provide a more consistent and explicit way of specifying types, especially before Python 3.9, where type hinting for built-in generic collections was not available. While it's essential to follow the best practices for the version of Python you're targeting, it’s generally recommended to use the built-in list
and dict
for typing when using Python 3.9 or later. For older codebases or when maintaining compatibility with older Python versions, stick with List
and Dict
from the typing
module.
The Callable
type in Python's typing
module is used to indicate that a particular argument or return type is a function or another object that can be called with ()
. The syntax for Callable
is Callable[[<arg types>], <return type>]
, where <arg types>
is a list of argument types the function accepts, and <return type>
is the type it returns.
Here’s an example where a function accepts another function as an argument and calls it:
Python1from typing import Callable 2 3def greet(name: str) -> str: 4 return f"Hello, {name}!" 5 6def process_and_greet(names: list[str], greeter: Callable[[str], str]) -> None: 7 greetings = map(greeter, names) 8 for g in greetings: 9 print(g) 10 11if __name__ == "__main__": 12 names_list = ["Alice", "Bob", "Charlie"] 13 process_and_greet(names_list, greet)
In this example, Callable[[str], str]
indicates that the greeter
argument is a function that takes a single str
argument and returns a str
. This makes the function signature more understandable and ensures type safety.
Using Callable
helps clarify that greeter
is expected to be callable, enhancing code readability and preventing type-related errors.
Let's look at a list of examples showing how to annotate various callables:
- A function that takes no arguments and returns
str
:Callable[[], str]
- A function that takes two
int
arguments and returns afloat
:Callable[[int, int], float]
- A function that takes
str
andint
, and returns abool
:Callable[[str, int], bool]
- A function that takes
int
and doesn't return anything:Callable[int, None]
- A function that takes
int
and returns an optionalint
:Callable[int, Optional[int]]
- A function that takes in another function and returns
int
:Callable[[Callable[[int], bool]], int]
- A function that no arguments and returns two values (e.g., a
str
and anint
):Callable[[], Tuple[str, int]]
As you can see, there are a lot of various situations. The key point is that describing a complex type annotation involves combining simple typings together into one nested complex type.
Today we covered six advanced typing features in Python:
Optional
- Used when a function might return a value orNone
.Union
- Used when a parameter or return type can be more than one type.Tuple
- Used to specify functions returning multiple values.List
- Specifies the type of elements in a list.Dict
- Specifies the types of keys and values in a dictionary.Callable
- Used when a parameter or return type is an object that can be called with()
Understanding and using these types makes our code clearer and more robust.
Now it's time for hands-on practice! You'll write functions and see how these advanced typing features work in action. This will solidify your understanding and give you practical experience with these concepts. Happy coding!