Bits of Python Information

Do not use mutable types as default parameter

Default argument values are evaluated only once during function definition at module load time. If you don't provide an argument to function with default parameter value, you don't get what you expect from your default parameter.
The fix is to set the keyword argument to None, and initialize it inside the function definition.

def func(data=[]):
    data.extend([num for num in range(3)])

    for item in data:
        print(item, end=' ')

func()
# Output: 0 1 2

func()
# Output: 0 1 2 0 1 2

def func(data=None):
    
    if data is None:
        data = []

    data.extend([num for num in range(3)])

    for item in data:
        print(item, end=' ')

func()
# Output: 0 1 2

func()
# Output: 0 1 2

func([1,2,3])
# Output: 1 2 3 0 1 2

zip and zip_longest functions

zip function can be used to process iterators in parallel. In Python 3, zip generator yields tuples containing the next value from each iterator.

However if the lengths of iterators are different, it yields tuples until one of the iterators is exhausted. The zip_longest function from itertools lets you iterate over multiple iterators in parallel regardless of their lengths.

list_a = [1, 2, 3, 4]
list_b = ['one', 'two', 'three', 'four']

for number, name in zip(list_a, list_b):
    print(f'{number}: {name}')

""" Output
1: one
2: two
3: three
4: four
"""

from itertools import zip_longest

# zip_longest can also take a 'fillvalue' parameter
# to fill non matching values.
list_c = ['one', 'two']
for number, name in zip_longest(list_a, list_c, fillvalue='***'):
    print(f'{number}: {name}')

""" Output
1: one
2: two
3: ***
4: ***
"""

Use generator expressions for large comprehensions

List comprehensions can cause problems for large inputs by using too much memory. Generator expressions avoid memory issues by producing outputs one at a time as an iterator.

squares_up_to_one_million = [num * num for num in range(1000000)]

# instead of above which will store all the one million number in memory, 
# use generator expression below 
squares_up_to_one_million = (num * num for num in range(1000000))

# this returns you an generator iterator instead of all the numbers 
# so that you can use as you need
squares_up_to_one_million
# <generator object <genexpr> at 0x107b491a8>

# For example you need only the first 100 squares from the result
for _ in range(100):
    print(next(squares_up_to_one_million))