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
315: Runtime Type Checking vs Static Checking
Python is a dynamically typed language, which is a fancy way of saying it doesn't care what you put in a variable until the exact moment it tries to do something with it. In small scripts, that's a superpower. In a production codebase with ten other engineers, it's a recipe for a 3:00 AM wake-up call because someone passed a string into a math function.
To wrap our heads around the difference between static and runtime checking, let's build a simple PaymentProcessor. We want a function that takes a payment amount and a currency code and returns a formatted string.
Starting with a fragile payment function
If I just write this the "classic" Python way, it looks like this:
def process_payment(amount, currency):
return f"Processing {amount} {currency}..."
# This works
print(process_payment(100.0, "USD"))
# This also "works" but is logically broken
print(process_payment("one hundred", "USD"))
The problem here is that Python doesn't blink. It just concatenates the string "one hundred" into the result. If this were actually hitting a banking API, the API would probably throw a 400 error, or worse, the system would crash deep inside a library where the stack trace is useless.
Adding hints for the editor
To fix this, I'll add type hints. This is where static checking comes in. I'm telling Python—and more importantly, telling my IDE and tools like Mypy—what I expect the types to be.
def process_payment(amount: float, currency: str) -> str:
return f"Processing {amount} {currency}..."
Now, if I try to pass a string into amount in VS Code or PyCharm, I'll see a yellow squiggle. This is great because it catches the bug while I'm typing, before I even hit "Run".
Realizing hints aren't guards
Here is where I often trip up when I'm moving too fast. I just added those type hints, so I assume the function is now "safe." I'll write a quick test case passing in a string, thinking the type hint will stop it:
# I'm thinking: "The type hint says float, so this will fail early!"
print(process_payment("100", "USD"))
I run the code... and it works perfectly. Wait, what? That's the crucial realization: Python type hints are ignored at runtime. They are purely documentation for the developer and for static analysis tools. If you don't run a tool like mypy over your code, those hints are essentially just comments.
Enforcing types at the gate
If I'm writing a library that other people will use, I can't trust them to run mypy. I need runtime checking. This means I have to explicitly check the types while the program is actually executing.
I'll modify the function to guard the input using isinstance(). This is a bit more verbose, but it's the only way to guarantee the type is correct at the moment of execution:
def process_payment(amount: float, currency: str) -> str:
# Runtime check: This actually stops the program if the type is wrong
if not isinstance(amount, (int, float)):
raise TypeError(f"Expected amount to be a number, got {type(amount).__name__}")
if not isinstance(currency, str):
raise TypeError(f"Expected currency to be a string, got {type(currency).__name__}")
return f"Processing {amount} {currency}..."
# Now this will actually raise a TypeError and stop the program
print(process_payment("100", "USD"))
Now we have the best of both worlds. The static hints help me write the code faster and catch errors in my IDE, and the runtime checks ensure that if some weird data sneaks in from an external API or a user input, the program crashes immediately with a clear error message rather than corrupting data downstream.
📋 Practical Task
Build a Runtime-Validated User Profile Updater
You are building a function called update_user_profile that takes three arguments: user_id (int), username (str), and age (int).
Your task is to implement both layers of checking:
- Add static type hints to the function signature for all three parameters and the return type (which should be
bool). - Implement runtime checks using
isinstance()for all three parameters. If any parameter is the wrong type, the function should raise aTypeErrorwith a descriptive message. - If all types are correct, return
True.
Test your function by passing a string into the age parameter and ensuring it raises the TypeError instead of returning True.
There are no comments for now.