Dynamic typing and type()
Overview
Dynamic typing means variable names are bound to objects at runtime, rather than being locked to a specific data type at compile time. Think of variables as sticky notes with names written on them that can be peeled off an `int` and slapped onto a `str`. The `type()` function acts as a barcode scanner that reveals the actual object's underlying nature. user_id = 42 print(type(user_id)) # <class 'int'> user_id = "admin" print(type(user_id)) # <class 'str'> A sharp edge arises when mixing dynamic reassignment with mutable objects. Reassigning a variable moves its reference to a completely new object, but calling methods on the object mutates it in place for all references. x = [1, 2] y = x x = "new type" # Reassignment: y is unaffected x = y x.append(3) # Mutation: y becomes [1, 2, 3] Because the underlying type dictates whether an object can be mutated in place, this trap is covered fully in `mutable-vs-immutable`.
It enables rapid prototyping and highly flexible code since you do not need to declare types upfront. However, this flexibility requires discipline, as a variable unexpectedly pointing to a different type can crash your application deep in the call stack.
Where used: Data validation pipelines, JSON payload parsing, Duck-typing interfaces
Why learn this
- Debugging type errors in JSON payloads where an API expects an `int` but receives a numeric `str`
- Writing type-safe Pydantic models by understanding how Python's runtime handles dynamic reassignment
Code walkthrough
api_response = 404
print('Value:', api_response, '| type:', type(api_response).__name__)
api_response = {'error': 'Not Found'}
print('Value:', api_response, '| type:', type(api_response).__name__)
Focus: Same name, different type: `api_response` goes from `int` to `dict`, and `type().__name__` follows the object, not the name.
Common mistakes
- Assuming variables have types: Developers from strictly typed languages often think the variable itself holds the type, leading to confusion when `user_id = 123` later becomes `user_id = '123_abc'`. Fix: Remember that types live on the object in memory, not on the variable name.
- Misusing type() for inheritance checks: Checking types with `type(obj) == dict` fails if the object is a subclass of `dict`, like an `OrderedDict`. Fix: Use `isinstance(obj, dict)` instead when validating object types to safely account for inheritance.
Glossary
- compile time
- The phase when code is converted into a format the computer can run, before the program actually starts executing. Languages like Java check variable types at compile time, but Python checks them later, at runtime. Example: `javac MyProgram.java`
- call stack
- A behind-the-scenes record that keeps track of the active functions your program is currently running. Example: `traceback.print_stack()`
Recall questions
- What built-in function reveals the underlying type of an object in Python?
- In Python, are types bound to the variable name or the object itself?
Understanding checks
What will be printed when this code is executed?
str
In Python, types are bound to the object, not the variable name. The variable `x` is simply reassigned to a string object, so `type()` returns the string type.
A developer claims that `x = 10` permanently restricts `x` to holding integers. Why is this incorrect in Python?
Variables are merely references to objects in memory. They do not have fixed types.
This is the essence of dynamic typing: the type information lives on the object, not on the variable name.
What will be printed when this code is executed?
[1, 2, 3]
Calling `items.append(3)` mutates the shared `list` object, so `backup` sees the change. Reassigning `items = "empty"` points the `items` label to a new `str` object, but leaves `backup` pointing to the mutated `list`.
Practice tasks
Runtime Type Logger
Modify the `log_payload_type(payload)` function so it returns a formatted string like `'Processed payload <value> of type <type_name>'`. Use `type(payload).__name__` to get the type's name.
Challenge
API Response Normalizer
Build a webhook receiver helper `normalize_webhook_data(data)` that inspects the dynamic type of `data`. If it is a `str`, return it converted to `int` (assume a valid numeric string). If it is already an `int`, return it as-is. If it is a `float`, return it cast to `int`. Otherwise return `0`. Use `isinstance()` here to practice safe runtime type checking.
Continue learning
Previous: Primitive types: int, float, bool, str
Next: Type conversion: implicit vs explicit casting