Skip to main content

Chapter 2: Data Types & Variables - Master Python's Data Structures and Type System

Welcome to the most comprehensive guide to Python data types and variables available! This chapter is your complete reference for understanding Python's powerful and flexible type system. Whether you're a beginner learning your first programming language or an experienced developer transitioning to Python, this detailed guide will provide you with an in-depth understanding of how Python handles data, memory management, and type operations.

Learning Objectives

By the end of this comprehensive chapter, you will have mastered:

  • Complete understanding of Python's type system - from basic types to complex data structures
  • Number types mastery - integers, floats, complex numbers, and their mathematical operations
  • String manipulation expertise - formatting, methods, and advanced string operations
  • List operations proficiency - creation, manipulation, and list comprehensions
  • Tuple immutability concepts - when and why to use immutable sequences
  • Dictionary mastery - key-value pairs, advanced operations, and performance considerations
  • Set theory and operations - mathematical set operations and practical applications
  • Type conversion and validation - converting between types and ensuring data integrity
  • Memory management understanding - how Python handles objects and references
  • Performance optimization - choosing the right data type for optimal performance

Python Data Types Overview: Understanding Python's Type System

The Philosophy of Python's Type System

Python's type system is one of its most distinctive and powerful features. Unlike statically-typed languages like C++ or Java, Python uses dynamic typing, which means that variables don't have fixed types - they can hold values of any type, and the type is determined at runtime. This flexibility, combined with Python's duck typing philosophy ("if it walks like a duck and quacks like a duck, it's a duck"), makes Python incredibly versatile and expressive.

Understanding Python's Type Categories

Python's built-in data types can be organized into several categories, each serving specific purposes and offering unique advantages:

1. Numeric Types: Mathematical Operations Foundation

Integer (int)

  • Purpose: Represents whole numbers without decimal points
  • Memory: Variable size depending on the number's magnitude
  • Range: Unlimited precision (limited only by available memory)
  • Use Cases: Counting, indexing, mathematical calculations

Float (float)

  • Purpose: Represents real numbers with decimal points
  • Memory: Fixed size (typically 64 bits)
  • Precision: Limited by IEEE 754 double-precision standard
  • Use Cases: Scientific calculations, measurements, financial calculations

Complex (complex)

  • Purpose: Represents complex numbers with real and imaginary parts
  • Memory: Two floats (real and imaginary parts)
  • Use Cases: Scientific computing, signal processing, mathematical modeling

2. Sequence Types: Ordered Collections

String (str)

  • Purpose: Represents text data as sequences of Unicode characters
  • Immutability: Immutable (cannot be changed after creation)
  • Memory: Efficient storage with various encoding options
  • Use Cases: Text processing, data formatting, user interfaces

List (list)

  • Purpose: Mutable sequences that can hold any type of data
  • Flexibility: Can grow and shrink dynamically
  • Performance: O(1) access by index, O(n) for search operations
  • Use Cases: Data collections, stacks, queues, dynamic arrays

Tuple (tuple)

  • Purpose: Immutable sequences that can hold any type of data
  • Performance: Faster than lists for iteration and access
  • Memory: More memory-efficient than lists
  • Use Cases: Fixed data collections, function return values, dictionary keys

3. Mapping Type: Key-Value Relationships

Dictionary (dict)

  • Purpose: Maps keys to values using hash tables
  • Performance: O(1) average case for lookup, insertion, and deletion
  • Flexibility: Keys must be hashable (immutable), values can be anything
  • Use Cases: Caching, configuration, data lookup, JSON-like structures

4. Set Types: Unique Collections

Set (set)

  • Purpose: Unordered collections of unique elements
  • Performance: O(1) average case for membership testing
  • Operations: Mathematical set operations (union, intersection, difference)
  • Use Cases: Removing duplicates, membership testing, mathematical operations

Frozenset (frozenset)

  • Purpose: Immutable version of sets
  • Use Cases: Dictionary keys, set operations with immutable sets

5. Boolean Type: Logical Values

Boolean (bool)

  • Purpose: Represents truth values (True or False)
  • Inheritance: Subclass of int (True = 1, False = 0)
  • Use Cases: Conditional logic, flags, state indicators

6. None Type: Absence of Value

