Dictionaries in Python
A dictionary in Python is an unordered, mutable collection of key–value pairs.
Syntax:
d = {key1: value1, key2: value2}
1. Dictionary Operations
These are basic operations that can be performed on a dictionary.
a. Accessing values
Use the key to access a value.
student = {"name": "John", "age": 20}
print(student["name"])
b. Adding new key–value pairs
student["marks"] = 85
c. Updating values
student["age"] = 21
d. Deleting items
Using del:
del student["age"]
Using pop() (removes and returns the value):
[Link]("marks")
Using popitem() (removes the last inserted pair):
[Link]()
e. Checking membership
Checks only keys, not values.
"name" in student # True
100 in student # False
f. Traversing a dictionary
for key in student:
print(key, student[key])
g. Length of dictionary
len(student)
Dictionary operations — detailed explanation
1. What a dictionary is (quick recap)
A dictionary is a collection of key → value pairs. Keys must be hashable (immutable types
like int, str, tuple of immutables). Values can be anything.
Note: In Python ≤3.6 dictionaries were conceptually unordered; since Python 3.7 insertion
order is guaranteed (i.e., iterating yields items in the order keys were inserted).
d = {"name": "Alice", "age": 30}
2. Creating dictionaries
Multiple ways:
d1 = {} # empty
d2 = dict() # empty via constructor
d3 = {"a": 1, "b": 2}
d4 = dict(a=1, b=2) # keys must be valid identifiers
pairs = [("x", 10), ("y", 20)]
d5 = dict(pairs) # from sequence of (key, value)
Comprehension:
squares = {n: n*n for n in range(5)} # {0:0,1:1,2:4,3:9,4:16}
3. Accessing values
Direct indexing (raises KeyError if missing):
value = d["name"]
Safely using get() (returns None or provided default if key missing):
value = [Link]("salary") # None
value = [Link]("salary", 0) #0
Use case: get() avoids try/except when a missing key is acceptable.
4. Adding and updating items
Assign by key:
d["city"] = "Mumbai" # add or update
Update multiple keys at once:
[Link]({"age": 31, "country": "IN"})
# or
[Link](other_dict)
Note: update() overwrites existing keys, and accepts any mapping or iterable of pairs.
5. Deleting items
del — removes key, raises KeyError if absent:
del d["age"]
pop(key[, default]) — removes and returns value; if key missing and default not
provided, raises KeyError:
v = [Link]("age", None)
popitem() — removes and returns last inserted key/value pair (Python 3.7+).
Historically arbitrary in older versions.
k,v = [Link]()
clear() — remove all items:
[Link]()
6. Membership tests
Membership checks keys, not values:
if "name" in d: # True if key exists
if "Alice" in [Link](): # check values (O(n))
Checking values is slower because it scans values.
7. Traversal / iteration patterns
Iterate keys (default):
for k in d:
print(k, d[k])
Keys, values, items views:
for k in [Link](): # same as for k in d
for v in [Link]():
for k,v in [Link](): # get both
Important: [Link](), [Link](), [Link]() return views — they reflect changes to the
dictionary. If you need a fixed list, wrap with list().
Safe mutation while iterating: don’t add/remove keys from a dict while iterating over it;
iterate over a static list copy instead:
for k in list([Link]()):
if should_delete(k):
del d[k]
8. Combining and merging dictionaries
update() modifies in place:
[Link](b) # a gets/overwrites keys from b
Merge to produce a new dict (Python 3.9+):
c=a|b # new dict with b overriding a where conflict
Merge with assignment (Python 3.9+):
a |= b # in-place merge (like update)
Old pattern (works on older versions):
c = {**a, **b} # dict unpacking; b overrides a
9. Aliasing and copying (brief)
Assignment creates an alias (two names same object):
a = {"x": 1}
b=a
b["x"] = 2 # a["x"] is now 2
Shallow copy (new outer dict, same inner objects):
b = [Link]()
# or b = dict(a)
Deep copy (duplicates nested mutable objects):
import copy
b = [Link](a)
10. Nested dictionaries
Dictionaries can contain dictionaries:
school = {
"class1": {"teacher": "Mr A", "students": 30},
"class2": {"teacher": "Ms B", "students": 28}
Access nested items safely:
teacher = [Link]("class1", {}).get("teacher")
Or use [Link] for auto-vivification patterns (convenient for grouping):
from collections import defaultdict
d = defaultdict(list)
d["fruits"].append("apple")
11. Useful methods and idioms
setdefault(key, default) — return value if key exists, else insert key with default and
return default:
[Link]("visits", 0)
d["visits"] += 1
[Link](iterable, value=None) — create dict where each key maps to same
value:
keys = ["a","b","c"]
d = [Link](keys, 0) # {"a":0,"b":0,"c":0}
items() + unpacking for sorting:
sorted_by_value = sorted([Link](), key=lambda kv: kv[1])
12. Hashing and valid keys
Keys must be hashable (immutable). Valid: int, str, float, tuple of immutables.
Invalid: list, dict, set.
Example bad key:
bad = {}
d = {}
d[[1,2]] = "oops" # TypeError: unhashable type: 'list'
14. Common pitfalls & gotchas
Mutating nested objects in a shallow copy:
a = {"lst": [1,2]}
b = [Link]()
b["lst"].append(3) # affects a too
Using mutable objects as keys will fail:
d = {}
d[{1:2}] = "no" # TypeError
Assuming unordered: remember insertion order is guaranteed in Python 3.7+.
Modifying dict while iterating causes RuntimeError — iterate over list(d) if modifying.
15. Examples (small snippets with expected behavior)
Add / update / delete
d = {}
d["a"] = 1 # {'a':1}
[Link]({"b":2}) # {'a':1,'b':2}
v = [Link]("a") # v==1, d is {'b':2}
Counting items (pattern)
words = ["apple","banana","apple"]
counts = {}
for w in words:
counts[w] = [Link](w,0) + 1
# counts -> {'apple':2, 'banana':1}
(Or use [Link] for convenience.)
Merging without mutating originals
a = {"x":1}
b = {"y":2}
c = {**a, **b} # {'x':1,'y':2}
16. When to use a dict vs other structures
Use a dict when you need fast key-based lookup or an association between keys and
values.
Use list when order and indexing by position matter.
Use set when you need membership uniqueness, not key→value mapping.
Consider defaultdict, Counter, OrderedDict (rarely needed now), MappingProxyType
(read-only view) for specific needs.
2. Dictionary Methods
Python provides many built-in dictionary methods.
a. keys()
Returns all keys.
[Link]()
b. values()
Returns all values.
[Link]()
c. items()
Returns key–value pairs as tuples.
[Link]()
d. get(key, default)
Safer way to access value; doesn’t give error.
[Link]("name")
[Link]("salary", "Not available")
e. update()
Updates or adds multiple key–value pairs.
[Link]({"age": 25, "city": "Chennai"})
f. setdefault(key, default)
If key exists → return value
If not → insert key with default value
[Link]("course", "Python")
g. clear()
Removes all items.
[Link]()
3. Aliasing and Copying
A. Aliasing
Aliasing means two variables refer to the same dictionary in memory.
d1 = {"a": 1, "b": 2}
d2 = d1 # aliasing
Now:
d2["a"] = 100
print(d1)
Output:
{'a': 100, 'b': 2}
Reason
Both d1 and d2 point to the same memory location.
B. Copying
Copying creates a new independent dictionary.
1. Shallow Copy
Using copy() or dict():
d1 = {"a": 1, "b": 2}
d2 = [Link]()
Now d2 is a new dictionary; changing d2 will not affect d1.
But shallow copy has a limitation
If the dictionary contains nested dictionaries or lists:
d1 = {"x": [1, 2, 3]}
d2 = [Link]()
d2["x"].append(4)
This will affect both d1 and d2 because the inner list is still shared.
2. Deep Copy
Deep copy duplicates even nested structures.
import copy
d1 = {"x": [1, 2, 3]}
d2 = [Link](d1)
d2["x"].append(4)
Now d1 remains unchanged.
Summary Table
Concept Meaning Example
Dictionary operations Add, delete, update, traverse d["a"] = 10, del d["b"]
Dictionary methods Built-in functions for dictionaries [Link](), [Link]()
Aliasing Two variables refer to same dictionary d2 = d1
Shallow copy Creates new dict but shares nested items [Link]()
Deep copy Fully independent copy [Link]()
NumPy – Detailed Explanation
NumPy (Numerical Python) is the fundamental package for scientific computing in Python.
It provides:
Fast array operations
Vectorized computation
Memory-efficient data storage
Support for multidimensional arrays (ndarrays)
NumPy arrays are faster than Python lists because they use contiguous memory and are
implemented in C.
1. About NumPy
NumPy Array (ndarray)
A NumPy ndarray is a homogeneous, fixed-type, multidimensional array.
Features
Faster than Python lists
Supports mathematical operations on entire arrays
Convenient reshaping, slicing, indexing
Supports broadcasting
Supports linear algebra, Fourier transforms, statistics
Example: Creating an array
import numpy as np
a = [Link]([1, 2, 3])
b = [Link]([[1, 2], [3, 4]])
2. shape — Array Shape
shape tells the dimensions (rows, columns, etc.) of an array.
arr = [Link]([[1, 2, 3],
[4, 5, 6]])
print([Link])
Output:
(2, 3) # 2 rows, 3 columns
Changing shape using reshape()
arr2 = [Link](3, 2)
Important:
Total number of elements must remain the same.
arr2 = [Link](3, 2)
Meaning of [Link](3, 2)
reshape() changes the shape (dimensions) of the NumPy array without changing
the data.
General rule:
Total number of elements must remain the same.
🔍 Example
import numpy as np
arr = [Link]([[1, 2, 3],
[4, 5, 6]])
# arr has 6 elements
arr2 = [Link](3, 2)
Original shape:
arr shape → (2 rows, 3 columns) → 6 elements
New shape:
arr2 → (3 rows, 2 columns) → 6 elements
So reshape is allowed.
How the elements are arranged in the
new shape
NumPy fills the new array row-wise (C-order) by default.
Original:
1 2 3
4 5 6
After reshape(3, 2):
1 2
3 4
5 6
Important notes
1. Reshape does not copy data (most of the time)
arr2 is usually a view of arr, meaning changing arr2 may change arr.
2. Total number of elements must match
[Link](4, 2) # ❌ Error (8 elements needed)
3. You can use -1 to let NumPy compute a dimension
[Link](-1, 2) # NumPy automatically finds correct shape
3. Slicing in NumPy
Slicing allows selecting sub-arrays using index ranges.
Syntax:
arr[start:end:step]
Example:
arr = [Link]([10, 20, 30, 40, 50])
print(arr[1:4])
Output:
[20 30 40]
2D slicing:
a = [Link]([[1,2,3],
[4,5,6],
[7,8,9]])
print(a[0:2, 1:3])
Output:
[[2 3]
[5 6]]
Important:
Slicing returns a view, not a copy.
Changing the slice changes the original array.
a = [Link]([[1,2,3],
[4,5,6],
[7,8,9]])
print(a[0:2, 1:3])
Understanding the array
The array a is:
Row 0 → 1 2 3
Row 1 → 4 5 6
Row 2 → 7 8 9
Indexes:
Row indices: 0, 1, 2
Column indices: 0, 1, 2
Meaning of the slice a[0:2, 1:3]
NumPy slicing works as:
a[row_start : row_end , column_start : column_end]
1. Row part → 0:2
This means select rows 0 and 1
(Row 2 not included because slicing stops at 2)
2. Column part → 1:3
Select columns 1 and 2
(Stops at 3, so column 3 is not included)
Extracted sub-array
From rows 0 and 1, and columns 1 and 2:
Row 0, cols 1 & 2 → 2 3
Row 1, cols 1 & 2 → 5 6
So the output is:
[[2 3]
[5 6]]
✔ Final Answer
[[2 3]
[5 6]]
4. Masking (Boolean Indexing)
Masking selects elements based on logical conditions.
Example:
arr = [Link]([10, 25, 30, 45, 50])
mask = arr > 30
print(mask)
Output:
[False False False True True]
Using mask to filter:
print(arr[arr > 30])
Output:
[45 50]
Masking with multiple conditions:
arr[(arr > 20) & (arr < 50)]
5. Broadcasting
Broadcasting allows NumPy to perform operations on arrays of different shapes by
“stretching” them without copying data.
Example 1: Adding scalar to array
arr = [Link]([1, 2, 3])
print(arr + 5)
Output:
[6 7 8]
Scalar is broadcast to match array shape.
Example 2: Adding 1D array to 2D array
a = [Link]([[1,2,3],
[4,5,6]])
b = [Link]([10,20,30])
print(a + b)
Output:
[[11 22 33]
[14 25 36]]
b (1×3) is broadcast across each row of a (2×3).
Broadcasting Rules (Important for exams)
1. Compare shapes from right to left
2. Dimensions match if:
o They are equal
o One of them is 1
3. If not matchable → broadcasting error
6. dtype — Data Type of Array
dtype represents the data type of array elements.
Common NumPy dtypes
int32, int64
float32, float64
bool
complex64
str (Unicode)
Example:
arr = [Link]([1, 2, 3], dtype='float32')
print([Link])
Output:
float32
Changing dtype using astype()
arr2 = [Link](int)
NumPy arrays are homogeneous, so all elements have the same dtype (unlike Python lists).
What is dtype in NumPy?
dtype stands for data type of a NumPy array.
Every NumPy array has one and only one data type, and all elements must have the same
type (unlike Python lists).
It defines:
How much memory each element uses
How the element is stored internally
What operations are allowed
Why dtype is important?
1. NumPy is fast because it uses fixed-type, continuous memory storage.
2. dtype ensures efficient vectorized operations.
3. dtype affects:
o speed
o memory usage
o precision of calculations
Checking the dtype
import numpy as np
arr = [Link]([1, 2, 3])
print([Link])
Output:
int64
You can check dtype using:
[Link] → returns the type of array elements
[Link]() → converts dtype
Common NumPy data types
dtype Meaning
int8, int16, int32, int64 Integer types
uint8, uint16, … Unsigned integers
float16, float32, float64 Floating-point numbers
complex64, complex128 Complex numbers
bool_ Boolean
str_ or unicode_ Strings
object_ Python objects
Specifying a dtype while creating an array
arr = [Link]([1, 2, 3], dtype='float32')
print([Link])
Output:
float32
Changing dtype (type conversion)
Use the astype() method:
arr = [Link]([1.5, 2.5, 3.5])
new_arr = [Link](int)
print(new_arr)
print(new_arr.dtype)
Output:
[1 2 3]
int64
dtype affects memory usage
a = [Link]([1,2,3], dtype=np.int32) # 4 bytes per element
b = [Link]([1,2,3], dtype=np.int64) # 8 bytes per element
Summary (Exam Points)
dtype = the data type of a NumPy array.
All elements in a NumPy array must have the same dtype.
Common dtypes: int32, int64, float32, float64, bool, complex64, etc.
You can check dtype using .dtype.
You can convert dtype using .astype().
dtype affects speed, precision, memory usage, and allowed operations.
Summary Table
Concept Meaning Example
About NumPy Library for fast numerical computing [Link]([1,2,3])
Shape Size/dimensions of array (2,3)
Slicing Extracting sub-arrays arr[1:3]
Masking Boolean filtering arr[arr>50]
Broadcasting Operations on arrays with different shapes a + b
dtype Data type of array elements [Link]