March 27-31, 2017
Anand Chitipothu
These notes are available online at https://notes.pipal.in/2017/symantec
© Pipal Academy LLP
d = {"x": 1, "y": 2, "z": 3}
print(d)
Dictionaries are unordered collections.
d['x']
d['y']
d['x'] = 11
print(d)
Q: Should the key be enclosed in quotes?
Need not be.
d = {"x": 1, "y": 2, "z": 3}
d['x']
key = "x"
d[key]
The keys of dictionary need not be strings.
records = {1: "x", 2: "y", 99: "z"}
records[1]
records[99]
In fact, keys can be tuples too.
d = {}
d["a", 1] = 123
d
Let us look at on more simple example.
person = {"name": "Alice", "email": "alice@example.com"}
print(person)
"name" in person
"email" in person
"phone" in person
"phone" not in person
The dictionaries provide a useful method get.
person.get("name")
person.get("name", "default-value")
person.get("phone", "not provided")
host1 = {"name": "mail-1", "ip": "1.2.3.4", "status": "up"}
host2 = {"name": "mail-2", "ip": "1.2.3.5", "status": "down"}
hosts = [host1, host2]
hosts
host3 = {"name": "web-1", "ip": "1.2.3.100"}
hosts.append(host3)
hosts
The status of the last host is not known.
for h in hosts:
print("{} {} {}".format(h["name"], h["ip"], h.get("status", "unknown")))
for h in hosts:
print("{:10s} {:10s} {}".format(h["name"], h["ip"], h.get("status", "unknown")))
The keys, values and items of a dictionary can be accesed using keys(), values() and items() methods.
d = {"x": 1, "y": 2, "z": 3}
d.keys()
d.values()
d.items()
for k in d.keys():
print(k)
for v in d.values():
print(v)
for k, v in d.items():
print(k, v)
If you ever want to delete a key from a dictionary, you can use the del statement.
d
del d['x']
d
Also, we can iterate over the dictionary directly, which will go over the keys.
for k in d:
print(k)
We can use the update method to combine two dictionaries.
d1 = {"x": 1, "y": 2}
d2 = {"y": 20, "z": 30}
d1.update(d2)
d1
marks = {
"english": 89,
"maths": 78,
"science": 68
}
marks
for k in marks:
print(k, marks[k])
for k, v in marks.items():
print(k, v)
for subject, score in marks.items():
print(subject, score)
print("-----------")
print("total", sum(marks.values()))
Let us write a program to compute the number of occurances of each word in the given file.
%%file words.txt
five
five four
five four three
five four three two
five four three two one
%%file wordfreq.py
"""Program to compute the number of occurances of each word in the given file.
USAGE: python wordfreq.py filename.txt
"""
import sys
def read_words(filename):
"""Returns all words in the given file.
"""
return open(filename).read().split()
def wordfreq(words):
"""Computes the frequency of each word in the given words.
>>> wordfreq(["a", "b", "a])
{'a': 2, 'b': 1}
"""
freq = {}
for w in words:
# if w in freq:
# freq[w] = freq[w] + 1
# else:
# freq[w] = 1
freq[w] = freq.get(w, 0) + 1
return freq
def print_freq(freq):
# TODO: improve this
print(freq)
def main():
filename = sys.argv[1]
words = read_words(filename)
freq = wordfreq(words)
print_freq(freq)
if __name__ == "__main__":
main()
!python wordfreq.py words.txt
%%file test_wordfreq.py
from wordfreq import wordfreq
def test_wordfreq():
assert wordfreq([]) == {}
assert wordfreq(["a"]) == {"a": 1}
assert wordfreq(["a", "a"]) == {"a": 2}
assert wordfreq(["a", "b", "a"]) == {"a": 2, "b": 1}
!py.test test_wordfreq.py
Problem: Improve the above program to print one word per line, like shown below (order is not important).
five 5
three 3
two 2
four 4
one 1
Problem: Improve the program further to print the words sorted by count, with most common words on the top.
five 5
four 4
three 3
two 2
one 1
freq = {'five': 5, 'four': 4, 'two': 2, 'one': 1, 'three': 3}
for w, count in freq.items():
print(w, count)
# let us try sorting it...
for w, count in sorted(freq.items()):
print(w, count)
sorted(freq.items())
def get_value(item):
# FIX ME
print(item)
return item[1]
sorted(freq.items(), key=get_value)
sorted(freq, key=freq.get)
def get_value_of_key(key):
return freq[key]
sorted(freq, key=get_value_of_key)
words = ["alice", "bob", "charlie", "dave"]
{w: len(w) for w in words}
This is similar to a list comprehension.
[(w, len(w)) for w in words]
Let us write a program to parse /etc/hosts file format.
%%file hosts.txt
127.0.0.1 localhost
1.2.3.4 myhost www.myhost
1.2.3.5 foo bar
We want the program to give back a mapping from hostname to ip address.
%%file hosts.py
"""Module to parse /etc/hosts file format.
"""
def parse(filename):
"""Parses the given file and returns a dictionary mapping
the hostnames to ip addresses.
"""
h = {} # hostname -> ip
lines = open(filename).readlines()
for line in lines:
h.update(parse_line(line))
return h
def parse_line(line):
"""Parses a single line of /etc/hosts file.
>>> parse_line("1.2.3.4 myhost www.myhost")
{"myhost": "1.2.3.4", "www.myhost": "1.2.3.4"}
"""
line = line.strip()
if not line or line.startswith("#"):
return {}
parts = line.split()
ip = parts[0]
hostnames = parts[1:]
return {h: ip for h in hostnames}
%%file test_hosts.py
from hosts import parse_line
def test_parse_line():
assert parse_line("") == {}
assert parse_line("#comment") == {}
assert parse_line("1.2.3.4 x") == {"x": "1.2.3.4"}
assert parse_line("1.2.3.4 x y") == {"x": "1.2.3.4", "y": "1.2.3.4"}
!py.test test_hosts.py
import hosts
h = hosts.parse("hosts.txt")
print(h)
h['myhost']
How to write a hosts file given the host to IP mapping?
ipdict = {} # mapping from ip to hosts
for host, ip in h.items():
# make sure there is an entry in the ipdict for this ip address
ipdict[ip] = ipdict.get(ip, [])
ipdict[ip].append(host)
ipdict
ipdict = {} # mapping from ip to hosts
for host, ip in h.items():
# make sure there is an entry in the ipdict for this ip address
ipdict.setdefault(ip, [])
ipdict[ip].append(host)
print(ipdict)
ipdict = {} # mapping from ip to hosts
for host, ip in h.items():
ipdict.setdefault(ip, []).append(host)
print(ipdict)
Now that we have ip to hostnames mapping, how to convert this into a string?
def tostring(ip, hostnames):
return ip + " " + " ".join(hostnames)
tostring('1.2.3.4', ['www.myhost', 'myhost'])
lines = [tostring(ip, hostnames) for ip, hostnames in ipdict.items()]
lines
print("\n".join(lines))
print("\n".join([ip + " " + " ".join(hostnames) for ip, hostnames in ipdict.items()]))
class Point:
def __init__(self, x1, y1):
self.x = x1
self.y = y1
p = Point(3, 4)
print(p.x, p.y)
Calling Point(3,4) creates a new instance of Point class. It does the following things.
- creates an empty object of type Point
- initializes that by calling the __init__ method
- return back the created object
isinstance(p, Point)
type(p)
isinstance(1, int)
isinstance(1, str)
Now let us see how to write methods.
class Point:
def __init__(self, x1, y1):
self.x = x1
self.y = y1
def getx(self):
return self.x
def display(self):
print(self.x, self.y)
def add(self, p):
x = self.x + p.x
y = self.y + p.y
return Point(x, y)
p1 = Point(2, 3)
p2 = Point(10, 20)
p1.display()
p2.display()
p1.getx()
Point.getx(p1)
p3 = p1.add(p2)
p3.display()
Problem: Add a method double to the Point class. It should return a new point with both x and y coordinates doubles.
>>> p = Point(2, 3)
>>> p2 = p.double()
>>> p2.display()
4 6
class Point:
def __init__(self, x1, y1):
self.x = x1
self.y = y1
def getx(self):
return self.x
def display(self):
print(self.x, self.y)
def add(self, p):
x = self.x + p.x
y = self.y + p.y
return Point(x, y)
def double(self):
x = 2 * self.x
y = 2 * self.y
return Point(x, y)
def double2(self):
return self.add(self)
p = Point(1, 2)
p2 = p.double()
p2.display()
p2 = p.double2()
p2.display()
__str__ and __repr__ methods¶p = Point(1, 2)
print(p)
class Point:
def __init__(self, x1, y1):
self.x = x1
self.y = y1
def __str__(self):
return "({}, {})".format(self.x, self.y)
def __repr__(self):
return "Point({}, {})".format(self.x, self.y)
p = Point(1, 2)
print(p)
print([p])
Python has two different ways to display an object.
print(1, "1")
print("hello", 1)
There is something else called representation of an object.
print([1, "1"])
print(str("1"))
print(repr("1"))
print(repr(p))
[(1,2), p]
Let us try to model a bank account.
%%file bank0.py
balance = 0
def deposit(amount):
global balance
balance = balance + amount
def withdraw(amount):
global balance
balance = balance - amount
def get_balance():
return balance
def main():
deposit(100)
withdraw(20)
print(get_balance())
withdraw(40)
print(get_balance())
if __name__ == "__main__":
main()
!python bank0.py
There is one big limitation of this program. It supports only one bank account. It is not possible to have more accounts.
Let us try to address that.
%%file bank1.py
def make_account():
return {"balance": 0}
def deposit(account, amount):
account["balance"] += amount
def withdraw(account, amount):
account["balance"] -= amount
def get_balance(account):
return account["balance"]
def main():
a1 = make_account()
a2 = make_account()
deposit(a1, 100)
deposit(a2, 50)
print(get_balance(a1), get_balance(a2))
withdraw(a1, 40)
withdraw(a2, 20)
print(get_balance(a1), get_balance(a2))
if __name__ == "__main__":
main()
!python bank1.py
Let us try to model this using classes.
%%file bank2.py
class BankAccount:
def __init__(self):
self.balance = 0
def deposit(self, amount):
self.balance += amount
def withdraw(self, amount):
self.balance -= amount
def get_balance(self):
return self.balance
def main():
a1 = BankAccount()
a2 = BankAccount()
a1.deposit(100)
a2.deposit(50)
print(a1.get_balance(), a2.get_balance())
a1.withdraw(40)
a2.withdraw(20)
print(a1.get_balance(), a2.get_balance())
if __name__ == "__main__":
main()
!python bank2.py
In fact, objects are glorified dictionaries.
from bank2 import BankAccount
a1 = BankAccount()
a1.deposit(100)
a2 = BankAccount()
a2.deposit(50)
a1.__dict__
a2.__dict__