NoneType (None)

  • Purpose: Represents the absence of a value
  • Singleton: Only one None object exists in Python
  • Use Cases: Default return values, optional parameters, null checks

Python's Dynamic Typing: The Power of Flexibility

Understanding Dynamic Typing:

# The same variable can hold different types
my_variable = 42 # Now it's an integer
my_variable = "Hello" # Now it's a string
my_variable = [1, 2, 3] # Now it's a list
my_variable = {"key": "value"} # Now it's a dictionary

# Python determines the type at runtime
print(type(my_variable)) # <class 'dict'>

Benefits of Dynamic Typing:

  • Flexibility: Variables can hold any type of data
  • Rapid Development: No need to declare types upfront
  • Polymorphism: Functions can work with multiple types
  • Duck Typing: Objects are used based on their behavior, not their type

Considerations of Dynamic Typing:

  • Runtime Errors: Type errors are caught at runtime, not compile time
  • Performance: Slight overhead due to type checking at runtime
  • Documentation: Need to document expected types for clarity

Numbers in Python: Mathematical Operations and Precision

Understanding Python's Number System

Python's number system is designed to handle mathematical operations with precision and flexibility. Unlike many programming languages that have separate types for different integer sizes, Python uses a unified approach that automatically handles large numbers and provides precise mathematical operations.

Integer (int): Unlimited Precision Numbers

The Power of Python's Integer Implementation

Python's integers are unique in the programming world because they have unlimited precision. This means you can work with arbitrarily large numbers without worrying about overflow errors that plague other languages.

# Python can handle extremely large numbers
small_number = 42
large_number = 123456789012345678901234567890
huge_number = 2 ** 1000 # 2 to the power of 1000

print(f"Small number: {small_number}")
print(f"Large number: {large_number}")
print(f"Huge number: {huge_number}")
print(f"Type of huge number: {type(huge_number)}") # Still <class 'int'>

Integer Literals and Number Systems

Python supports multiple number systems, making it easy to work with different representations:

# Decimal (base 10) - default
decimal_num = 42
print(f"Decimal 42: {decimal_num}")

# Binary (base 2) - prefix with 0b or 0B
binary_num = 0b1010 # Binary: 1010 = 10 in decimal
binary_num_alt = 0B1010 # Alternative syntax
print(f"Binary 0b1010: {binary_num}")

# Octal (base 8) - prefix with 0o or 0O
octal_num = 0o755 # Octal: 755 = 493 in decimal
octal_num_alt = 0O755 # Alternative syntax
print(f"Octal 0o755: {octal_num}")

# Hexadecimal (base 16) - prefix with 0x or 0X
hex_num = 0xFF # Hexadecimal: FF = 255 in decimal
hex_num_alt = 0XFF # Alternative syntax
print(f"Hexadecimal 0xFF: {hex_num}")

# Scientific notation (creates float, not int)
scientific = 1e6 # 1.0 × 10^6 = 1,000,000.0
print(f"Scientific notation: {scientific}, type: {type(scientific)}")

Integer Operations: Mathematical Precision

Python's integer operations maintain precision and provide various mathematical functions:

# Basic arithmetic operations
a, b = 10, 3

# Addition
result_add = a + b
print(f"Addition: {a} + {b} = {result_add}")

# Subtraction
result_sub = a - b
print(f"Subtraction: {a} - {b} = {result_sub}")

# Multiplication
result_mul = a * b
print(f"Multiplication: {a} × {b} = {result_mul}")

# Division (always returns float in Python 3)
result_div = a / b
print(f"Division: {a} ÷ {b} = {result_div} (type: {type(result_div)})")

# Floor division (integer division)
result_floor = a // b
print(f"Floor division: {a} // {b} = {result_floor}")

# Modulus (remainder)
result_mod = a % b
print(f"Modulus: {a} % {b} = {result_mod}")

# Exponentiation
result_pow = a ** b
print(f"Exponentiation: {a}^{b} = {result_pow}")

# Power function (alternative syntax)
result_pow_func = pow(a, b)
print(f"Power function: pow({a}, {b}) = {result_pow_func}")

Advanced Integer Methods and Properties

# Integer methods and properties
num = 42

