Writing Efficient Python Code

Foundations for efficiencies

  1. Pythonic: Code that executes quickly for the task at hand, minimizes the memory footprint and follows Python’s coding style principles.

  2. Example:

1
2
3
4
5
6
7
# Collect the names in the above list that have six letters or more.
for name in names:
if len(name) >= 6:
better_list.append(name)

# Pythonic Way
best_list = [name for name in names if len(name) >= 6]
  1. Python Standard Library
    (1) Built-in types: list, tuple, set, dict, and others
    (2) Built-in functions: print(), len(), range(), round(), enumerate(), map(), zip(), and others
    (3) Built-in modules: os, sys, itertools, collections, math and others

Examples:

  • range()
1
2
3
4
5
6
7
8
9
10
11
# Create a range object that goes from 0 to 5
nums = range(6)
print(type(nums))

# Convert nums to a list
nums_list = list(nums)
print(nums_list)

# Create a new list of odd numbers from 1 to 11 by unpacking a range object
nums_list2 = [*range(1,12,2)]
print(nums_list2)
  • enumerate()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Rewrite the for loop to use enumerate
indexed_names = []
for i,name in enumerate(names):
index_name = (i,name)
indexed_names.append(index_name)
print(indexed_names)

# Rewrite the above for loop using list comprehension
indexed_names_comp = [(i,name) for i,name in enumerate(names)]
print(indexed_names_comp)

# Unpack an enumerate object with a starting index of one
indexed_names_unpack = [*enumerate(names, 1)]
print(indexed_names_unpack)
  • map()
1
2
3
4
5
6
7
8
9
10
11
# Use map to apply str.upper to each element in names
names_map = map(str.upper, names)

# Print the type of the names_map
print(type(names_map))

# Unpack names_map into a list
names_uppercase = [*names_map]

# Print the list created above
print(names_uppercase)
  • zip()
    combine multiple objects together. It allows us to easily combine two or more objects. If we provide zip() with objects of differing lengths, it will only combine until the smallest lengthed object is exhausted.

NumPy arrays

  1. Numpy array makes every element the same type
  2. Mathematical operations are broadcasted to each element of a numpy array by default.
    NumPy’s broadcasting concept: broadcasting refers to a numpy array’s ability to vectorize operations, so they are performed on all elements of an object at once.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Print second row of nums
print(nums[1,:])

# Print all elements of nums that are greater than six
print(nums[nums > 6])
# nums > 6 create a boolean index for all items in nums that are greater than 6.

# Double every element of nums
nums_dbl = nums * 2
print(nums_dbl)

# Replace the third column of nums
nums[:,2] = nums[:,2] + 1
print(nums)

Exercise:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Create a list of arrival times
arrival_times = [*range(10,60,10)]

# Convert arrival_times to an array and update the times
arrival_times_np = np.array(arrival_times)
new_times = arrival_times_np - 3

# Use list comprehension and enumerate to pair guests to new times
guest_arrivals = [(names[i],time) for i,time in enumerate(new_times)]

# Map the welcome_guest function to each (guest,time) pair
welcome_map = map(welcome_guest, guest_arrivals)
# The function provided to map() shouldn't contain closing parenthesis (i.e., use welcome_guest instead of welcome_guest()).

guest_welcomes = [*welcome_map]
print(*guest_welcomes, sep='\n')

Examing the runtime

  1. Unpacking the range object was faster than list comprehension.
1
2
3
4
5
6
7
# Create a list of integers (0-50) using list comprehension
nums_list_comp = [num for num in range(51)]
print(nums_list_comp)

# Create a list of integers (0-50) by unpacking range
nums_unpack = [*range(51)]
print(nums_unpack)
  1. Using Python’s literal syntax to define a data structure can speed up your runtime. Consider using the literal syntaxes (like [] instead of list(), {} instead of dict(), or () instead of tuple()), where applicable, to gain some speed.

  2. Numpy is more efficient than Array