Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
240: The typing Module Overview
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_stocktakes 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.
There are no comments for now.