# Bit length (number of bits needed to represent the number)
bit_length = num.bit_length()
print(f"Bit length of {num}: {bit_length}")

# Binary representation
binary_repr = bin(num)
print(f"Binary representation: {binary_repr}")

# Octal representation
octal_repr = oct(num)
print(f"Octal representation: {octal_repr}")

# Hexadecimal representation
hex_repr = hex(num)
print(f"Hexadecimal representation: {hex_repr}")

# Absolute value
negative_num = -42
abs_value = abs(negative_num)
print(f"Absolute value of {negative_num}: {abs_value}")

# Maximum and minimum
numbers = [10, 5, 8, 3, 15]
max_num = max(numbers)
min_num = min(numbers)
print(f"Maximum: {max_num}, Minimum: {min_num}")

# Sum of numbers
sum_numbers = sum(numbers)
print(f"Sum: {sum_numbers}")

Integer Performance and Memory Considerations

# Understanding integer memory usage
import sys

# Small integers are cached (singleton objects)
a = 5
b = 5
print(f"a is b: {a is b}") # True - same object
print(f"Memory size of 5: {sys.getsizeof(5)} bytes")

# Large integers are not cached
large_a = 1000
large_b = 1000
print(f"large_a is large_b: {large_a is large_b}") # May be True or False
print(f"Memory size of 1000: {sys.getsizeof(1000)} bytes")

# Very large integers use more memory
huge_num = 2 ** 100
print(f"Memory size of 2^100: {sys.getsizeof(huge_num)} bytes")

Practical Integer Applications

# Real-world integer applications

# 1. Counting and indexing
students = ["Alice", "Bob", "Charlie", "Diana"]
for i in range(len(students)):
print(f"Student {i + 1}: {students[i]}")

# 2. Mathematical calculations
def calculate_factorial(n):
"""Calculate factorial using integers."""
if n < 0:
raise ValueError("Factorial is not defined for negative numbers")
if n <= 1:
return 1

result = 1
for i in range(2, n + 1):
result *= i
return result

factorial_5 = calculate_factorial(5)
print(f"5! = {factorial_5}")

# 3. Number system conversions
def convert_base(number, from_base, to_base):
"""Convert number between different bases."""
# Convert to decimal first
decimal = int(str(number), from_base)

# Convert to target base
if to_base == 2:
return bin(decimal)
elif to_base == 8:
return oct(decimal)
elif to_base == 16:
return hex(decimal)
else:
return decimal

# Convert binary to hexadecimal
binary_input = "1010"
hex_output = convert_base(binary_input, 2, 16)
print(f"Binary {binary_input} = {hex_output}")

# 4. Prime number checking
def is_prime(n):
"""Check if a number is prime."""
if n < 2:
return False
if n == 2:
return True
if n % 2 == 0:
return False

for i in range(3, int(n ** 0.5) + 1, 2):
if n % i == 0:
return False
return True

# Test prime numbers
test_numbers = [17, 25, 29, 97]
for num in test_numbers:
print(f"{num} is prime: {is_prime(num)}")

Float (float): Real Numbers and Floating-Point Precision

Understanding Floating-Point Numbers

Floating-point numbers in Python represent real numbers with decimal points. They are implemented using the IEEE 754 double-precision standard, which provides approximately 15-17 decimal digits of precision. Understanding how floating-point numbers work is crucial for scientific computing, financial calculations, and any application requiring decimal precision.

Float Literals and Scientific Notation

# Basic float literals
pi = 3.14159
negative_float = -2.5
zero_float = 0.0
large_float = 123456.789

# Scientific notation (exponential form)
scientific_large = 1.23e4 # 1.23 × 10^4 = 12300.0
scientific_small = 1.23e-4 # 1.23 × 10^-4 = 0.000123
scientific_negative = -5.67e-2 # -5.67 × 10^-2 = -0.0567

# Alternative scientific notation
alt_large = 1.23E4 # Capital E also works
alt_small = 1.23E-4 # Capital E for negative exponent

print(f"Scientific large: {scientific_large}")
print(f"Scientific small: {scientific_small}")
print(f"Scientific negative: {scientific_negative}")

# Converting between scientific and decimal notation
decimal_from_scientific = float("1.23e4")
print(f"Decimal from scientific: {decimal_from_scientific}")

