Sai Vidya Institute of Technology, Bengaluru (Affiliated to VTU)
Course: Python Programming – 1BPLC105B
Module 3: Dictionaries, Numpy, Files
3.1. Dictionaries:
3.1.1. Dictionary operations,
3.1.2. dictionary methods,
3.1.3. aliasing and copying.
3.2. Numpy:
3.2.1. About,
3.2.2. Shape,
3.2.3. Slicing,
3.2.4. masking,
3.2.5. Broadcasting,
3.2.6. dtype.
3.3. Files:
3.3.1. About files,
3.3.2. Writing our first file,
3.3.3. Reading a file line-at-a-time,
3.3.4. Turning a file into a list of lines,
3.3.5. Reading the whole file at once,
3.3.6. Working with binary files,
3.3.7. Directories,
3.3.8. Fetching something from the Web.
Dictionaries: Dictionary Operations, Dictionary Methods, Aliasing and Copying
3.1. Dictionaries
3.1.1. Dictionary operations,
3.1.2. dictionary methods,
3.1.3. aliasing and copying.
3.1. Dictionaries:
Q. What is Dictionary in Python Programming?
1. Dictionary is a compound type. Dictionaries are Python’s built-in mapping type. They map keys to values.
2. In Dictionary, keys are immutable but values are mutable.
3. The key:value pairs of the dictionary are separated by commas. Each pair contains a key and a value separated by
a colon.
4. Other sequence types(strings, lists, and tuples) use integer indices to access the values. But the dictionary value
can be accessed by the key.
Q. How to create a dictionary?
• To create a dictionary is to start with the empty dictionary and add key : value pairs. The empty dictionary is
denoted { } (pair of curly-braces).
• Example: Create a dictionary to translate English words into Spanish. For this dictionary, the keys are strings.
english_spanish = {}
english_spanish["one"] = "uno"
english_spanish["two"] = "dos"
➢ The first assignment creates a dictionary named english_spanish; the other assignments add new key:value pairs to the
dictionary. We can print the current value of the dictionary in the usual way:
print(english_spanish)
output:
{"two": "dos", "one": "uno"}
➢ Another way to create a dictionary is to provide a list of key:value pairs using the same syntax as the previous output:
english_spanish = {"one": "uno", "two": "dos", "three": "tres"}
➢ It doesn’t matter what order we write the pairs. The values in a dictionary are accessed with keys, not with indices, so there
is no need to care about ordering.
➢ how we use a key to look up the corresponding value?
print(english_spanish["two"])
'dos'
The key "two" yields the value "dos".
Q. Justify why we can’t index or slice a dictionary?
➢ Lists, tuples, and strings have been called sequences, because their items occur in order. The dictionary is the first
compound type that we’ve seen that is not a sequence, so we can’t index or slice a dictionary.
Hashing
Example:
{"apples": 430, "bananas": 312, "oranges": 525, "pears": 217}
➢ Using list of tuples to implement the above dictionary:
[('apples', 430), ('bananas', 312), ('oranges', 525), ('pears', 217)]
➢ List of tuples also can map the key to a value, then why we are using dictionaries?
The reason is dictionaries are very fast, implemented using a hashing technique, which allows us to access a
value very quickly. By contrast, the list of tuples implementation is slow. If we wanted to find a value associated with a key,
we would have to iterate over every tuple, checking the 0th element. What if the key wasn’t even in the list? We would have
to get to the end of it to find out.
3.1.1. Dictionary operations:
1. Deletion
2. Insertion
3. Lookup (Search)
➢ The del statement removes a key:value pair from a dictionary.
➢ For example, the following dictionary contains the names of various fruits and the number of each fruit in stock:
inventory = {"apples": 430, "bananas": 312, "oranges": 525, "pears": 217}
print(inventory)
output: {'pears': 217, 'apples': 430, 'oranges': 525, 'bananas': 312}
➢ If someone buys all of the bananas, we can remove the entry from the dictionary:
del inventory["bananas"]
print(inventory)
output: {'apples': 430, 'oranges': 525, 'pears': 217}
Note: If we then try to see how many bananas we have, we get an error (because, yes, we have no bananas). Or if
we’re expecting more bananas soon, we might just change the value associated with bananas:
inventory["bananas"] = 0
print(inventory)
{'pears': 217, 'apples': 430, 'oranges': 525, 'bananas': 0}
➢ A new shipment of bananas arriving could be handled like this:
inventory["bananas"] += 200
print(inventory)
{'pears': 0, 'apples': 430, 'oranges': 525, 'bananas': 512}
➢ The len function also works on dictionaries; it returns the number of key:value pairs:
len(inventory)
output: 4
3.1.2. Dictionary methods
❖ keys(): The keys method returns all keys of dictionary items. We can iterate over the view, or turn the view into a
list. For example,
Example:
english_spanish = {"one": "uno", "two": "dos", "three": "tres"}
for key in english_spanish.keys(): #iterate over the view
print("Got key", key, "which maps to value", english_spanish[key])
keys = list(english_spanish.keys()) # turn the view into a list
print(keys)
output:
Got key three which maps to value tres
Got key two which maps to value dos
Got key one which maps to value uno
['three', 'two', 'one']
➢ Without using keys method, call in the for loop: It will iterate over a dictionary implicitly iterates over its
keys.
Example:
english_spanish = {"one": "uno", "two": "dos", "three": "tres"}
for key in english_spanish:
print("Got key", key)
output:
Got key one
Got key two
Got key three
❖ values(): The values method will return the list of values from dictionary items.
Example:
english_spanish = {"one": "uno", "two": "dos", "three": "tres"}
list(english_spanish.values()) #output:['uno', 'dos', 'tres']
❖ items(): The items method returns a list of tuples — one tuple for each key:value pair.
Example:
english_spanish = {"one": "uno", "two": "dos", "three": "tres"}
list(english_spanish.items()) # [('one', 'uno'), ('two', 'dos'), ('three', 'tres')]
❖ The in and not in operators can test if a key is in the dictionary
➢ "one" in english_spanish # output: True
➢ "six" in english_spanish # output: False
❖ Searching a key which is not available in a dictionary causes a runtime error. For Example,
english_spanish["dog"] # Run-Time error will raise
Traceback (most recent call last): ... KeyError: 'dog'
3.1.3. Aliasing and Copying
❖ Whenever two variables refer to the same object, changes to one affect the other.
❖ If we want to modify a dictionary and keep a copy of the original, use the copy method.
❖ For example, opposites is a dictionary that contains pairs of opposites:
opposites = {"up": "down", "right": "wrong", "yes": "no"}
alias = opposites
copy = [Link]() # Shallow copy
❖ alias and opposites refer to the same object; copy refers to a fresh copy of the same dictionary. If we modify alias,
opposites also changed:
alias["right"] = "left"
opposites["right"] #output: 'left'
❖ If we modify copy, ‘opposites’ not changed:
copy["right"] = "privilege"
opposites["right"] #output: 'left'
❖ Solved Problem in Text Book: Count the number of occurrences of a letter in a string. (form a frequency table of
the letters in the string, that is, how many times each letter appears)
Dictionaries provide an elegant way to generate a frequency table by using get(). Example;
letter_counts = {}
for letter in "Mississippi":
letter_counts[letter] = letter_counts.get(letter, 0) + 1
print(letter_counts) #output: {'M': 1, 's': 4, 'p': 2, 'i': 4}
❖ To display the frequency table in alphabetical order. We can do that with the items and sort methods (more
precisely, sort orders lexicographically):
letter_items = list(letter_counts.items())
letter_items.sort()
print(letter_items) # output: [('M', 1), ('i', 4), ('p', 2), ('s', 4)]
Numpy
3.2. Numpy:
3.2.1. About,
3.2.2. Shape,
3.2.3. Slicing,
3.2.4. masking,
3.2.5. Broadcasting,
3.2.6. dtype.
3.2.1. About
➢ Numpy: Python lists are not designed as mathematical objects. To get a type of list which behaves like a
“mathematical array” or “matrix”, we use Numpy module.
For example: Create an array and multiply each element of the array by 2.
import numpy as np
a = [Link]([2, 3, 8])
2.1 * a
array([ 4.2, 6.3, 16.8])
• In the above example, abbreviated numpy to np. “[Link]” takes a Python list as argument.
• The list [2, 3, 8] contains int’s, yet the result contains float’s. This means numpy changed the data type
automatically.
➢ Multiplying between arrays. The following example shows squared the array element-wise. Arithmetic
operations between arrays are performed element-wise, not on the arrays as a whole.
import numpy as np
a = [Link]([2, 3, 8])
a * a # output: array([ 4, 9, 64])
a**2 # output: array([ 4, 9, 64])
➢ Matrix multiplication between 2D arrays will be done using [Link].
➢ NumPy additionally has algebraic functions like [Link], [Link], etc.
# Example for dot product between arrays.
a = [Link]([2, 3, 8]) #create an array
[Link](a,a) # multiply arrays using “[Link]”
#output: 77 (2*2+3*3+8*8)
3.2.2. Shapes (Arrays and their Shape):
• Shape of an array is one of the most important properties of a NumPy array.
• The shape of a NumPy array represents the number of elements in each dimension (axis) and is returned
as a tuple.
• Single Dimension (1D) Arrays has row of elements. It has a list of elements in a single dimension.
1D array Example: [1, 2, 3, 4].
• Two Dimensional (2D) Arrays are represented as rows and columns (like a matrix).
2D arrays Example: Matrices, Tables, grayscale image (rows × columns of pixels)
• Three Dimensional (3D) Arrays: For example, Color Images.
In color image, each pixel is an RGB tuple: the intensity in red, green and blue. Since, each pixel itself is an
array, the whole image becomes a 3D array.
• Accessing Shape in NumPy: The shape of an array is accessed using the .shape attribute.
import numpy as np
a = [Link]([2, 3, 8])
[Link] #output: (3,)
The array ‘a’ has 3 elements. It is a 1-dimensional array. Shape is written as 3, (comma shows it is a tuple).
• 2D array example:
b = [Link]( [ [2, 3, 8], [4, 5, 6]])
[Link] #output: (2,3)
In above example, array ‘b’ has 2 rows. Each row has 3 columns. Shape of the array ‘b’ is is (2, 3)
3.2.3. Slicing
• Slicing allows selection of specific portions of arrays, similar to Python lists.
• Indexing starts at 0: Always remember Python uses zero-based indexing.
• 1D Array Slicing: Works exactly like Python lists.
Example:
a = [Link]([2, 3, 8])
a [ 2 ] # Output: 8
a [ 1 : ] # Output: array([3, 8])
a[start:end] → selects elements from index start up to (but not including) end.
a[start:] → selects from start till the end.
a[:end] → selects from beginning till end-1.
• 2D Array Slicing: Arrays can have multiple dimensions (rows & columns).
Example for slicing a 2D array:
b = [Link] ( [ [2, 3, 8], [4, 5, 6] ] )
Row selection: b[1] → selects the 2nd row → array([4, 5, 6])
Element selection:
b[1][2] → selects element at row 2, column 3 → 6
Shortcut: b[1, 2] → 6
To select 1st column in 2D array: use : to select all items along the first dimension, and then a 1:
b[:,1] #output: array([3,5])
3.2.4. masking
• Masking is the process of selecting or modifying elements of an array based on a condition.
• It is one of the most powerful features of NumPy because it avoids explicit loops and makes code concise
and efficient.
Boolean Masks: A mask is a Boolean array (True/False) created by applying a condition on the array.
a = [Link]([230, 10, 284, 39, 76])
cutoff = 200
mask = a > cutoff
print(mask)
# Output: [True, False, True, False, False]
• Each element is tested against the condition, producing a Boolean result.
Applying Masks: Masks can be used to filter or modify values directly.
Example: Set all values above 200 to zero
a[a > cutoff] = 0
print(a)
# Output: [0, 10, 0, 39, 76]
In the above coe, a[a > cutoff] = 0 represents, select all positions where condition is True and assign 0 to
those positions.
• Advantage of masking over traditional loops is efficiency: Masking uses vectorized operations in NumPy,
which are executed at the low‑level (C/Fortran) backend. This makes them much faster than Python’s explicit
for loops, especially for large datasets or multi‑dimensional arrays.
Example,
# code with NumPy masking
a[a > 200] = 0 # one line, fast
# Traditional loop code has multiple lines, slower:
for x in a:
if x > cutoff:
new_a.append(0) # replace with 0 if above cutoff
else:
new_a.append(x) # keep the value if below cutoff
• Advantages of Masking
• Concise: One line replaces multiple lines of looping code.
• Efficient: Vectorized operations are faster than Python loops.
• Readable: Easier to understand and maintain.
• Scalable: Works seamlessly with multi dimensional arrays (e.g., images).
• Applications of Masking in NumPy:
Data cleaning: Replace invalid, missing, or extreme values in datasets with safe defaults (e.g., zeros).
Image processing: Apply conditions across pixels in 2D/3D arrays, such as removing noise or
thresholding.
Statistical analysis: Select subsets of data that meet specific criteria for further computation or
visualization.
3.2.5. Broadcasting
• Broadcasting in NumPy:
Broadcasting is a powerful NumPy feature that allows arithmetic operations between arrays of
different shapes.
NumPy automatically stretches dimensions of size 1 to match the shape of the other array, enabling
element wise operations without explicit looping. For example,
a = [Link]( [ [0, 1], [2, 3],[4, 5] ])
b = [Link]([10, 100])
print(a * b)
output:
array([[ 0, 100], [ 20, 300], [ 40, 500]])
In the above example, array a has shape (3,2) and array b has shape (2,). NumPy stretches b into
shape (3,2) internally, then performs element-wise multiplication.
• Rules of Broadcasting
Dimension comparison starts from the last axis (rightmost).
Dimensions must either be equal, OR one of them must be 1 (stretchable).
If both condition are not satisfied, broadcasting fails and gives “Value Error”
• Example of Broadcasting Error:
c = [Link]([[0, 1, 2], [3, 4, 5]]) # shape (2,3)
b = [Link]([10, 100]) # shape (2,)
print(c * b)
Error: Shapes (2,3) and (2,) cannot be broadcast because last dimensions 3 and 2 differ, and neither is 1.
• Fixing Broadcasting Errors: Use None (or [Link]) to explicitly add a dimension of size 1 to fix the above
code’s broadcasting error.
c = [Link]([[0, 1, 2],[3, 4, 5]])
b = [Link]([10, 100])
print(c * b[:, None])
output:
[[ 0 10 20]
[300 400 500]]
➢ Here, b[:, None] reshapes b from (2,) → (2,1), making it compatible with c.
• Advantages of Broadcasting
No explicit loops → concise and readable code.
Efficient → operations are vectorized and faster.
Flexible → works across arrays of different shapes.
Useful in scientific computing → especially in image processing, linear algebra, and machine learning.
3.2.6. dtype
• dtype stands for data type in NumPy arrays. It specifies the type and size of elements stored in the array (e.g.,
int8, float32).
• Commonly used dtypes: integers (int8, int16, int32, int64), unsigned integers (uint8, uint16), and
floating‑point numbers (float32, float64).
Example: int8 and uint8
int8 → signed 8 bit integer. Range: –128 to +127.
uint8 → unsigned 8 bit integer. Range: 0 to 255.
Each bit can be 0 or 1 → with 8 bits, 2^8=256possible values.
• Larger dtypes
int64 → signed 64 bit integer. Range: –9,223,372,036,854,775,808 to +9,223,372,036,854,775,807.
Default dtype depends on machine architecture (commonly 64 bit).
Bigger dtype ≠ always better → wastes memory if values are small.
• If a value exceeds the maximum of its dtype, overflow occurs. For example,
a = [Link]([200], dtype='uint8')
print(a + a)
# Output: [144] (overflow, since 400 > 255)
• To fix this error, use a larger dtype (uint16) to accommodate bigger results:
a = [Link]([200], dtype='uint16')
print(a + a)
# Output: [400]
Practical Example: Images
• Images are stored as arrays of pixels, usually in RGB format.
• Each channel (R, G, B) is a uint8 value (0–255).
(0,0,0) → black
(255,0,0) → red
• If you add an image to itself, values may exceed 255. So, overflow generates noise instead of brighter colors.
Advantages of Choosing Correct dtype
Memory efficiency: Use smaller dtypes when values are small.
Performance: Smaller dtypes can be faster to process.
Accuracy: Larger dtypes prevent overflow and preserve correct results.
Compatibility: Image formats and scientific data often require specific dtypes.
• Changing dtype : use astype method to change the dtype of an existing array:
import numpy as np
a = [Link]([200], dtype='uint8')
[Link]('uint64')
Files
3.3. Files:
3.3.1. About files,
3.3.2. Writing our first file,
3.3.3. Reading a file line-at-a-time,
3.3.4. Turning a file into a list of lines,
3.3.5. Reading the whole file at once,
3.3.6. Working with binary files,
3.3.7. Directories,
3.3.8. Fetching something from the Web.
3.3.1. About files
• While a program is running, its data is stored in random access memory (RAM). RAM is fast and inexpensive,
but it is volatile(temporary) memory, which means that when the program ends, or the computer shuts down,
data in RAM disappears.
• To make data available the next time the computer is turned on and the program is started, it has to be
written to a non-volatile(permanent) storage medium, such a hard drive, usb drive, or CD-RW.
• Data on non-volatile storage media is stored in named locations called files. By reading and writing files,
programs can save information between program runs.
• Files in programming:
Open a file → specify its name and mode (read or write).
Close a file → ensures data is saved and resources are released.
Read from a file → access stored data sequentially or randomly.
Write to a file → store new data or update existing content.
3.3.2. Writing our first file
• Files allow programs to store data permanently on disk (non‑volatile storage).
• Writing to a file means saving data so it can be accessed later, even after the program ends.
Example:
with open("[Link]", "w") as myfile:
[Link]("My first file written from Python\n")
[Link]("---------------------------------\n")
[Link]("Hello, world!\n")
➢ Created when a file is opened. Variable myfile refers to the handle object. Methods called on the handle
(write) make changes to the actual file on disk.
Syntax: open(filename, mode)
• First argument → name of the file ("[Link]")
• Second argument → mode ("w" for writing).
• "w" mode: Creates the file if it does not exist. Replaces (overwrites) the file if it already exists.
write() Method: Used to put data into the file. Each call writes a string to the file. In larger programs,
writing is often done inside a loop
with Block: Ensures the file is automatically closed after the block ends. Even if an error occurs, the file
is properly closed (except in extreme cases like power failure). Safer and cleaner than manually calling
close().
• Modes of File Opening (for context)
➢ "w" → Write (creates/overwrites file).
➢ "r" → Read (file must exist).
➢ "a" → Append (adds data to end of file).
➢ "b" → Binary mode (used for non text files like images).
3.3.3. Reading a file line-at-a-time:
• After writing a file, we often need to read its contents back into a program.
• Reading line‑by‑line is efficient, especially for large files.
Example:
with open("[Link]", "r") as my_new_handle:
for the_line in my_new_handle:
# Process each line
print(the_line, end="")
➢ open() function: open(filename, mode) is used to access files; "r" mode opens a file for reading, and if
the file does not exist, Python raises a FileNotFoundError.
➢ File Handle: A file handle (e.g., my_new_handle) refers to the opened file object and is used to iterate
through its lines
➢ for Loop with File Handle: A for loop on the file handle reads each line sequentially, including its
newline character.
➢ print with end="": Using print(line, end="") suppresses the extra newline since each line already
contains \n.
3.3.4. Turning a file into a list of lines
• Often useful to read all lines from a file into a list for processing.
• Example: storing friends’ names and email addresses, then sorting them alphabetically.
with open("[Link]", "r") as input_file:
all_lines = input_file.readlines()
all_lines.sort()
with open("[Link]", "w") as output_file:
for line in all_lines:
output_file.write(line)
In the above code,
➢ readlines() Method: Reads all lines from the file at once. Returns a list of strings, each string
representing one line (including newline characters).
➢ Sorting Lines: [Link]() arranges the lines in alphabetical (lexicographic) order. Writing Sorted Lines:
Open a new file in "w" mode. Use a loop to write each line back into the new file.
➢ Efficiency: Easier than reading line‑by‑line and manually building a list. Python’s built‑in methods
(readlines(), sort()) simplify the process.
• Applications
Preparing sorted contact lists.
Organizing log files or reports.
Data preprocessing before analysis.
3.3.5. Reading the whole file at once
• Instead of reading line‑by‑line, Python allows reading the entire file content into a single string.
• Useful when the line structure is not important and to process the whole text at once.
Example:
with open("[Link]") as f:
content = [Link]()
words = [Link]()
print("There are {0} words in the file.".format(len(words)))
➢ read() Method: Reads the complete file content into a single string. Suitable for small to medium files where
memory usage is not a concern.
➢ String Processing: Once content is read, string methods can be applied. For example, split() breaks the string
into words for counting.
➢ Default Mode: If no mode is specified in open(), Python defaults to "r" (read mode).
➢ Word Count Example: Read file → split into words → count length of list → print result.
• Applications
➢ Word count programs.
➢ Text analysis (frequency of words, searching patterns).
➢ Preprocessing text data for natural language processing.
Question Bank for Module 3
1. What is aliasing in dictionaries? (2 marks)
2. What is a dictionary in Python? (2 marks)
3. Explain dictionary operations with examples. (5 marks)
4. Explain the concept of shape in NumPy arrays with examples. (5 marks)
5. Define slicing in NumPy arrays. (2 marks)
6. Write the output if a = [Link]([2,3,8]); print(a[1:]). (2 marks)
7. What does b[1,2] return if b = [Link]([[2,3,8],[4,5,6]]). (2 marks)
8. Differentiate between b[1] and b[:,1]. (2 marks)
9. State the general syntax of slicing in Python/NumPy. (2 marks)
10. Explain slicing in 1D and 2D arrays with examples. (5 marks)
11. Demonstrate how to extract subarrays using slicing (with code and output). (5 marks)
12. Define masking in NumPy. (2 marks)
13. State one advantage of masking over traditional loops. (2 marks)
14. Demonstrate how masking can be used to replace values above a cutoff with zero. Compare with a loop-based
approach. (5 marks)
15. Define broadcasting in NumPy. (2 marks)
16. Explain broadcasting in NumPy with an example of multiplying arrays of different shapes.(5 marks)
17. Justify why dictionaries are faster than lists of tuples. (2 marks)
18. How can you sort the lines of a file alphabetically? (2 marks)
19. Write a program to read a file of names, sort them, and save the sorted list into another file. (5 marks)
20. Explain with an example how to read the entire contents of a file into a string and process it in Python. (5 marks)