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
308: Generics with TypeVar
Imagine you're designing a high-end shipping crate. This crate is engineered to protect whatever is inside it, whether it's a delicate glass vase, a heavy engine part, or a stack of rare books. The crate doesn't need to know the specific properties of a vase to keep it safe; it just needs to be a "container for something." Crucially, if you put a vase in, you expect to get a vase back out—not a random object that might or might not be a vase.
In Python, we often run into this exact scenario. You want to write a function that works with any data type, but you want the type checker to remember which specific type was used. If you just use Any, you're telling Python, "I don't care what this is," and you lose all the helpful autocomplete and safety checks. That's where TypeVar comes in. It's our way of saying, "I don't know what the type is yet, but whatever it is, it stays consistent throughout this function."
The frustration of losing type identity
Let's look at a common pattern: a function that returns the first item of a list. If I write it using Any, I'm throwing away valuable information.
from typing import Any, List
def get_first(items: List[Any]) -> Any:
return items[0]
names = ["Alice", "Bob", "Charlie"]
first_name = get_first(names)
# The IDE thinks first_name is 'Any'.
# I lose autocomplete for string methods like .upper()
I've seen a lot of developers just settle for Any here because it's easy. But as your codebase grows, that "Any" becomes a black hole. You'll find yourself constantly casting types or, worse, hitting AttributeError at runtime because you forgot what the function actually returned.
Creating a type placeholder
To fix this, we use TypeVar. Think of it as a variable for a type. By convention, we usually call it T, but you can call it whatever you like.
from typing import TypeVar, List
# We define T as a placeholder for "some type"
T = TypeVar('T')
def get_first(items: List[T]) -> T:
return items[0]
names = ["Alice", "Bob", "Charlie"]
first_name = get_first(names)
# Now, Python knows that because 'names' is a List[str],
# T must be 'str'. Therefore, first_name is a 'str'.
Notice what happened there. I didn't tell Python that T is a string. I told Python: "Whatever type the list contains, the return value will be that exact same type." It's a relationship, not a definition.
Putting guardrails on your generics
Sometimes, you don't want any type. You might want a function that works with any type, as long as it's a number. If you try to use a string in a math-heavy generic function, your code will crash. We can handle this by "constraining" the TypeVar.
You do this by passing the allowed types as arguments to the TypeVar constructor. Here's how I usually handle functions that need to support both integers and floats but nothing else:
from typing import TypeVar
# T can only be an int or a float
T = TypeVar('T', int, float)
def add_bonus(value: T, bonus: T) -> T:
return value + bonus
print(add_bonus(10, 5)) # Works! (int)
print(add_bonus(10.5, 2.5)) # Works! (float)
# add_bonus("10", "5") # Static type checker will scream at you here
I love this approach because it's explicit. You're telling other developers (and your future self) exactly what the boundaries are. You get the flexibility of generics without the "wild west" feel of Any.
📋 Practical Task
Build a Generic Response Wrapper
In professional API development, we often wrap our data in a consistent "Response" object that includes metadata (like a success flag) and the actual payload. The payload could be a User object, a list of Products, or a simple Error string.
Your task is to create a generic class called ApiResponse using TypeVar. It should:
- Define a
TypeVarcalledT. - Have an
__init__method that acceptsdataof typeTandstatusas a string. - Have a method called
get_datathat returns the data, ensuring the return type is mapped toT.
Test your class by creating one instance with a list of strings and another with a dictionary. Verify (using a type checker or by observing IDE behavior) that the return type of get_data() changes based on what you passed into the constructor.
There are no comments for now.