Photo by David Werbrouck on Unsplash
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.
List Comprehension
Set and Dict comprehension
Default Dict
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'
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