Float Operations and Precision Considerations

# Basic float operations
x, y = 3.14, 2.71

# Addition
result_add = x + y
print(f"Addition: {x} + {y} = {result_add}")

# Subtraction
result_sub = x - y
print(f"Subtraction: {x} - {y} = {result_sub}")

# Multiplication
result_mul = x * y
print(f"Multiplication: {x} × {y} = {result_mul}")

# Division
result_div = x / y
print(f"Division: {x} ÷ {y} = {result_div}")

# Understanding floating-point precision issues
print("Floating-point precision example:")
print(f"0.1 + 0.2 = {0.1 + 0.2}") # May not be exactly 0.3
print(f"0.1 + 0.2 == 0.3: {0.1 + 0.2 == 0.3}") # False!

# Proper way to compare floats
import math
def is_close(a, b, tolerance=1e-9):
"""Check if two floats are close enough."""
return abs(a - b) < tolerance

print(f"0.1 + 0.2 is close to 0.3: {is_close(0.1 + 0.2, 0.3)}")
print(f"Using math.isclose: {math.isclose(0.1 + 0.2, 0.3)}")

Float Methods and Mathematical Functions

# Float methods and properties
num = 3.14159

# Check if float represents an integer
print(f"Is {num} an integer? {num.is_integer()}")

# Round to specific decimal places
rounded_2 = round(num, 2)
rounded_4 = round(num, 4)
print(f"Rounded to 2 places: {rounded_2}")
print(f"Rounded to 4 places: {rounded_4}")

# Absolute value
negative_float = -3.14
abs_value = abs(negative_float)
print(f"Absolute value of {negative_float}: {abs_value}")

# Mathematical functions
import math

# Square root
sqrt_result = math.sqrt(16.0)
print(f"Square root of 16: {sqrt_result}")

# Power
power_result = math.pow(2.0, 3.0)
print(f"2^3: {power_result}")

# Trigonometric functions
angle_rad = math.pi / 4 # 45 degrees in radians
sin_value = math.sin(angle_rad)
cos_value = math.cos(angle_rad)
print(f"sin(π/4): {sin_value}")
print(f"cos(π/4): {cos_value}")

# Logarithmic functions
log_result = math.log(10.0) # Natural logarithm
log10_result = math.log10(100.0) # Base-10 logarithm
print(f"ln(10): {log_result}")
print(f"log₁₀(100): {log10_result}")

Special Float Values and Edge Cases

# Special float values
import math

# Positive and negative infinity
pos_inf = float('inf')
neg_inf = float('-inf')
print(f"Positive infinity: {pos_inf}")
print(f"Negative infinity: {neg_inf}")

# Not a Number (NaN)
nan_value = float('nan')
print(f"NaN: {nan_value}")

# Checking for special values
print(f"Is infinity? {math.isinf(pos_inf)}")
print(f"Is NaN? {math.isnan(nan_value)}")
print(f"Is finite? {math.isfinite(3.14)}")

# Operations with special values
print(f"Infinity + 1: {pos_inf + 1}")
print(f"Infinity * 0: {pos_inf * 0}") # Results in NaN
print(f"NaN + 1: {nan_value + 1}") # Results in NaN

# Maximum and minimum representable values
max_float = sys.float_info.max
min_float = sys.float_info.min
print(f"Maximum float: {max_float}")
print(f"Minimum positive float: {min_float}")

Float Precision and Rounding Strategies

# Understanding float precision
import sys

# System float information
print("Float system information:")
print(f"Maximum value: {sys.float_info.max}")
print(f"Minimum positive value: {sys.float_info.min}")
print(f"Machine epsilon: {sys.float_info.epsilon}")
print(f"Digits of precision: {sys.float_info.dig}")

# Rounding strategies
def demonstrate_rounding():
"""Demonstrate different rounding strategies."""
numbers = [2.5, 3.5, 4.5, 5.5]

print("Rounding strategies:")
for num in numbers:
# Standard rounding (round to nearest, ties to even)
standard = round(num)

# Floor (always round down)
floor_val = math.floor(num)

# Ceiling (always round up)
ceiling_val = math.ceil(num)

# Truncate (remove decimal part)
truncate_val = math.trunc(num)

