Skip to content

Commit 3ba1d46

Browse files
author
iram lee
committed
complete section 4.2
1 parent 3ac4c88 commit 3ba1d46

5 files changed

Lines changed: 181 additions & 93 deletions

File tree

‎Work/fileparse.py‎

Lines changed: 39 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,55 +3,54 @@
33
# Exercise 3.3
44
import csv
55

6-
def parse_csv(filename, select=None, types=None, has_headers=True, delimiter=',',
7-
silence_errors=False):
6+
7+
def parse_csv(file_lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):
88
""" Parse a CSV file into a list of records
99
"""
1010
if select and not has_headers:
1111
raise RuntimeError('select argument requires column headers')
1212

13-
with open(filename) as f:
14-
rows = csv.reader(f, delimiter=delimiter)
13+
lines = csv.reader(file_lines, delimiter=delimiter)
1514

16-
#Read the file headers
17-
if has_headers:
18-
headers = next(rows)
15+
#Read the file headers
16+
if has_headers:
17+
headers = next(lines)
1918

2019
# If a column selector was given, find indices of the specified columns.
2120
# Also narrow the set of headers used for resulting dictionaries
22-
if select:
23-
indices = [headers.index(colname) for colname in select]
24-
headers = select
25-
else:
26-
indices = []
27-
28-
records = []
29-
rownum = 0
30-
for row in rows:
31-
rownum += 1
32-
if not row: #Skip rows with no data
33-
continue
21+
if select:
22+
indices = [headers.index(colname) for colname in select]
23+
headers = select
24+
else:
25+
indices = []
26+
27+
records = []
28+
rownum = 0
29+
for line in lines:
30+
rownum += 1
31+
if not line: #skip lines with no data
32+
continue
3433

35-
try:
36-
# Filter the row if specific columns were selected
37-
if indices:
38-
row = [ row[index] for index in indices]
39-
# Perform type conversion
40-
if types:
41-
row = [func(val) for func, val in zip(types, row)]
42-
43-
if has_headers:
44-
# Make a dictionary
45-
record = dict(zip(headers, row))
46-
else:
47-
# Make a tuple
48-
record = tuple(row)
49-
50-
records.append(record)
51-
except ValueError as e:
52-
if not silence_errors:
53-
print(f'Row {rownum}: Couldn\'t convert {row}')
54-
print(f'Row {rownum}: Reason {e}')
34+
try:
35+
# filter the line if specific columns were selected
36+
if indices:
37+
line = [ line[index] for index in indices]
38+
# perform type conversion
39+
if types:
40+
line = [func(val) for func, val in zip(types, line)]
41+
42+
if has_headers:
43+
# make a dictionary
44+
record = dict(zip(headers, line))
45+
else:
46+
# make a tuple
47+
record = tuple(line)
48+
49+
records.append(record)
50+
except ValueError as e:
51+
if not silence_errors:
52+
print(f'line {rownum}: couldn\'t convert {line}')
53+
print(f'line {rownum}: reason {e}')
5554
continue
5655

57-
return records
56+
return records

‎Work/pcost.py‎

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,38 @@
1+
#!/usr/bin/env python3
2+
13
# pcost.py
24
#
35
# Exercise 1.27
46
import sys
5-
import csv
7+
import report
68

79
def portfolio_cost(filename):
810
total_cost = 0.0
911
stock_price = 0.0
1012
num_of_shares = 0
11-
f = open(filename, 'rt')
12-
lines = csv.reader(f)
13-
headers = next(lines)
13+
records = report.read_portfolio(filename)
1414

15-
for lineno, line in enumerate(lines, start=1):
16-
record = dict(zip(headers, line))
15+
for lineno, record in enumerate(records, start=1):
1716
try:
18-
nshares = int(record['shares'])
19-
price = float(record['price'])
17+
nshares = int(record.shares)
18+
price = float(record.price)
2019
total_cost += nshares * price
2120
# This catches errors in int() and float() conversions above
2221
except ValueError:
2322
print(f'Row {lineno}: Bad row: {line}')
2423

25-
f.close()
2624
return total_cost
2725

26+
def main(argv):
27+
if len(argv) == 2:
28+
filename = argv[1]
29+
else:
30+
filename = 'Data/portfolio.csv'
2831

29-
if len(sys.argv) == 2:
30-
filename = sys.argv[1]
31-
else:
32-
filename = 'Data/portfoliodate.csv'
33-
34-
cost = portfolio_cost(filename)
35-
print('Total cost:', cost)
32+
cost = portfolio_cost(filename)
33+
print('Total cost:', cost)
34+
35+
36+
if __name__ == '__main__':
37+
import sys
38+
main(sys.argv)

‎Work/report.py‎

Lines changed: 42 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,70 @@
1+
#!/usr/bin/env python3
2+
13
# report.py
24
#
35
# Exercise 2.4
46
import csv
7+
import fileparse
8+
import stock
9+
import tableformat
510

611
def read_portfolio(filename):
7-
portfolio = []
8-
9-
with open(filename, 'rt') as f:
10-
rows = csv.reader(f)
11-
headers = next(rows)
12-
for row in rows:
13-
record = dict(zip(headers, row))
14-
holding = record
15-
portfolio.append(holding)
16-
return portfolio
12+
with open(filename) as f:
13+
portdicts = fileparse.parse_csv(f, select=['name', 'shares', 'price'], types=[str, int, float])
14+
portfolio = [ stock.Stock(d['name'], d['shares'], d['price']) for d in portdicts ]
15+
return portfolio
1716

