Make your code more readable using Pythonic Comprehension and Iterator.

A concise , crisp notes on list, set, dict comprehension and iterator patterns.

Introduction

Python is not just a programming language, it is a big community that uses, maintains, and enjoys Python. The value of the Python community is well summarized in Tim Peter's document The Zen Of Python(PEP 20).

There should be one - and preferably only one - obvious way to do it.

What is the Pythonic way?

Code is Pythonic if it is clear and works the way that a Python programmer would expect it to work. Pythonic code is easy to write and crisp in look. To be able to write Pythonic code is a very important skill you should have because, in the world of open-source software, data scientist share their code through the Jupyter Notebook, pythonic code is your gateway to membership in the global Python community.

Let's dive into it.

  1. List Comprehension

  2. Set and Dict comprehension

  3. Default Dict

  4. Iterators

1. List Comprehensions

i) Squaring the elements of a list

original_list = [1, 2, 3, 4, 5]
squared_list = [num ** 2 for num in original_list]
print(squared_list)

# Output: 
[1, 4, 9, 16, 25]

ii) Filtering a list to include only even numbers

original_list = [1, 2, 3, 4, 5]
even_list = [num for num in original_list if num % 2 == 0]
print(even_list)  

# Output: 
[2, 4]

iii) Creating a list of tuples from two separate lists

list1 = ['a', 'b', 'c']
list2 = [1, 2, 3]
combined_list = [(letter, number) for letter in list1 for number in list2]
print(combined_list)  

# Output: 
[('a', 1), ('a', 2), ('a', 3), ('b', 1), ('b', 2), ('b', 3), ('c', 1), ('c', 2), ('c', 3)]

with these examples, you can create your list comprehension, try it yourself.

2. Set and Dictionary comprehension

i) Set of squares of numbers from 1 to 10

squares_set = {num ** 2 for num in range(1, 11)}
print(squares_set)  

# Output: 
{1, 4, 9, 16, 25, 36, 49, 64, 81, 100}

ii) Create a set of unique vowels in a string

string = 'Hello, World!'
vowels_set = {char.lower() for char in string if char.lower() in 'aeiou'}
print(vowels_set)  

# Output: 
{'o', 'e'}

iii) Create a dictionary mapping number to its square

squares_dict = {num: num ** 2 for num in range(1, 11)}
print(squares_dict)  

# Output: 
{1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81, 10: 100}

iv) Create a dictionary of word frequencies in a list of words

word_list = ['apple', 'banana', 'cherry', 'apple', 'banana', 'apple']
word_freq_dict = {word: word_list.count(word) for word in set(word_list)}
print(word_freq_dict)  

# Output: 
{'cherry': 1, 'banana': 2, 'apple': 3}

These are some examples of set and dictionary comprehensions.

3. Default Dictionary

What is Default Dict?

defaultdict is a subclass of the built-in dict class in Python that provides a default value for missing keys. When you access a missing key in a defaultdict, instead of raising a KeyError as a regular dictionary would, a new key-value pair is automatically created with a default value determined by a default factory function.

The default factory function is specified when creating a defaultdict object, and it is called with no arguments to provide a default value whenever a missing key is accessed. The default factory can be a built-in function (such as int, list, or set) or any other callable object that returns a default value.

e.g

from collections import defaultdict

int_dict = defaultdict(int)

int_dict['a'] += 1  
# Above line doesn't raise a KeyError, instead it adds 1 to the default value 0
print(int_dict)  

# Output: 
defaultdict(<class 'int'>, {'a': 1})

i) Creating a default dictionary with integer values

from collections import defaultdict

# Create a defaultdict with integer values
int_dict = defaultdict(int)

# Add some values to the dictionary
int_dict['a'] = 1
int_dict['b'] = 2
int_dict['c'] += 1  # This line doesn't raise a KeyError, instead it adds 1 to the default value 0

print(int_dict)  
# Output: 
defaultdict(<class 'int'>, {'a': 1, 'b': 2, 'c': 1})

ii) Creating a default dictionary with list values

from collections import defaultdict

# Create a defaultdict with list values
list_dict = defaultdict(list)

# Add some values to the dictionary
list_dict['a'].append(1)
list_dict['b'].extend([2, 3])
list_dict['c'].append(4)

print(list_dict)  
# Output: 
defaultdict(<class 'list'>, {'a': [1], 'b': [2, 3], 'c': [4]})

iii) Creating a default dictionary with a lambda function as the default factory

from collections import defaultdict

# Create a defaultdict with a lambda function as the default factory
lambda_dict = defaultdict(lambda: 'default value')

# Add some values to the dictionary
lambda_dict['a'] = 'apple'
lambda_dict['b'] = 'banana'

print(lambda_dict['c'])  
# Output: 
'default value'
  1. Iterator patterns

Iterators are methods that iterate collections like lists, tuples, etc. Using an iterator method, we can loop through an object and return its elements.

In Python, you can create an iterator pattern by defining a class that implements the __iter__() and __next__() methods.

The __iter__() method returns the iterator object itself.

the __next__() method returns the next value from the iterator or raises the StopIteration exception if there are no more values.

Let's create a Fibonacci Iterator.

class FibIterator:
    def __init__(self, n):
        self.n = n
        self.curr = 0
        self.next = 1
        self.count = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.count >= self.n:
            raise StopIteration
        result = self.curr
        self.curr , self.next = self.next, self.curr + self.next
        self.count += 1
        return result

if __name__ == "__main__":
    fib = FibIterator(10)

    for num in fib:
        print(num)

#Output
0
1
1
2
3
5
8
13
21
34

the above example is a custom iterator implementation, but you can make any iterable (list, tuples, dict) to an ieterator by using built-in iter() function.

e.g

my_list = [1, 2, 3, 4, 5]
my_iter = iter(my_list)
print(next(my_iter)) # Output: 1
print(next(my_iter)) # Output: 2

Or, Using iter() with a custom iterable class

class MyIterable:
    def __init__(self, data):
        self.data = data

    def __iter__(self):
        return iter(self.data)

my_iterable = MyIterable([1, 2, 3, 4, 5])
my_iter = iter(my_iterable)
print(next(my_iter)) # Output: 1
print(next(my_iter)) # Output: 2

We define a custom iterable class MyIterable that takes a list of data as input. The __iter__() method returns an iterator object created using the iter() function on self.data. We create an instance of MyIterable with a list of numbers, and then use the iter() function to create an iterator object my_iter that we can use to iterate over the elements of my_iterable.

Or, Using iter() with a generator function

def my_generator(start, end):
    current = start
    while current <= end:
        yield current
        current += 1

my_iter = iter(my_generator(1, 5))
print(next(my_iter)) # Output: 1
print(next(my_iter)) # Output: 2

we define a generator function my_generator() that generates a sequence of numbers from start to end. We use the yield keyword to define each element of the sequence and the while loop to generate the sequence. We then use the iter() function to create an iterator object my_iter from the generator function and use the next() function to iterate over the elements of the sequence.

Conclusion

In this article, we learn about how to write clean concise ways of writing comprehension. You can apply those methods or create your comprehension to the various development area such as Data analysis, model development, and web development. Thank you for reading my article.

Follow me on Avijit Biswas and on Twitter @avizyt