print(f"Number: {num}")
print(f" Standard: {standard}")
print(f" Floor: {floor_val}")
print(f" Ceiling: {ceiling_val}")
print(f" Truncate: {truncate_val}")
print()

demonstrate_rounding()

# Decimal precision for financial calculations
from decimal import Decimal, getcontext

# Set precision for decimal calculations
getcontext().prec = 28

# Using Decimal for precise calculations
decimal_a = Decimal('0.1')
decimal_b = Decimal('0.2')
decimal_sum = decimal_a + decimal_b
print(f"Decimal precision: {decimal_a} + {decimal_b} = {decimal_sum}")
print(f"Decimal sum == 0.3: {decimal_sum == Decimal('0.3')}")

Practical Float Applications

# Real-world float applications

# 1. Financial calculations
def calculate_compound_interest(principal, rate, time, compound_frequency=12):
"""Calculate compound interest with proper decimal handling."""
# Use Decimal for financial precision
from decimal import Decimal, getcontext
getcontext().prec = 28

p = Decimal(str(principal))
r = Decimal(str(rate))
t = Decimal(str(time))
n = Decimal(str(compound_frequency))

amount = p * (1 + r / n) ** (n * t)
return float(amount)

# Calculate compound interest
principal = 1000.0
rate = 0.05 # 5% annual rate
time = 2.0 # 2 years
result = calculate_compound_interest(principal, rate, time)
print(f"Compound interest result: ${result:.2f}")

# 2. Scientific calculations
def calculate_distance(x1, y1, x2, y2):
"""Calculate Euclidean distance between two points."""
dx = x2 - x1
dy = y2 - y1
distance = math.sqrt(dx**2 + dy**2)
return distance

# Calculate distance between points
point1 = (0.0, 0.0)
point2 = (3.0, 4.0)
distance = calculate_distance(*point1, *point2)
print(f"Distance between {point1} and {point2}: {distance}")

# 3. Statistical calculations
def calculate_statistics(numbers):
"""Calculate basic statistics for a list of numbers."""
if not numbers:
return None

# Mean (average)
mean = sum(numbers) / len(numbers)

# Variance
variance = sum((x - mean) ** 2 for x in numbers) / len(numbers)

# Standard deviation
std_dev = math.sqrt(variance)

return {
'mean': mean,
'variance': variance,
'standard_deviation': std_dev,
'min': min(numbers),
'max': max(numbers)
}

# Calculate statistics
data = [1.5, 2.3, 3.7, 4.1, 5.9, 6.2, 7.8, 8.4, 9.1, 10.0]
stats = calculate_statistics(data)
print("Statistics:")
for key, value in stats.items():
print(f" {key}: {value:.3f}")

# 4. Temperature conversions
def celsius_to_fahrenheit(celsius):
"""Convert Celsius to Fahrenheit."""
return (celsius * 9/5) + 32

def fahrenheit_to_celsius(fahrenheit):
"""Convert Fahrenheit to Celsius."""
return (fahrenheit - 32) * 5/9

# Temperature conversions
celsius_temp = 25.0
fahrenheit_temp = celsius_to_fahrenheit(celsius_temp)
print(f"{celsius_temp}°C = {fahrenheit_temp}°F")

fahrenheit_temp = 77.0
celsius_temp = fahrenheit_to_celsius(fahrenheit_temp)
print(f"{fahrenheit_temp}°F = {celsius_temp}°C")

Complex Numbers

Complex numbers have real and imaginary parts:

# Complex number examples
z1 = 3 + 4j
z2 = complex(2, 5) # 2 + 5j
z3 = 1j # 0 + 1j

# Complex operations
z1, z2 = 3 + 4j, 1 + 2j
print(z1 + z2) # (4+6j)
print(z1 - z2) # (2+2j)
print(z1 * z2) # (-5+10j)
print(z1 / z2) # (2.2-0.4j)

# Complex number methods
z = 3 + 4j
print(z.real) # 3.0
print(z.imag) # 4.0
print(abs(z)) # 5.0 (magnitude)
print(z.conjugate()) # (3-4j)

Strings in Python

String Basics

Strings are sequences of characters:

