Chapter 1 - Lists and dictionaries¶
Goal
- Get comfortable with Python lists and dictionaries
- Learn to manipulate them to your will without having to google for common operations
- Practice till it becomes second nature
Introduction¶
As a hardware engineer, most of the Python code I write centers around two data structures
- Python List
[]
- which can be compared to SystemVerilog Queues - Python Dictionary
{}
- which is equivalent to SystemVerilog Associative Arrays
Practice
Try out the examples in this tutorial either by launching python3
on your Linux or MacOS terminal, or using this Python web interpreter.
Python list¶
Interacting with lists¶
In any programming language (C, SystemVerilog, Perl, etc), we typically interact with arrays in a few common ways
- Store stuff in them
- Push and pop items from them
- Slice arrays to select a portion of it, or combine arrays to make a larger one
- Check how many items are there in it
Here are some of these actions in Python compared against SystemVerilog queues.
Action | SystemVerilog | Python | Comment |
---|---|---|---|
initialize | int k[$] = {1, 2, 3} |
k = [1, 2, 3] |
|
access element | k[0], k[1] |
k[0], k[1] k[-1], k[-2] |
In Python you can access elements from the end using negative indices |
push, insert | k.push_front(100) k.push_back(101) k.insert(2, 20) |
k.insert(0, 100) k.insert(-1, 101) k.append(101) |
Notice how you can address the last index using -1 |
pop | k.pop_front() k.pop_back() |
k.pop(0) k.pop(-1) |
|
size | k.size() |
len(k) |
|
slicing | k[0:3] k[4:$] |
k[:3] k[4:] |
Omitting first element starts index from 0 Omitting second index indicates end of list |
clear | k.delete() |
k.clear() |
|
copy | m = k |
m = k[:] m = k.copy() m = list(k) |
Three different ways to copy lists |
exists | Iterate and check usingforeach k[i] |
if 2 in k: return True |
(<elem> in <list>) will return True if element exists, else False |
More things you can do with lists
¶
Instead of having to iterate over a list in a for
loop, Python offers convenient functions to perform the following actions.
reverse
sum
- find
min
andmax
elements sort
- concatenate lists
count
occurencejoin
# Reverse a list in place. This modifies the list.
>>> k = [1, 2, 3, 4]
>>> k.reverse()
>>> k
[4, 3, 2, 1]
# Sum all elements of the list
>>> sum([10, 20, 30, 40, 50])
150
# Sum all elements but add a base value to it
>>> sum([10, 20, 30, 40, 50], start=1000)
1150
# Finding min and max elements
>>> min([10, 20, 30, 40, 50])
10
>>> max([10, 20, 30, 40, 50])
50
# Concatenate lists
>>> k.extend([5, 6, 7])
>>> k
[1, 2, 3, 4, 5, 6, 7]
>>> m = [10, 11, 12]
>>> k + m
[1, 2, 3, 4, 5, 6, 7, 10, 11, 12]
# Sort list
>>> k.sort() #ascending
>>> k.sort(reverse=True) #descending
# Join
'/'.join(['apple', 'banana', 'pear'])
'+'.join(['apple', 'banana', 'pear'])
' '.join(['apple', 'banana', 'pear'])
Python Dictionary¶
Interacting with dicts¶
Dictionaries (dict
for short), similar to SV associative arrays, are used to store [key][value]
pairs. Here are some common ways to interact with dicts.
# Initialize
>>> d = {'apple': 'fruit', 'horse': 'animal', 'orange': 'fruit', 'dog': 'animal'}
# Insert
>>> d['mango'] = 'fruit'
# Update
# I'm changing the value of 'dog' and adding 2 new keys
>>> d.update({'dog': 'pet', 'banana': 'fruit', 'kiwi': 'fruit'})
# Delete
>>> del d['orange']
# Check if key exists
>>> 'dog' in d
True
>>> 'cat' in d
False
Using the get
method¶
In Python, you have two ways to fetch an entry from a dict
- Simply access it
d['orange']
- Or retrieve using the
.get()
method
If you access a key that does not exists (such as k['cat']
in our example), a KeyError
exception is raised. This can sometimes be problematic, because you don't want your script to error out. Instead, using k.get('cat')
to retrieve an entry will cleanly return None
and not cause errors.
.get
default
Also, dict.get(<key>, <default>)
takes an optional second argument. Instead of having .get()
return None
, you can make it return a different value if a key does not exist.
dict.get(<key>, <default>)
>>> d['cat']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'cat'
# No exception thrown
>>> d.get('cat')
# Get a different default value instead of None,
# in case the key does not exist
>>> d.get('cat', 'animal')
animal
Iterating over dicts¶
Iterating over dicts is a little different from lists
- Simply doing
for item in d:
will only fetch the keys for key, value in d.items()
will fetch both, key and value, for each entry in the dict.
>>> d = {'apple': 'fruit', 'horse': 'animal', 'orange': 'fruit', 'dog': 'animal'}
>>> for item in d:
... print(item)
...
apple
horse
orange
dog
>>> for k, v in d.items():
... print(k, '=>', v)
...
apple => fruit
horse => animal
orange => fruit
dog => animal
Making copies¶
Shallow copy¶
- In SV you can copy a queue by making a simple assignment
m=k
. - But in Python this assignment
m=k
will makem
point to the same memory location ask
, so modifyingm
will also modifyk
# Wrong way to copy a list
>>> k = [1, 2, 3]
>>> m = k
>>> m.append(4)
>>> print(k)
[1, 2, 3, 4]
# Correct way to copy a list
>>> m = k[:]
>>> m = k.copy()
>>> m = list(k)
# Wrong way to copy a dictionary
>>> d = {'name': 'john', 'age': '40'}
>>> s = d
>>> s['age'] = 100
>>> d
{'name': 'john', 'age': 100}
# Correct way to copy a dictionary
>>> s = d.copy()
>>> d = dict(d)
Deep Copy¶
- If you have a list within a list such as a 2D array, or a list within a dict, then a simple
.copy()
will only perform a shallow copy. - To clone such a multi-dimensional structure, you will have to use
copy.deepcopy()
- Here we access the
deepcopy()
utility present in thecopy
library by doing aimport copy
. - The
import copy
statement is similar toimport pkg;
in SV or#include <stdio.h>
in C. (More on this in a later chapter). - In this example, lets consider a 2D matrix
k
and a dictd
with a list inside it and examine how they are affected by shallow copy versus deep copy.
# 2D matrix
k = [[1, 2], [3, 4]]
# List within a dict
d = {'name': 'john', 'num': [1, 2, 3]}
# Shallow copy of a 2D list
>>> m = k[:]
>>> m[0][0] = 1000
>>> k # changing `m` also changes `k`
# Deep copy of a 2D list
>>> import copy
>>> m = copy.deepcopy(k)
>>> m[0][0] = 1000
>>> k # changing `m` does NOT change `k`
# Shallow copy of dict
>>> s = d.copy()
>>> s['num'][0] = 1000
>>> d # changing `s` also changes `d`
# Deep copy of dict
>>> import copy
>>> s = copy.deepcopy(d)
>>> s['num'][0] = 1000
>>> d # changing `s` does NOT change `d`
List and Dictionary Comprehension¶
One of the actions tht I perform frequently is iterating over lists and dicts to create new lists or dicts. The obvious way to do this is using a for
loop. But, Python offers a more convenient and terse way to do the same. Here are some examples.
# Given a list of numbers add 100 to each of them
>>> k = [1, 2, 3, 4, 5]
>>> m = [i+100 for i in k]
>>> m
[101, 102, 103, 104, 105]
# Filter out names that start with the letter `m`
>>> names = ['tony', 'mike', 'jack', 'bryan', 'mark', 'matthew', 'nancy', 'miles']
>>> m = [n for n in names if n[0] == 'm']
>>> m
['mike', 'mark', 'matthew', 'miles']
# Add 100 to values of each key in the dict
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> m = {k:v+100 for k,v in d.items()}
>>> m
{'a': 101, 'b': 102, 'c': 103}
# Add a suffix `_name` to all the keys in the dict
>>> m = {k+'_name':v for k,v in d.items()}
>>> m
{'a_name': 1, 'b_name': 2, 'c_name': 3}
List/Dict Comprehension
If you find yourself writing for
loops to iterate over lists or dict, try to see if you can refactor your code and rewrite it using list/dict comprehensions
Practice, Practice, Practice¶
- The key to learning Python is practice
- Set 15 minutes on a timer, fire up
python3
on your Linux or MacOS terminal (or use this Python web interpreter) and practice some of the code in this chapter.