Previous | Tutorial index | Next
Use different types of data and data models correctly.
1.1 What is a Data Type and Why Does it Matter?
In programming, a data type is a classification that tells Python what kind of value a variable holds and—most importantly—what operations you can perform on it and how it is stored in memory.
Think of it like real-world containers:
Dynamic vs. Static Typing: Python is dynamically typed. This means:
x = 10 makes it an integer, later x = "Hello" makes it a string).int x = 10; and cannot change it to a string later.1.2 Numeric Types: Integers and Floats
int (Integers): Whole numbers, positive or negative, without a decimal point.
42, -15, 0, 1234567890123456789population = 8_000_000_000 (Python ignores the underscores).float (Floating-Point Numbers): Real numbers with a decimal point. They are stored using the IEEE 754 standard (double-precision, 64-bit).
3.14, -0.5, 2.0, 1.5e3 (scientific notation, equals 1500.0).print(0.1 + 0.2) # Outputs: 0.30000000000000004 (Not 0.3!)
decimal module—but that's beyond this unit.complex (Complex Numbers): Python also has a complex type (e.g., 3+4j). This is rarely used in introductory programming, so we'll skip it here.
1.3 Text Type: Strings (str)
A str is an ordered sequence of characters (Unicode text). Since Python 3, all strings are Unicode by default, meaning you can use emojis and foreign characters.
Ways to create strings:
'Hello'"World" (useful if you have a single quote inside: "It's nice")'''Multi-line''' or """Multi-line""" – used for docstrings and preserving line breaks.Escape Sequences:
\n = Newline\t = Tab\\ = Backslash\" = Double quote inside a double-quoted string.print("Hello\nWorld") prints on two lines.String Operations (Preview):
+): "Hello" + " " + "World" → "Hello World"*): "Ha" * 3 → "HaHaHa"[]): "Python"[0] → "P" (we'll cover indexing in detail later).1.4 Boolean Type (bool)
True and False.True behaves like 1False behaves like 0True + 5 → 6 (Valid, but a terrible practice—don't do this in real code).1.5 Sequence Types: Lists and Tuples
Both list and tuple are ordered collections. The critical difference lies in mutability.
list (Mutable Sequence):
[].[1, "hello", 3.14].my_list = [10, 20, 30]; my_list[0] = 99 → [99, 20, 30].my_list.append(40).tuple (Immutable Sequence):
().my_tuple = (10, 20, 30); my_tuple[0] = 99 → TypeError (immutable).single = (5,) – without the comma, (5) is just an integer 5 (parentheses for grouping).1.6 Mapping Type: Dictionaries (dict)
dict is an unordered (insertion-ordered in Python 3.7+) collection of key-value pairs.{} and colons : separating keys and values.
person = {"name": "Alice", "age": 30, "city": "NYC"}person["name"] → "Alice".{ "key1": 1, 10: "value", (1,2): "point" }{ [1,2]: "error" } (TypeError: unhashable type: 'list')1.7 Data Models and Mutability (Deep Dive)
This is the foundational concept behind "data models" in Python.
Immutable Types (Value Types): When you "change" an immutable variable, Python actually creates a new object in memory and points your variable to it. The old object is garbage-collected.
int, float, bool, str, tuple.x = 10
print(id(x)) # Memory address, e.g., 140736...
x = x + 1
print(id(x)) # Address changed! New integer object created.
Mutable Types (Reference Types): When you modify a mutable object, Python changes the existing object in place. The variable still points to the same memory address.
list, dict, set.my_list = [1, 2, 3]
print(id(my_list)) # e.g., 432345...
my_list.append(4)
print(id(my_list)) # SAME address! The list object was modified in-place.
Why does this matter?
a = [1, 2]
b = a # 'b' points to the SAME list as 'a'
b.append(3)
print(a) # [1, 2, 3] – Uh oh, a changed too!
1.8 Checking Types
Use the built-in type() function.
print(type(10)) # <class 'int'>
print(type(3.14)) # <class 'float'>
print(type("Hello")) # <class 'str'>
print(type(True)) # <class 'bool'>
print(type([1, 2])) # <class 'list'>
print(type((1, 2))) # <class 'tuple'>
print(type({"a": 1})) # <class 'dict'>
To check if a variable is a specific type, use isinstance():
print(isinstance(10, int)) # True
# --- Numeric Types ---
age = 30 # int
height = 5.9 # float
population = 8_000_000_000 # int with underscore readability
print(0.1 + 0.2) # Floating point inaccuracy: 0.30000000000000004
# --- Text Type ---
name = "Alice" # str
greeting = 'Hello' # str (single quotes)
multi_line = """This is
a multi-line string""" # Triple quotes
print("Hello\nWorld") # Escape sequence
# --- Boolean ---
is_adult = True # bool
print(True + 5) # 6 (legal but don't do it)
# --- Lists (Mutable) ---
fruits = ["apple", "banana"]
fruits[0] = "orange" # Changing an element (valid)
fruits.append("grape") # Adding an element
print(fruits) # ['orange', 'banana', 'grape']
# --- Tuples (Immutable) ---
coordinates = (10, 20) # tuple
# coordinates[0] = 5 # TypeError! Cannot change.
single_tuple = (5,) # Correct singleton tuple
not_a_tuple = (5) # Just an integer 5
# --- Dictionaries (Mutable Mappings) ---
student = {"name": "Bob", "age": 22}
print(student["name"]) # Accessing value by key
student["age"] = 23 # Updating a value (valid)
student["major"] = "CS" # Adding a new key-value pair (valid)
print(student)
# --- Mutability Demonstration with id() ---
print("\n--- Mutability Check ---")
x = 10
print(f"x id (before): {id(x)}")
x += 1
print(f"x id (after): {id(x)}") # New address (immutable)
my_list = [1, 2]
print(f"list id (before): {id(my_list)}")
my_list.append(3)
print(f"list id (after): {id(my_list)}") # Same address (mutable)
# --- Alias Danger ---
a = [1, 2, 3]
b = a # b points to the same list
b[0] = 99
print(f"a: {a}") # [99, 2, 3] - a changed too!
Question 1: What data type does Python assign to the value 3.0?
a) int
b) float
c) str
d) bool
Question 2: What is the difference between [1, 2, 3] and (1, 2, 3)?
Question 3: Why does 0.1 + 0.2 not equal 0.3 in Python?
Question 4: Can a dictionary have a list as a key? Why or why not?
Question 5: What is the output of type(True)?
a) <class 'bool'>
b) <class 'int'>
c) <class 'str'>
d) True
Question 6: (Trick question) What is the type of the variable x after x = (5)?
a) tuple
b) int
Question 7: You have my_var = "Python". Is it possible to change the first character to "J" (e.g., my_var[0] = "J")? What happens if you try?
Question 8: What is the output of the following code?
data = {"a": 1, "b": 2}
data["c"] = 3
print(len(data))
Exercise 1: Type Detective
Create a Python script that creates at least 8 different variables, each representing one of the core data types (int, float, str, bool, list, tuple, dict). For each variable, print the variable itself and its type using type(). Ensure you test a float that looks like an integer (e.g., 5.0).
print(a, type(a)) print(b, type(b)) print(c, type(c)) print(d, type(d)) print(e, type(e)) print(f, type(f)) print(g, type(g)) print(h, type(h))
</details>
**Exercise 2: Mutability Exploration**
Write a script that:
1. Creates a list `colors = ["red", "green", "blue"]`.
2. Prints the memory address of the list using `id(colors)`.
3. Replaces the second element with "yellow".
4. Prints the list and the memory address again.
5. Creates a tuple `numbers = (10, 20, 30)`.
6. Try to change the first element to `99` (comment it out after seeing the error).
7. Explain in comments what you observed about mutability.
<details><summary>Sample Solution</summary>
```python
colors = ["red", "green", "blue"]
print(id(colors)) # e.g., 140123456
colors[1] = "yellow"
print(colors) # ['red', 'yellow', 'blue']
print(id(colors)) # same id – list modified in‑place
numbers = (10, 20, 30)
# numbers[0] = 99 # TypeError: 'tuple' object does not support item assignment
Exercise 3: The Dictionary Mix
Create a dictionary called inventory with keys: "item", "price", "quantity", and "in_stock". Assign appropriate values using different data types. Then:
"category" with a string value.Exercise 4: Tuple Trap
Try to create a tuple with a single element, 3. Print its type. Now try to create a tuple with a single element 3 correctly (with a comma). Print its type. Compare the difference in output.
# Attempt 1: Without a comma - this is NOT a tuple!
not_a_tuple = (3)
print(f"Value: {not_a_tuple}")
print(f"Type: {type(not_a_tuple)}") # <class 'int'>
# Attempt 2: With a comma - this IS a tuple!
correct_tuple = (3,)
print(f"Value: {correct_tuple}")
print(f"Type: {type(correct_tuple)}") # <class 'tuple'>
# Comparison
print("\nComparison:")
print(f"Value of first: {not_a_tuple} (type: {type(not_a_tuple).__name__})")
print(f"Value of second: {correct_tuple} (type: {type(correct_tuple).__name__})")
print(f"Are they equal? {not_a_tuple == correct_tuple}") # False
print(f"Are they the same type? {type(not_a_tuple) == type(correct_tuple)}") # False
Explanation:
(3) is interpreted by Python as an integer 3 with parentheses for grouping, not as a tuple.(3,) creates a proper tuple with a single element. The trailing comma tells Python to treat it as a tuple.Exercise 5: Alias Demonstration Write code to demonstrate the "alias danger":
original = [10, 20, 30].copy = original.copy to 99.original. Observe that original changed too. Then, fix this by creating a true copy using copy = original[:] (slicing) and repeat the experiment.# --- Alias Danger Demonstration ---
print("=== ALIAS DANGER (Shared Reference) ===")
# Create the original list
original = [10, 20, 30]
print(f"Original (before): {original}")
print(f"Original ID: {id(original)}")
# Create an alias (copy points to the SAME list)
copy = original
print(f"Copy (before): {copy}")
print(f"Copy ID: {id(copy)}")
print(f"Same object? {original is copy}") # True
# Modify the copy
copy[1] = 99
print("\n--- After modifying copy[1] = 99 ---")
print(f"Original (after): {original}") # Changed!
print(f"Copy (after): {copy}") # Changed!
print("\n" + "=" * 50)
# --- True Copy Demonstration ---
print("=== TRUE COPY (Independent Object) ===")
# Start fresh
original = [10, 20, 30]
print(f"Original (before): {original}")
print(f"Original ID: {id(original)}")
# Create a true independent copy using slicing
true_copy = original[:]
print(f"True Copy (before): {true_copy}")
print(f"True Copy ID: {id(true_copy)}")
print(f"Same object? {original is true_copy}") # False
# Modify the true copy
true_copy[1] = 99
print("\n--- After modifying true_copy[1] = 99 ---")
print(f"Original (after): {original}") # Unchanged!
print(f"True Copy (after): {true_copy}") # Only this changed
print("\n" + "=" * 50)
# --- Additional Methods for Creating Copies ---
print("=== Other Copy Methods ===")
# Method 1: Using the copy() method (Python 3.3+)
original = [10, 20, 30]
copy_method = original.copy()
copy_method[1] = 99
print(f"Original: {original}")
print(f"Copy using .copy(): {copy_method}")
# Method 2: Using the list() constructor
original = [10, 20, 30]
list_constructor = list(original)
list_constructor[1] = 99
print(f"Original: {original}")
print(f"Copy using list(): {list_constructor}")
Key Takeaways:
= (e.g., copy = original), both variables reference the same list object in memory.true_copy = original[:] (slicing)true_copy = original.copy() (copy method)true_copy = list(original) (list constructor)[:] creates a shallow copy, which is sufficient for simple lists like this one.Why This Matters:
Question 1 (Research & Explain):
Research the decimal module in Python. Write a small program that calculates 0.1 + 0.2 using float and again using decimal.Decimal('0.1') + decimal.Decimal('0.2'). Print both results. Explain in your own words why the decimal module is important for financial applications and what caused the float inaccuracy.
Question 2 (Conceptual): In your own words, explain the difference between mutable and immutable data types. Provide two examples of each and explain what happens in memory when you "change" an immutable value.
Question 3 (Code Analysis): What is the output of the following program? Justify your answer with a memory diagram (or a verbal description) of how the variables reference objects.
x = [1, 2]
y = x
x = x + [3, 4]
print(x)
print(y)
Hint: Is x + [3,4] creating a new list or modifying the existing one?
Output:
[1, 2, 3, 4]
[1, 2]
Explanation with Memory Diagram:
The key to understanding this code is recognizing that x + [3, 4] creates a new list rather than modifying the existing one.
Step-by-step memory diagram:
Step 1: x = [1, 2]
Memory:
x ──→ [1, 2] (Object A at address 1000)
Step 2: y = x
Memory:
x ──→ [1, 2] (Object A at address 1000)
y ──→ [1, 2] (Same object! y points to Object A)
Step 3: x = x + [3, 4]
Right side: x + [3, 4] reads Object A and creates a NEW list
Memory before assignment:
Object A: [1, 2] at address 1000
Temporary object created: [1, 2, 3, 4] at address 2000
After assignment:
x ──→ [1, 2, 3, 4] (Object B at address 2000 - NEW)
y ──→ [1, 2] (Object A at address 1000 - unchanged!)
Step 4: print(x) → [1, 2, 3, 4]
Step 5: print(y) → [1, 2]
Key Insight:
x + [3, 4] is not x += [3, 4].x + [3, 4] creates a new list object.y still points to the original list [1, 2].x += [3, 4], it would have modified the original list in-place, and both x and y would show [1, 2, 3, 4].Takeaway: When working with mutable objects, always be aware of whether an operation creates a new object or modifies the existing one. This distinction is crucial for understanding variable references.
Question 4 (Real-World Scenario): You are building a program that stores a student's information (name, ID, and a list of their course grades). Should the list of grades be mutable or immutable? Explain your reasoning. If you accidentally used a tuple for the grades, would that be an issue?
Recommendation: Use a mutable list for the grades.
Reasoning:
Grades change frequently – Students earn new grades throughout the semester. A teacher may need to add new grades, update a grade after a correction, or even remove a grade if a student drops a course. A mutable list allows all these operations easily using methods like .append(), .insert(), .pop(), or direct assignment.
Ease of modification – With a list, you can:
student["grades"].append(92) # Add a new grade
student["grades"][0] = 85 # Update a specific grade
student["grades"].remove(75) # Remove a grade
Performance – Lists are optimized for these types of operations. Creating a new tuple every time a grade changes (which is what would happen with immutable types) is inefficient and wasteful.
If you accidentally used a tuple for the grades:
Yes, this would be a significant issue because:
You could not add a new grade without creating an entirely new tuple.
You would need to use cumbersome workarounds:
# With tuple (immutable) - cumbersome!
grades_tuple = (85, 90, 88)
# To add a new grade, you must create a new tuple
new_grades_tuple = grades_tuple + (92,)
# To update a grade, you must slice and rebuild
updated_tuple = grades_tuple[:1] + (95,) + grades_tuple[2:]
Exception: If the program were designed to store only finalized grades that never change (e.g., a transcript archive), then an immutable tuple would be appropriate as it would prevent accidental modifications and convey intent clearly.
Best Practice: Choose mutability based on how the data will be used. If data needs to change frequently, use a mutable type. If data should be protected from changes, use an immutable type.
Question 5 (Practical Build): Write a complete program that:
employee containing: "first_name", "last_name", "base_salary" (float), "is_manager" (bool), and "projects" (a list of project names)."projects" list."base_salary" by 10%.# Complete Employee Information Program
# Demonstrating mutability of different data structures
# --- Step 1: Define the employee dictionary ---
# Dictionaries are MUTABLE - we can modify, add, or remove key-value pairs
employee = {
"first_name": "Sarah", # str - IMMUTABLE (strings cannot be changed)
"last_name": "Johnson", # str - IMMUTABLE
"base_salary": 75000.0, # float - IMMUTABLE (floats create new objects)
"is_manager": True, # bool - IMMUTABLE (booleans are immutable)
"projects": [ # list - MUTABLE (we can modify this list)
"Project Phoenix",
"Data Migration 2026"
]
}
# --- Step 2: Print the employee's full name ---
print("=" * 50)
print("EMPLOYEE INFORMATION")
print("=" * 50)
print(f"Full Name: {employee['first_name']} {employee['last_name']}")
print(f"Manager: {employee['is_manager']}")
print(f"Base Salary: ${employee['base_salary']:,.2f}")
# --- Step 3: Display current projects ---
print("\nCurrent Projects:")
for i, project in enumerate(employee['projects'], 1):
print(f" {i}. {project}")
# --- Step 4: Add a new project to the list ---
# The list is MUTABLE, so we can modify it directly
print("\n--- Adding new project ---")
new_project = "AI Integration 2026"
employee['projects'].append(new_project) # Modified the existing list
print(f"Added project: {new_project}")
# --- Step 5: Increase base salary by 10% ---
# float is IMMUTABLE, so we create a new value and assign it
print("\n--- Processing salary increase ---")
old_salary = employee['base_salary']
employee['base_salary'] *= 1.10 # Equivalent to: employee['base_salary'] = employee['base_salary'] * 1.10
print(f"Old Salary: ${old_salary:,.2f}")
print(f"New Salary: ${employee['base_salary']:,.2f}")
print(f"Increase: ${employee['base_salary'] - old_salary:,.2f} (10%)")
# --- Step 6: Print final summary ---
print("\n" + "=" * 50)
print("UPDATED EMPLOYEE SUMMARY")
print("=" * 50)
print(f"Name: {employee['first_name']} {employee['last_name']}")
print(f"Base Salary: ${employee['base_salary']:,.2f}")
print(f"Manager: {'Yes' if employee['is_manager'] else 'No'}")
print("\nProject List:")
for i, project in enumerate(employee['projects'], 1):
print(f" {i}. {project}")
print(f"\nTotal Projects: {len(employee['projects'])}")
print("\n" + "=" * 50)
print("Mutability Summary:")
print("- Dictionary (employee): MUTABLE")
print("- Strings (first_name, last_name): IMMUTABLE")
print("- Float (base_salary): IMMUTABLE")
print("- Boolean (is_manager): IMMUTABLE")
print("- List (projects): MUTABLE")
print("=" * 50)
Sample Output:
==================================================
EMPLOYEE INFORMATION
==================================================
Full Name: Sarah Johnson
Manager: True
Base Salary: $75,000.00
Current Projects:
1. Project Phoenix
2. Data Migration 2026
--- Adding new project ---
Added project: AI Integration 2026
--- Processing salary increase ---
Old Salary: $75,000.00
New Salary: $82,500.00
Increase: $7,500.00 (10%)
==================================================
UPDATED EMPLOYEE SUMMARY
==================================================
Name: Sarah Johnson
Base Salary: $82,500.00
Manager: Yes
Project List:
1. Project Phoenix
2. Data Migration 2026
3. AI Integration 2026
Total Projects: 3
==================================================
Mutability Summary:
- Dictionary (employee): MUTABLE
- Strings (first_name, last_name): IMMUTABLE
- Float (base_salary): IMMUTABLE
- Boolean (is_manager): IMMUTABLE
- List (projects): MUTABLE
==================================================
Key Concepts Demonstrated:
Mutable Dictionary – We modified the dictionary by changing the base_salary value and accessing the list for modification.
Immutable Strings – Though we "changed" the salary, we actually created a new float object and reassigned it. The original float was discarded.
Mutable List – The projects list was modified in-place using .append(). The list object's identity remained the same; only its contents changed.
Type Awareness – Understanding mutability helps predict when modifications affect original data and when they create new objects.
Takeaway: Always consider mutability when designing data structures. Use mutable types (lists, dictionaries) when data changes frequently, and immutable types (strings, tuples, floats) for data that should remain constant or when you want to prevent accidental modifications.