In Python, a dictionary (or dict) is a built-in data type for storing data in key-value pairs. While keys have to be immutable (e.g., integers or strings), values can have any type, including lists or other dictionaries.
Dictionaries use curly braces {} with a colon : separating each key and value. Commas separate the pairs.
# Creating a dictionary of user data
user_profile = {
"username": "coder_99",
"email": "coder@example.com",
"followers": 1250,
"is_active": True
}
Keys must be unique: You cannot have duplicate keys. If you assign a new value to an existing key, it will overwrite the old one.
Keys must be immutable: You can use strings, numbers, or tuples as keys, but you cannot use lists (because lists can change).
Values can be anything: A value can be a string, integer, list, boolean, or even another dictionary.
Here is how you interact with a dictionary in everyday Python coding:
You can grab a value by passing its key inside square brackets [] or by using the .get() method.
# Method 1: Square brackets
print(user_profile["username"]) # Output: coder_99
# Method 2: The .get() method (Safer, returns None if key doesn't exist instead of crashing)
print(user_profile.get("email")) # Output: coder@example.com
If the key already exists, the value gets updated. If it doesn't, a new pair is created.
# Updating an existing value
user_profile["followers"] = 1251
# Adding a new key-value pair
user_profile["location"] = "New York"
You can use del or the .pop() method to remove a key.
# Removes the "is_active" key and returns its value
user_profile.pop("is_active")
# Deletes the key completely
del user_profile["email"]
You can loop through just the keys, just the values, or both at the same time using .items().
# Looping through keys and values together
for key, value in user_profile.items():
print(f"{key}: {value}")
Nested Dictionaries
A nested dictionary is simply a dictionary that contains other dictionaries as its values.
Think of it like a folder structure on your computer: you have a main folder, and inside it are subfolders, each containing their own specific files. This is incredibly useful for storing complex, structured data—like user profiles, product catalogs, or API responses.
Here is how you structure a nested dictionary. In this example, we have a classroom dictionary, where each student's name is a key, and their details are stored in another dictionary.
classroom = {
"student_1": {
"name": "Alice",
"age": 14,
"grades": {"math": 95, "science": 88} # Double nested!
},
"student_2": {
"name": "Bob",
"age": 15,
"grades": {"math": 78, "science": 82}
}
}
To drill down into a nested dictionary, you string together square brackets []. You start from the outermost key and move inward.
# 1. Get student_1's entire dictionary
print(classroom["student_1"])
# Output: {'name': 'Alice', 'age': 14, 'grades': {'math': 95, 'science': 88}}
# 2. Get student_1's name
print(classroom["student_1"]["name"])
# Output: Alice
# 3. Get student_1's math grade (going three layers deep)
print(classroom["student_1"]["grades"]["math"])
# Output: 95
Safe Access: If you are worried a key might not exist, you can chain .get() methods: classroom.get("student_1", {}).get("grades", {}).get("math")
Modifying a nested dictionary works the same way as accessing it—you just chain the keys to target the exact spot you want to change.
# Updating an existing value (Changing Bob's age to 16)
classroom["student_2"]["age"] = 16
# Adding a new key-value pair inside a nested dictionary
classroom["student_1"]["attendance"] = "98%"
# Adding a brand new nested dictionary (student_3)
classroom["student_3"] = {
"name": "Charlie",
"age": 14,
"grades": {"math": 85, "science": 90}
}
When you loop through a nested dictionary, the first loop targets the outer keys. You need a second, inner loop to look through the inner data.
for student_id, student_info in classroom.items():
print(f"\nID: {student_id}")
# Loop through the inner dictionary
for key, value in student_info.items():
print(f" {key}: {value}")
Dictionary vs JSON
A Python Dictionary is a live data structure inside your computer's memory while a Python script is running. JSON (JavaScript Object Notation) is just a plain text string used to exchange data between different languages and systems (like sending data from a Python backend to a JavaScript frontend).
While they look like twins, they have subtle syntax differences that will crash your code if mixed up:
# A Python Dictionary
my_dict = {
"name": "Alice",
"is_admin": True,
"hobbies": None
}
# The equivalent JSON (Notice the quotes and lowercase values)
'''
{
"name": "Alice",
"is_admin": true,
"hobbies": null
}
'''
Python has a built-in module called json specifically designed to translate between Python dictionaries and JSON strings.
Serialization (dumps): Converting a Python dict into a JSON string.
Deserialization (loads): Converting a JSON string into a Python dict.
Think of this as "dumping" a Python object into a string so you can save it to a file or send it over the internet.
import json
user_data = {"name": "Bob", "active": True}
# Convert to JSON string
json_string = json.dumps(user_data)
print(json_string) # Output: '{"name": "Bob", "active": true}'
print(type(json_string)) # Output: <class 'str'>
Think of this as "loading" a raw text string you received from an API and turning it into a usable Python dictionary.
import json
raw_json = '{"name": "Bob", "active": true}'
# Convert to Python dictionary
python_dict = json.loads(raw_json)
print(python_dict["name"]) # Output: Bob
print(type(python_dict)) # Output: <class 'dict'>
If you want to read or write directly to a .json file on your hard drive, you drop the "s" from the function names and use json.dump() and json.load().
import json
data = {"score": 42, "player": "Neo"}
# Open a file in write mode
with open("save_data.json", "w") as file:
json.dump(data, file, indent=4) # indent makes it pretty and readable
import json
# Open a file in read mode
with open("save_data.json", "r") as file:
loaded_data = json.load(file)
print(loaded_data["player"]) # Output: Neo
In Python, a tuple is a built-in data type used to store a collection of data. It is very similar to a list, but with one critical difference: tuples are immutable, meaning once they are created, their elements cannot be changed, added, or removed.
Think of a tuple as a locked list.
Tuples are defined by enclosing elements in parentheses (), separated by commas.
# A simple tuple of integers
numbers = (1, 2, 3)
# A tuple with mixed data types
mixed_tuple = ("Python", 3.14, True, 42)
# An empty tuple
empty_tuple = ()
The One-Item Gotcha: If you want to create a tuple with only one item, you must include a trailing comma. Otherwise, Python will just see it as a regular variable in parentheses.
not_a_tuple = ("apple") # Type is str
a_tuple = ("apple",) # Type is tuple
Ordered: Elements have a defined order that will not change.
Immutable: You cannot modify, add, or delete items after creation.
Allows Duplicates: Since items are indexed, tuples can have multiple identical values.
Heterogeneous: They can store any mix of data types (including lists or other tuples).
Like lists, tuples use zero-based indexing.
fruits = ("apple", "banana", "cherry", "orange")
print(fruits[0]) # Output: apple
print(fruits[-1]) # Output: orange (negative indexing)
print(fruits[1:3]) # Output: ('banana', 'cherry') (slicing)
You can extract the values of a tuple back into individual variables.
coordinates = (4, 5)
x, y = coordinates
print(x) # Output: 4
print(y) # Output: 5
In Python, a set is an unordered collection of unique elements. Think of it like a bag of items where duplicates are automatically thrown out, and the order you put them in doesn't matter.
Here is a quick breakdown of how they work, how to use them, and why they are incredibly useful.
Unordered: The items do not have a defined order. You cannot access them using an index (like my_set[0]).
Unique: Duplicate elements are not allowed. If you try to add a duplicate, Python just ignores it.
Mutable: You can add or remove items from a set.
Unindexed/Hashable Items: While the set itself is mutable, the elements inside the set must be immutable (like strings, ints, or tuples). You cannot put a list or another set inside a set.
You can create a set using curly braces {} or the set() function.
# Creating a set with elements
fruits = {"apple", "banana", "cherry", "apple"}
print(fruits)
# Output: {'banana', 'cherry', 'apple'} (Notice "apple" only appears once!)
# Creating an empty set
# Note: empty_set = {} creates an empty DICTIONARY, not a set!
empty_set = set()
Because sets are mutable, you can change their contents using built-in methods.
colors = {"red", "green"}
# Adding elements
colors.add("blue")
# Removing elements
colors.remove("green") # Raises a KeyError if "green" doesn't exist
colors.discard("yellow") # Safely removes; does NOT raise an error if missing
print(colors) # Output: {'red', 'blue'}
One of the best features of Python sets is their ability to perform mathematical set operations like union, intersection, and difference.
set_a = {1, 2, 3, 4}
set_b = {3, 4, 5, 6}
# Union (All unique elements from both sets)
print(set_a | set_b) # Output: {1, 2, 3, 4, 5, 6}
print(set_a.union(set_b)) # Same result
# Intersection (Elements present in BOTH sets)
print(set_a & set_b) # Output: {3, 4}
print(set_a.intersection(set_b))
# Difference (Elements in A but NOT in B)
print(set_a - set_b) # Output: {1, 2}
print(set_a.difference(set_b))
# Symmetric Difference (Elements in A or B, but NOT both)
print(set_a ^ set_b) # Output: {1, 2, 5, 6}
Removing Duplicates: The easiest way to clean up a list with duplicate entries is to convert it to a set and back to a list: unique_list = list(set(ordered_list)).
Membership Testing: Checking if an item exists in a set (if item in my_set) is incredibly fast ($O(1)$ time complexity). Doing the same in a list requires scanning the whole list ($O(n)$ time complexity).
In Python, a function is a reusable block of code that only runs when it is called. Functions help break our code into smaller, modular, and manageable chunks.
To define a function, Python uses the def keyword, followed by the function name, parentheses (), and a colon :.
def greet(name):
"""This is a docstring (optional) explaining what the function does."""
return f"Hello, {name}!"
def: Keywords that flags the start of a function definition.
Parameters: The variables inside the parentheses that receive data (e.g., name).
Docstring: An optional triple-quoted string used to document what the function does.
return: Sends a value back to the caller. If no return is specified, the function returns None by default.
To execute a function, simply use its name followed by parentheses containing any necessary arguments.
# Calling the function and storing the result
message = greet("Alice")
print(message) # Output: Hello, Alice!
Python functions are incredibly flexible with how they accept data. Here are the four main ways to handle arguments:
Arguments passed in the exact order they were defined.
def describe_pet(animal, name):
print(f"I have a {animal} named {name}.")
describe_pet("dog", "Buddy") # Output: I have a dog named Buddy.
Arguments passed explicitly by name, meaning order doesn't matter.
describe_pet(name="Whiskers", animal="cat") # Output: I have a cat named Whiskers.
You can provide default values for parameters. If the caller skips that argument, the default is used.
def greet_user(name, greeting="Welcome"):
return f"{greeting}, {name}!"
print(greet_user("Bob")) # Output: Welcome, Bob!
print(greet_user("Bob", "Good day")) # Output: Good day, Bob!
When you don't know how many arguments will be passed:
Use *args to receive arguments as a tuple.
def make_pizza(size, *toppings):
print(f"Making a {size} pizza with:")
for topping in toppings:
print(f"- {topping}")
make_pizza("Large", "pepperoni", "mushrooms", "extra cheese")
Use kwargs to receive arguments as a dictionary.
def print_user_profile(**kwargs):
print(kwargs) # It behaves exactly like a dictionary
print(type(kwargs)) # <class 'dict'>
# Passing different numbers of keyword arguments
print_user_profile(username="coder123", email="alex@email.com", age=25)
# Output: {'username': 'coder123', 'email': 'alex@email.com', 'age': 25}
If you want to use multiple types of arguments in a single function, you must follow a strict specific order:
Positional/Standard arguments
*args (Arbitrary positional arguments)
kwargs (Arbitrary keyword arguments)
def master_function(required_arg, *args, **kwargs):
print(f"Required: {required_arg}")
print(f"args: {args}")
print(f"kwargs: {kwargs}")
master_function("Hello", 1, 2, 3, site="GitHub", status="Active")
# Output:
# Required: Hello
# args: (1, 2, 3)
# kwargs: {'site': 'GitHub', 'status': 'Active'}
Python comes with a library of pre-defined functions that are always available. You don't need to define or import them.
Examples: print(), len(), type(), int(), sum(), max().
numbers = [1, 2, 3, 4]
print(len(numbers)) # Output: 4
print(sum(numbers)) # Output: 10
For small, one-liner functions, Python offers lambda functions. They can take any number of arguments but can only have one expression.
# Standard function
def square(x):
return x * x
# Equivalent Lambda function
square_lambda = lambda x: x * x
print(square_lambda(5)) # Output: 25
multiply = lambda x, y: x * y
print(multiply(3, 4)) # Output: 12
Variables defined inside a function belong to that function's local scope and cannot be accessed from outside.
def my_func():
x = 10 # Local variable
print(x)
my_func()
# print(x) # This would throw a NameError because x doesn't exist outside the function.
A recursive function is a function that calls itself in order to break down a problem into smaller, manageable sub-problems. It always requires a base case to prevent an infinite loop.
def factorial(n):
if n == 1: # Base case
return 1
else:
return n * factorial(n - 1) # Recursive call
print(factorial(5)) # Output: 120
A function is called a "higher-order function" if it does at least one of the following:
Takes one or more functions as arguments.
Returns a function as its result.
Python has built-in higher-order functions like map(), filter(), and reduce().
# map() applies a function to all items in an input list
numbers = [1, 2, 3, 4]
squared = list(map(lambda x: x**2, numbers))
print(squared) # Output: [1, 4, 9, 16]