1817

1918
def read_prices(filename):
20-
prices = {}
21-
22-
f = open(filename, 'rt')
23-
lines = csv.reader(f)
24-
for line in lines:
25-
try:
26-
prices[line[0]] = float(line[1])
27-
except IndexError:
28-
pass
29-
f.close()
30-
return prices
19+
with open(filename) as f:
20+
return dict(fileparse.parse_csv(f, types=[str, float], has_headers=False))
3121

3222

3323
def make_report(portfolio, prices):
3424
updated_portfolio = []
3525

3626
for holding in portfolio:
37-
name = holding['name']
38-
shares = int(holding['shares'])
27+
name = holding.name
28+
shares = int(holding.shares)
3929
price = prices[name]
40-
change = price - float(holding['price'])
30+
change = price - float(holding.price)
4131
updated_portfolio.append((name, shares, price, change))
4232

4333
return updated_portfolio
4434

4535

46-
def print_report(report):
47-
headers = ('Name', 'Shares', 'Price', 'Change')
48-
separator = '----------'
49-
print(f'{headers[0]:>10s} {headers[1]:>10s} {headers[2]:>10s} {headers[3]:>10s}')
50-
print(f'{separator:>10s} {separator:>10s} {separator:>10s} {separator:>10s}')
36+
def print_report(reportdata, formatter):
37+
formatter.headings(['Name','Shares','Price','Change'])
38+
# headers = ('Name', 'Shares', 'Price', 'Change')
39+
# separator = '----------'
40+
# print(f'{headers[0]:>10s} {headers[1]:>10s} {headers[2]:>10s} {headers[3]:>10s}')
41+
# print(f'{separator:>10s} {separator:>10s} {separator:>10s} {separator:>10s}')
42+
for name, shares, price, change in reportdata:
43+
rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}']
44+
formatter.row(rowdata)
45+
# currency_sym = '$'
46+
# for name, shares, price, change in report:
47+
# money = f'{currency_sym}{price:0.2f}'
48+
# print(f'{name:>10s} {shares:>10d} {price:>10.2f} {change:>10.2f}')
5149

52-
currency_sym = '$'
53-
for name, shares, price, change in report:
54-
money = f'{currency_sym}{price:0.2f}'
55-
print(f'{name:>10s} {shares:>10d} {money:>10s} {change:>10.2f}')
5650

57-
58-
def portfolio_report(portfolio_filename, prices_filename):
51+
def portfolio_report(portfolio_filename, prices_filename, fmt='txt'):
52+
# Read data files
5953
portfolio = read_portfolio(portfolio_filename)
6054
prices = read_prices(prices_filename)
55+
56+
# Create the report data
6157
report = make_report(portfolio, prices)
62-
print_report(report)
6358

6459

65-
portfolio_report('Data/portfolio.csv', 'Data/prices.csv')
60+
formatter = tableformat.create_formatter(fmt)
61+
print_report(report, formatter)
62+
63+
64+
def main(argv):
65+
portfolio_report(argv[1], argv[2], argv[3])
66+
67+
68+
if __name__ == '__main__':
69+
import sys
70+
main(sys.argv)

‎Work/stock.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
class Stock:
2+
3+
def __init__(self, name, shares, price):
4+
self.name = name
5+
self.shares = shares
6+
self.price = price
7+
8+
9+
def sell(self, qty):
10+
self.shares -= qty
11+
12+
13+
def cost(self):
14+
return self.shares * self.price

‎Work/tableformat.py‎

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# tableformat.py
2+
3+
class TableFormatter:
4+
def headings(self, headers):
5+
''' Emit the table headings.
6+
'''
7+
raise NotImplementedError()
8+
9+
10+
def row(self, rowdata):
11+
''' Emit a single row of table data.
12+
'''
13+
raise NotImplementedError()
14+
15+
16+
class TextTableFormatter(TableFormatter):
17+
''' Emit a table in plain-text format
18+
'''
19+
def headings(self, headers):
20+
for h in headers:
21+
print(f'{h:>10s}', end=' ')
22+
print()
23+
print(('-'*10 + ' ')*len(headers))
24+
25+
26+
def row(self, rowdata):
27+
for d in rowdata:
28+
print(f'{d:>10s}', end=' ')
29+
print()
30+
31+
32+
33+
class CSVTableFormatter(TableFormatter):
34+
'''
35+
Output portfolio data in CSV format
36+
'''
37+
def headings(self, headers):
38+
print(','.join(headers))
39+
40+
41+
def row(self, rowdata):
42+
print(','.join(rowdata))
43+
44+
45+
class HTMLTableFormatter(TableFormatter):
46+
'''
47+
Output portfolio data in HTML format
48+
'''
49+
def headings(self, headers):
50+
header_row = '</th><th>'.join(headers)
51+
print(f'<tr><th>{header_row}</th></tr>')
52+
53+
54+
def row(self, rowdata):
55+
single_row = '</td><td>'.join(rowdata)
56+
print(f'<tr><td>{single_row}</td></tr>')
57+
58+
59+
def create_formatter(name):
60+
if name == 'txt':
61+
return TextTableFormatter()
62+
elif name == 'csv':
63+
return CSVTableFormatter()
64+
elif name == 'html':
65+
return HTMLTableFormatter()
66+
else:
67+
raise RuntimeError(f'Unknown format {fmt}')

0 commit comments

Comments
 (0)