Skip to Content
Course content

240: The typing Module Overview

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I was looking through a project I wrote about a year ago, and I hit a wall. I found a function called process_user_data, and I genuinely had no idea what it wanted. Did it want a list of user IDs? A single dictionary? A tuple of names? I spent ten minutes digging through the call stack just to figure out the expected input. It was a waste of time, and it's exactly why I want to show you the typing module.

Wait, what am I passing in here?

Let's recreate that frustration. I'll write a quick function that takes a list of IDs and returns a joined string of those IDs. In a basic script, I might just do this:

def summarize_ids(ids):
    return ", ".join(map(str, ids))

Now, if you're using a modern IDE, you'll notice that when you hover over ids, it just says Any. That's a problem. If I accidentally pass a single integer instead of a list, Python won't complain until the code actually hits the map function and crashes. I want my editor to yell at me before I run the code.

I'll try adding a basic hint. I know I want a list, so I'll try this:

def summarize_ids(ids: list):
    return ", ".join(map(str, ids))

That's better, but it's still too vague. A list of what? Strings? Integers? Booleans? If I pass a list of complex objects that can't be converted to strings easily, I'm still in trouble. This is where the typing module comes in. I need to be specific about the contents of the list.

Broadening the horizon with Union

In a real-world app, IDs are messy. Sometimes they come from a database as integers, and sometimes they come from an API as strings. I want my function to handle both. Let's pull in the typing module to handle this complexity.

from typing import List, Union

def summarize_ids(ids: List[Union[int, str]]):
    return ", ".join(map(str, ids))

Break that down for a second. List[Union[int, str]] tells anyone reading this (and the IDE) that "this is a list, and the items inside that list can be either integers or strings." It's a contract. I'm essentially saying, "If you give me anything else, you're breaking the deal."

I'll be honest: in Python 3.10+, you can use the pipe operator int | str instead of Union, but you'll see Union everywhere in existing professional codebases, so you have to know it.

Dealing with the void

Now, let's add a twist. What if the list is empty? I don't want to return an empty string; I want to return None to signal that there was nothing to summarize. I'll adjust the logic:

from typing import List, Union, Optional

def summarize_ids(ids: List[Union[int, str]]):
    if not ids:
        return None
    return ", ".join(map(str, ids))

If I check the return type now, my IDE is going to be confused. It sees that I'm returning a string in one path and None in another. This is where Optional comes in. Optional[str] is just a shorthand for Union[str, None].

def summarize_ids(ids: List[Union[int, str]]) -> Optional[str]:
    if not ids:
        return None
    return ", ".join(map(str, ids))

Now the function is fully documented. I don't need a long docstring explaining the types because the code explains itself.

The runtime realization

Here is the part that trips people up. I'm going to intentionally break my own contract and see what happens:

# This should be a list of ints/strs, but I'll pass a float
print(summarize_ids([1.5, 2.7])) 

If you run this, it works perfectly. Python prints "1.5, 2.7". Wait, what? I just told Python it should be Union[int, str]`, so why didn't it crash?

Because type hints are not enforced at runtime. Python is still a dynamically typed language. The typing module doesn't change how the code executes; it changes how we analyze the code. To actually catch that error, you'd use a tool like mypy. You run mypy script.py in your terminal, and that is where it would scream at you: Argument 1 to "summarize_ids" has incompatible type "List[float]"; expected "List[Union[int, str]]".

That distinction is key. We aren't adding constraints to the language; we're adding a map for the developers.




πŸ“‹ Practical Task

Refactoring the Inventory Validator

You've inherited a piece of code that validates inventory items. The current function is "blind"β€”it has no type hints, making it dangerous to use in a large system. Your task is to use the typing module to explicitly define the expected inputs and outputs.

The Requirements:

  • The function validate_stock takes a dictionary where the keys are item names (strings) and the values are the quantity (integers).
  • The function returns a list of names (strings) of items that are out of stock (quantity is 0).
  • If the input dictionary is empty, the function should return None.
from typing import Dict, List, Optional

def validate_stock(inventory):
    if not inventory:
        return None
    
    out_of_stock = []
    for item, quantity in inventory.items():
        if quantity == 0:
            out_of_stock.append(item)
    return out_of_stock

# Test your hints with these cases:
inv_1 = {"Apples": 10, "Bananas": 0, "Oranges": 0}
inv_2 = {}

print(validate_stock(inv_1)) # Expected: ['Bananas', 'Oranges']
print(validate_stock(inv_2)) # Expected: None

Your Goal: Add the correct type hints to the inventory parameter and the return type of validate_stock using Dict, List, and Optional.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.