# String creation
single_quote = 'Hello, World!'
double_quote = "Hello, World!"
triple_quote = """This is a
multi-line string"""
raw_string = r"C:\Users\Name" # Raw string

# String indexing
text = "Python Programming"
print(text[0]) # P
print(text[-1]) # g
print(text[7:18]) # Programming

# String methods
text = " Hello, World! "
print(text.strip()) # "Hello, World!"
print(text.upper()) # " HELLO, WORLD! "
print(text.lower()) # " hello, world! "
print(text.replace("World", "Python")) # " Hello, Python! "
print(text.split(",")) # [' Hello', ' World! ']
print(len(text)) # 17

String Formatting

# f-strings (Python 3.6+)
name = "Alice"
age = 30
print(f"My name is {name} and I am {age} years old")

# .format() method
print("My name is {} and I am {} years old".format(name, age))
print("My name is {name} and I am {age} years old".format(name=name, age=age))

# % formatting (older style)
print("My name is %s and I am %d years old" % (name, age))

# String alignment
text = "Python"
print(f"{text:<10}") # Left align: "Python "
print(f"{text:>10}") # Right align: " Python"
print(f"{text:^10}") # Center: " Python "

String Operations

# String concatenation
first = "Hello"
second = "World"
result = first + " " + second # "Hello World"
result = f"{first} {second}" # "Hello World"

# String repetition
text = "Python"
repeated = text * 3 # "PythonPythonPython"

# String membership
text = "Python Programming"
print("Python" in text) # True
print("Java" not in text) # True

# String comparison
str1, str2 = "apple", "banana"
print(str1 < str2) # True (lexicographic order)

Lists in Python

List Basics

Lists are ordered, mutable collections:

# List creation
numbers = [1, 2, 3, 4, 5]
mixed = [1, "hello", 3.14, True]
empty = []
nested = [[1, 2], [3, 4], [5, 6]]

# List indexing and slicing
fruits = ["apple", "banana", "cherry", "date"]
print(fruits[0]) # "apple"
print(fruits[-1]) # "date"
print(fruits[1:3]) # ["banana", "cherry"]
print(fruits[:2]) # ["apple", "banana"]
print(fruits[2:]) # ["cherry", "date"]

# List methods
fruits = ["apple", "banana"]
fruits.append("cherry") # Add to end
fruits.insert(1, "orange") # Insert at index
fruits.extend(["grape", "kiwi"]) # Add multiple items
fruits.remove("banana") # Remove first occurrence
popped = fruits.pop() # Remove and return last item
fruits.clear() # Remove all items

List Operations

# List concatenation and repetition
list1 = [1, 2, 3]
list2 = [4, 5, 6]
combined = list1 + list2 # [1, 2, 3, 4, 5, 6]
repeated = list1 * 2 # [1, 2, 3, 1, 2, 3]

# List comprehensions
squares = [x**2 for x in range(10)]
evens = [x for x in range(20) if x % 2 == 0]
words = ["hello", "world", "python"]
lengths = [len(word) for word in words]

# Nested list comprehensions
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flattened = [item for row in matrix for item in row]

# List sorting and reversing
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
numbers.sort() # In-place sort
sorted_nums = sorted(numbers) # Return new sorted list
numbers.reverse() # In-place reverse
reversed_nums = list(reversed(numbers)) # Return new reversed list

Tuples in Python

Tuple Basics

Tuples are ordered, immutable collections:

# Tuple creation
coordinates = (10, 20)
colors = ("red", "green", "blue")
single = (42,) # Single element tuple (note the comma)
empty = ()

# Tuple unpacking
point = (3, 4)
x, y = point # x = 3, y = 4

# Multiple assignment
a, b, c = 1, 2, 3

# Tuple methods
colors = ("red", "green", "blue", "red")
print(colors.count("red")) # 2
print(colors.index("green")) # 1

# Tuple operations
tuple1 = (1, 2, 3)
tuple2 = (4, 5, 6)
combined = tuple1 + tuple2 # (1, 2, 3, 4, 5, 6)
repeated = tuple1 * 2 # (1, 2, 3, 1, 2, 3)

When to Use Tuples

# Use tuples for:
# 1. Fixed data that shouldn't change
RGB_RED = (255, 0, 0)
RGB_GREEN = (0, 255, 0)

