Previous | Tutorial index | Next

Tutorial 2: Basic Data Types and Data Models

Learning Objective

Use different types of data and data models correctly.

1. Introduction

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:

1.2 Numeric Types: Integers and Floats

1.3 Text Type: Strings (str)

1.4 Boolean Type (bool)

1.5 Sequence Types: Lists and Tuples

Both list and tuple are ordered collections. The critical difference lies in mutability.

1.6 Mapping Type: Dictionaries (dict)

1.7 Data Models and Mutability (Deep Dive)

This is the foundational concept behind "data models" in Python.

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

2. Code Examples (Annotated)

# --- 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!

3. Quiz (Check Your Understanding)

Question 1: What data type does Python assign to the value 3.0? a) int b) float c) str d) bool

Answer b) `float` – any number with a decimal point is a float.

Question 2: What is the difference between [1, 2, 3] and (1, 2, 3)?

Answer `[1, 2, 3]` is a **list** (mutable, can be changed). `(1, 2, 3)` is a **tuple** (immutable, cannot be changed after creation).

Question 3: Why does 0.1 + 0.2 not equal 0.3 in Python?

Answer Floating‑point numbers are stored in binary, and some decimal fractions cannot be represented exactly. This leads to small rounding errors.

Question 4: Can a dictionary have a list as a key? Why or why not?

Answer No. Dictionary keys must be immutable. Lists are mutable, so they are not hashable and cannot be used as keys.

Question 5: What is the output of type(True)? a) <class 'bool'> b) <class 'int'> c) <class 'str'> d) True

Answer a) `` – `True` is a boolean value.

Question 6: (Trick question) What is the type of the variable x after x = (5)? a) tuple b) int

Answer b) `int`. Parentheses alone do not create a tuple; a comma is needed: `(5,)` creates a tuple.

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?

Answer No, strings are immutable. Trying to assign to an index raises a `TypeError`.

Question 8: What is the output of the following code?

data = {"a": 1, "b": 2} data["c"] = 3 print(len(data))
Answer `3` – a new key‑value pair was added, so the dictionary now has three items.

4. Exercises (In-Class / Lab Practice)

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).

Sample Solution ```python a = 10 # int b = 3.14 # float c = 5.0 # float (looks like int) d = "Hello" # str e = True # bool f = [1, 2, 3] # list g = (1, 2, 3) # tuple h = {"name": "Alice"} # dict

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:

Sample Solution ```python inventory = { "item": "laptop", "price": 999.99, "quantity": 10, "in_stock": True } inventory["price"] = 899.99 inventory["category"] = "electronics" print(inventory) ```

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.

Sample Answer
# 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:

Exercise 5: Alias Demonstration Write code to demonstrate the "alias danger":

Sample Answer
# --- 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:

  1. When you assign a list to another variable using = (e.g., copy = original), both variables reference the same list object in memory.
  2. Modifying one variable affects the other because they share the same underlying data.
  3. To create an independent copy, use:
    • true_copy = original[:] (slicing)
    • true_copy = original.copy() (copy method)
    • true_copy = list(original) (list constructor)
  4. Slicing [:] creates a shallow copy, which is sufficient for simple lists like this one.

Why This Matters:

5. Homework Questions (Deep Thinking)

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.

Sample Answer ```python from decimal import Decimal print(0.1 + 0.2) # 0.30000000000000004 print(Decimal('0.1') + Decimal('0.2')) # 0.3 ``` The float result is slightly off because binary floating‑point cannot represent 0.1 exactly. The `decimal` module uses base‑10 arithmetic, which is exact for decimal fractions, making it essential for financial calculations where rounding must be precise.

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.

Sample Answer Mutable types (e.g., list, dict) can be modified in‑place without creating a new object. Immutable types (e.g., int, str, tuple) cannot be changed; any operation that appears to modify them actually creates a new object in memory and rebinds the variable. For example, `x = 5; x += 1` creates a new integer object `6` and assigns `x` to it; the old `5` is discarded.

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?

Sample Answer

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:

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?

Sample Answer

Recommendation: Use a mutable list for the grades.

Reasoning:

  1. 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.

  2. 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
  3. 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:

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:

  1. Defines a dictionary employee containing: "first_name", "last_name", "base_salary" (float), "is_manager" (bool), and "projects" (a list of project names).
  2. The program should:
  3. Include comments in your code explaining which data structures are mutable and which are not.
Sample Answer
# 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:

  1. Mutable Dictionary – We modified the dictionary by changing the base_salary value and accessing the list for modification.

  2. Immutable Strings – Though we "changed" the salary, we actually created a new float object and reassigned it. The original float was discarded.

  3. Mutable List – The projects list was modified in-place using .append(). The list object's identity remained the same; only its contents changed.

  4. 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.

6. Summary Checklist (For Student Self-Review)

Previous | Tutorial index | Next