Lesson 2
Advanced Typing in Python
Lesson Introduction

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!

Optional Typing

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.

Python
1from 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.

Union Typing

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.

Python
1from 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.

Tuple Typing

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:

Python
1from 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.

Data Structures Typing: List

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:

Python
1from 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.

Data Structures Typing: Dict

Similarly, we use Dict to specify the types of keys and values in a dictionary.

Python
1from 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.

Callable Typing

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:

Python
1from 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.

Callable Examples

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 a float: Callable[[int, int], float]
  • A function that takes str and int, and returns a bool: 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 optional int: 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 an int): 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.

Lesson Summary

Today we covered six advanced typing features in Python:

  1. Optional - Used when a function might return a value or None.
  2. Union - Used when a parameter or return type can be more than one type.
  3. Tuple - Used to specify functions returning multiple values.
  4. List - Specifies the type of elements in a list.
  5. Dict - Specifies the types of keys and values in a dictionary.
  6. 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!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.