# 2. Function return values
def get_name_age():
return "Alice", 30

name, age = get_name_age()

# 3. Dictionary keys (tuples are hashable)
locations = {
(0, 0): "origin",
(1, 1): "diagonal"
}

# 4. Performance-critical code (tuples are faster)

Dictionaries in Python

Dictionary Basics

Dictionaries store key-value pairs:

# Dictionary creation
person = {
"name": "Alice",
"age": 30,
"city": "New York"
}

# Alternative creation methods
person = dict(name="Alice", age=30, city="New York")
person = dict([("name", "Alice"), ("age", 30)])

# Accessing values
print(person["name"]) # "Alice"
print(person.get("age")) # 30
print(person.get("salary", 0)) # 0 (default value)

# Dictionary methods
person = {"name": "Alice", "age": 30}
person["city"] = "New York" # Add/update
del person["age"] # Delete key
age = person.pop("age", 0) # Remove and return value
keys = person.keys() # Get all keys
values = person.values() # Get all values
items = person.items() # Get all key-value pairs

Dictionary Operations

# Dictionary comprehensions
squares = {x: x**2 for x in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

# Filtering dictionaries
scores = {"Alice": 85, "Bob": 92, "Charlie": 78}
high_scores = {name: score for name, score in scores.items() if score > 80}

# Merging dictionaries
dict1 = {"a": 1, "b": 2}
dict2 = {"c": 3, "d": 4}
merged = {**dict1, **dict2} # Python 3.5+
merged = dict1 | dict2 # Python 3.9+

# Nested dictionaries
students = {
"Alice": {"age": 20, "grade": "A"},
"Bob": {"age": 19, "grade": "B"}
}
print(students["Alice"]["grade"]) # "A"

Sets in Python

Set Basics

Sets are unordered collections of unique elements:

# Set creation
fruits = {"apple", "banana", "cherry"}
numbers = set([1, 2, 3, 4, 5])
empty = set() # Note: {} creates an empty dict, not set

# Set operations
set1 = {1, 2, 3, 4}
set2 = {3, 4, 5, 6}

# Union
union = set1 | set2 # {1, 2, 3, 4, 5, 6}
union = set1.union(set2) # Same result

# Intersection
intersection = set1 & set2 # {3, 4}
intersection = set1.intersection(set2)

# Difference
diff = set1 - set2 # {1, 2}
diff = set1.difference(set2)

# Symmetric difference
sym_diff = set1 ^ set2 # {1, 2, 5, 6}
sym_diff = set1.symmetric_difference(set2)

Set Methods

# Set methods
fruits = {"apple", "banana"}
fruits.add("cherry") # Add single element
fruits.update(["grape", "kiwi"]) # Add multiple elements
fruits.remove("banana") # Remove element (raises KeyError if not found)
fruits.discard("orange") # Remove element (no error if not found)
popped = fruits.pop() # Remove and return arbitrary element
fruits.clear() # Remove all elements

# Set membership and comparison
numbers = {1, 2, 3, 4, 5}
print(3 in numbers) # True
print(6 not in numbers) # True
print({1, 2}.issubset(numbers)) # True
print(numbers.issuperset({1, 2})) # True
print({1, 2}.isdisjoint({3, 4})) # True

Boolean and None Types

Boolean Type

# Boolean values
is_true = True
is_false = False

# Boolean operations
print(True and False) # False
print(True or False) # True
print(not True) # False

# Truthiness
print(bool(1)) # True
print(bool(0)) # False
print(bool("hello")) # True
print(bool("")) # False
print(bool([])) # False
print(bool([1, 2, 3])) # True

# Comparison operators return booleans
print(5 > 3) # True
print(5 == 3) # False
print(5 != 3) # True

None Type

# None represents absence of value
value = None
print(value is None) # True
print(value is not None) # False

# Common use cases
def find_user(user_id):
# Some logic here
if user_found:
return user_data
else:
return None

# Check for None
user = find_user(123)
if user is not None:
print(f"User found: {user}")
else:
print("User not found")

Type Conversion and Validation

Type Conversion

# Converting between types
num_str = "123"
num_int = int(num_str)
num_float = float(num_str)

float_num = 3.14
int_num = int(float_num) # 3 (truncates)
str_num = str(float_num) # "3.14"

# List, tuple, set conversions
numbers = [1, 2, 3, 2, 1]
tuple_nums = tuple(numbers) # (1, 2, 3, 2, 1)
set_nums = set(numbers) # {1, 2, 3} (removes duplicates)

# String to list
text = "hello"
char_list = list(text) # ['h', 'e', 'l', 'l', 'o']

# Dictionary conversions
items = [("a", 1), ("b", 2)]
dict_from_list = dict(items) # {'a': 1, 'b': 2}

Type Checking

# Check data types
value = 42
print(type(value)) # <class 'int'>
print(isinstance(value, int)) # True
print(isinstance(value, (int, float))) # True

# Type hints (Python 3.5+)
def add_numbers(a: int, b: int) -> int:
return a + b

# Union types (Python 3.10+)
def process_value(value: int | str) -> str:
return str(value)

Practical Examples

Example 1: Student Grade Manager

def student_grade_manager():
"""Manage student grades using dictionaries and lists."""
students = {}

while True:
print("\nStudent Grade Manager")
print("1. Add student")
print("2. Add grade")
print("3. View grades")
print("4. Calculate average")
print("5. Exit")

choice = input("Enter choice: ")

if choice == "1":
name = input("Enter student name: ")
students[name] = []
print(f"Student {name} added")

elif choice == "2":
name = input("Enter student name: ")
if name in students:
grade = float(input("Enter grade: "))
students[name].append(grade)
print(f"Grade {grade} added for {name}")
else:
print("Student not found")

elif choice == "3":
name = input("Enter student name: ")
if name in students:
grades = students[name]
print(f"Grades for {name}: {grades}")
else:
print("Student not found")

elif choice == "4":
name = input("Enter student name: ")
if name in students and students[name]:
grades = students[name]
average = sum(grades) / len(grades)
print(f"Average grade for {name}: {average:.2f}")
else:
print("No grades found for student")

elif choice == "5":
break

else:
print("Invalid choice")

# Run the manager
if __name__ == "__main__":
student_grade_manager()

Example 2: Data Analysis Helper

def data_analysis_helper():
"""Analyze a list of numbers."""
numbers = []

print("Enter numbers (type 'done' when finished):")
while True:
user_input = input("Number: ")
if user_input.lower() == 'done':
break
try:
numbers.append(float(user_input))
except ValueError:
print("Please enter a valid number")

if not numbers:
print("No numbers entered")
return

# Calculate statistics
total = sum(numbers)
count = len(numbers)
average = total / count
minimum = min(numbers)
maximum = max(numbers)

# Find unique numbers
unique_numbers = set(numbers)

# Count frequency
frequency = {}
for num in numbers:
frequency[num] = frequency.get(num, 0) + 1

# Display results
print(f"\nAnalysis Results:")
print(f"Total: {total}")
print(f"Count: {count}")
print(f"Average: {average:.2f}")
print(f"Minimum: {minimum}")
print(f"Maximum: {maximum}")
print(f"Unique numbers: {sorted(unique_numbers)}")
print(f"Frequency: {frequency}")

# Run the analyzer
if __name__ == "__main__":
data_analysis_helper()

Summary

In this chapter, we've covered:

  • Numeric types: integers, floats, and complex numbers
  • Strings: creation, manipulation, and formatting
  • Lists: mutable sequences with comprehensive operations
  • Tuples: immutable sequences for fixed data
  • Dictionaries: key-value pairs for structured data
  • Sets: unique collections with mathematical operations
  • Boolean and None: truth values and absence of data
  • Type conversion: converting between different data types

Understanding Python's data types is fundamental to writing effective Python code. Each data type has its specific use cases and methods that make Python programming both powerful and flexible.

Next Steps

Now that you understand Python data types, you're ready to explore:

  1. Control Flow: Learn about if statements, loops, and conditional logic
  2. Functions: Create reusable code with functions and parameters
  3. Object-Oriented Programming: Build classes and objects
  4. Error Handling: Manage exceptions and errors gracefully
  5. Advanced Topics: Explore decorators, generators, and more

Ready to control your program flow? Continue with Chapter 3: Control Flow to master Python's conditional statements and loops!