Added solution code

This commit is contained in:
David Beazley
2020-05-27 17:03:35 -05:00
parent 960d4fa2fa
commit 5b6f15db17
136 changed files with 5828 additions and 350 deletions

View File

@@ -0,0 +1,28 @@
# mortgage.py
principal = 500000.0
rate = 0.05
payment = 2684.11
total_paid = 0.0
month = 0
extra_payment = 1000.0
extra_payment_start_month = 60
extra_payment_end_month = 108
while principal > 0:
month = month + 1
principal = principal * (1+rate/12) - payment
total_paid = total_paid + payment
if month >= extra_payment_start_month and month <= extra_payment_end_month:
principal = principal - extra_payment
total_paid = total_paid + extra_payment
print(month, round(total_paid,2), round(principal, 2))
print('Total paid', round(total_paid, 2))
print('Months', month)

13
Solutions/1_27/pcost.py Normal file
View File

@@ -0,0 +1,13 @@
# pcost.py
total_cost = 0.0
with open('../../Work/Data/portfolio.csv', 'rt') as f:
headers = next(f)
for line in f:
row = line.split(',')
nshares = int(row[1])
price = float(row[2])
total_cost += nshares * price
print('Total cost', total_cost)

31
Solutions/1_32/pcost.py Normal file
View File

@@ -0,0 +1,31 @@
# pcost.py
import csv
def portfolio_cost(filename):
'''
Computes the total cost (shares*price) of a portfolio file
'''
total_cost = 0.0
with open(filename, 'rt') as f:
rows = csv.reader(f)
headers = next(rows)
for row in rows:
try:
nshares = int(row[1])
price = float(row[2])
total_cost += nshares * price
# This catches errors in int() and float() conversions above
except ValueError:
print('Bad row:', row)
return total_cost
import sys
if len(sys.argv) == 2:
filename = sys.argv[1]
else:
filename = input('Enter a filename:')
cost = portfolio_cost(filename)
print('Total cost:', cost)

8
Solutions/1_5/bounce.py Normal file
View File

@@ -0,0 +1,8 @@
# bounce.py
height = 100
bounce = 1
while bounce <= 10:
height = height * (3/5)
print(bounce, round(height, 4))
bounce += 1

66
Solutions/2_11/report.py Normal file
View File

@@ -0,0 +1,66 @@
# report.py
import csv
def read_portfolio(filename):
'''
Read a stock portfolio file into a list of dictionaries with keys
name, shares, and price.
'''
portfolio = []
with open(filename) as f:
rows = csv.reader(f)
headers = next(rows)
for row in rows:
stock = {
'name' : row[0],
'shares' : int(row[1]),
'price' : float(row[2])
}
portfolio.append(stock)
return portfolio
def read_prices(filename):
'''
Read a CSV file of price data into a dict mapping names to prices.
'''
prices = {}
with open(filename) as f:
rows = csv.reader(f)
for row in rows:
try:
prices[row[0]] = float(row[1])
except IndexError:
pass
return prices
def make_report_data(portfolio, prices):
'''
Make a list of (name, shares, price, change) tuples given a portfolio list
and prices dictionary.
'''
rows = []
for stock in portfolio:
current_price = prices[stock['name']]
change = current_price - stock['price']
summary = (stock['name'], stock['shares'], current_price, change)
rows.append(summary)
return rows
# Read data files and create the report data
portfolio = read_portfolio('../../Work/Data/portfolio.csv')
prices = read_prices('../../Work/Data/prices.csv')
# Generate the report data
report = make_report_data(portfolio, prices)
# Output the report
headers = ('Name', 'Shares', 'Price', 'Change')
print('%10s %10s %10s %10s' % headers)
print(('-' * 10 + ' ') * len(headers))
for row in report:
print('%10s %10d %10.2f %10.2f' % row)

32
Solutions/2_16/pcost.py Normal file
View File

@@ -0,0 +1,32 @@
# pcost.py
import csv
def portfolio_cost(filename):
'''
Computes the total cost (shares*price) of a portfolio file
'''
total_cost = 0.0
with open(filename) as f:
rows = csv.reader(f)
headers = next(rows)
for rowno, row in enumerate(rows, start=1):
record = dict(zip(headers, row))
try:
nshares = int(record['shares'])
price = float(record['price'])
total_cost += nshares * price
# This catches errors in int() and float() conversions above
except ValueError:
print(f'Row {rowno}: Bad row: {row}')
return total_cost
import sys
if len(sys.argv) == 2:
filename = sys.argv[1]
else:
filename = input('Enter a filename:')
cost = portfolio_cost(filename)
print('Total cost:', cost)

67
Solutions/2_16/report.py Normal file
View File

@@ -0,0 +1,67 @@
# report.py
import csv
def read_portfolio(filename):
'''
Read a stock portfolio file into a list of dictionaries with keys
name, shares, and price.
'''
portfolio = []
with open(filename) as f:
rows = csv.reader(f)
headers = next(rows)
for row in rows:
record = dict(zip(headers, row))
stock = {
'name' : record['name'],
'shares' : int(record['shares']),
'price' : float(record['price'])
}
portfolio.append(stock)
return portfolio
def read_prices(filename):
'''
Read a CSV file of price data into a dict mapping names to prices.
'''
prices = {}
with open(filename) as f:
rows = csv.reader(f)
for row in rows:
try:
prices[row[0]] = float(row[1])
except IndexError:
pass
return prices
def make_report_data(portfolio, prices):
'''
Make a list of (name, shares, price, change) tuples given a portfolio list
and prices dictionary.
'''
rows = []
for stock in portfolio:
current_price = prices[stock['name']]
change = current_price - stock['price']
summary = (stock['name'], stock['shares'], current_price, change)
rows.append(summary)
return rows
# Read data files and create the report data
portfolio = read_portfolio('../../Work/Data/portfolio.csv')
prices = read_prices('../../Work/Data/prices.csv')
# Generate the report data
report = make_report_data(portfolio, prices)
# Output the report
headers = ('Name', 'Shares', 'Price', 'Change')
print('%10s %10s %10s %10s' % headers)
print(('-' * 10 + ' ') * len(headers))
for row in report:
print('%10s %10d %10.2f %10.2f' % row)

55
Solutions/2_7/report.py Normal file
View File

@@ -0,0 +1,55 @@
# report.py
import csv
def read_portfolio(filename):
'''
Read a stock portfolio file into a list of dictionaries with keys
name, shares, and price.
'''
portfolio = []
with open(filename) as f:
rows = csv.reader(f)
headers = next(rows)
for row in rows:
stock = {
'name' : row[0],
'shares' : int(row[1]),
'price' : float(row[2])
}
portfolio.append(stock)
return portfolio
def read_prices(filename):
'''
Read a CSV file of price data into a dict mapping names to prices.
'''
prices = {}
with open(filename) as f:
rows = csv.reader(f)
for row in rows:
try:
prices[row[0]] = float(row[1])
except IndexError:
pass
return prices
portfolio = read_portfolio('../../Work/Data/portfolio.csv')
prices = read_prices('../../Work/Data/prices.csv')
# Calculate the total cost of the portfolio
total_cost = 0.0
for s in portfolio:
total_cost += s['shares']*s['price']
print('Total cost', total_cost)
# Compute the current value of the portfolio
total_value = 0.0
for s in portfolio:
total_value += s['shares']*prices[s['name']]
print('Current value', total_value)
print('Gain', total_value - total_cost)

View File

@@ -0,0 +1,48 @@
# fileparse.py
import csv
def parse_csv(filename, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):
'''
Parse a CSV file into a list of records with type conversion.
'''
if select and not has_headers:
raise RuntimeError('select requires column headers')
with open(filename) as f:
rows = csv.reader(f, delimiter=delimiter)
# Read the file headers (if any)
headers = next(rows) if has_headers else []
# If specific columns have been selected, make indices for filtering and set output columns
if select:
indices = [ headers.index(colname) for colname in select ]
headers = select
records = []
for rowno, row in enumerate(rows, 1):
if not row: # Skip rows with no data
continue
# If specific column indices are selected, pick them out
if select:
row = [ row[index] for index in indices]
# Apply type conversion to the row
if types:
try:
row = [func(val) for func, val in zip(types, row)]
except ValueError as e:
if not silence_errors:
print(f"Row {rowno}: Couldn't convert {row}")
print(f"Row {rowno}: Reason {e}")
continue
# Make a dictionary or a tuple
if headers:
record = dict(zip(headers, row))
else:
record = tuple(row)
records.append(record)
return records

View File

@@ -0,0 +1,48 @@
# fileparse.py
import csv
def parse_csv(filename, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):
'''
Parse a CSV file into a list of records with type conversion.
'''
if select and not has_headers:
raise RuntimeError('select requires column headers')
with open(filename) as f:
rows = csv.reader(f, delimiter=delimiter)
# Read the file headers (if any)
headers = next(rows) if has_headers else []
# If specific columns have been selected, make indices for filtering and set output columns
if select:
indices = [ headers.index(colname) for colname in select ]
headers = select
records = []
for rowno, row in enumerate(rows, 1):
if not row: # Skip rows with no data
continue
# If specific column indices are selected, pick them out
if select:
row = [ row[index] for index in indices]
# Apply type conversion to the row
if types:
try:
row = [func(val) for func, val in zip(types, row)]
except ValueError as e:
if not silence_errors:
print(f"Row {rowno}: Couldn't convert {row}")
print(f"Row {rowno}: Reason {e}")
continue
# Make a dictionary or a tuple
if headers:
record = dict(zip(headers, row))
else:
record = tuple(row)
records.append(record)
return records

19
Solutions/3_14/pcost.py Normal file
View File

@@ -0,0 +1,19 @@
# pcost.py
import report
def portfolio_cost(filename):
'''
Computes the total cost (shares*price) of a portfolio file
'''
portfolio = report.read_portfolio(filename)
return sum([s['shares']*s['price'] for s in portfolio])
import sys
if len(sys.argv) == 2:
filename = sys.argv[1]
else:
filename = input('Enter a filename:')
cost = portfolio_cost(filename)
print('Total cost:', cost)

56
Solutions/3_14/report.py Normal file
View File

@@ -0,0 +1,56 @@
# report.py
import fileparse
def read_portfolio(filename):
'''
Read a stock portfolio file into a list of dictionaries with keys
name, shares, and price.
'''
return fileparse.parse_csv(filename, select=['name','shares','price'], types=[str,int,float])
def read_prices(filename):
'''
Read a CSV file of price data into a dict mapping names to prices.
'''
return dict(fileparse.parse_csv(filename,types=[str,float], has_headers=False))
def make_report_data(portfolio,prices):
'''
Make a list of (name, shares, price, change) tuples given a portfolio list
and prices dictionary.
'''
rows = []
for stock in portfolio:
current_price = prices[stock['name']]
change = current_price - stock['price']
summary = (stock['name'], stock['shares'], current_price, change)
rows.append(summary)
return rows
def print_report(reportdata):
'''
Print a nicely formated table from a list of (name, shares, price, change) tuples.
'''
headers = ('Name','Shares','Price','Change')
print('%10s %10s %10s %10s' % headers)
print(('-'*10 + ' ')*len(headers))
for row in reportdata:
print('%10s %10d %10.2f %10.2f' % row)
def portfolio_report(portfoliofile,pricefile):
'''
Make a stock report given portfolio and price data files.
'''
# Read data files
portfolio = read_portfolio(portfoliofile)
prices = read_prices(pricefile)
# Create the report data
report = make_report_data(portfolio, prices)
# Print it out
print_report(report)
portfolio_report('../../Work/Data/portfolio.csv',
'../../Work/Data/prices.csv')

View File

@@ -0,0 +1,48 @@
# fileparse.py
import csv
def parse_csv(filename, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):
'''
Parse a CSV file into a list of records with type conversion.
'''
if select and not has_headers:
raise RuntimeError('select requires column headers')
with open(filename) as f:
rows = csv.reader(f, delimiter=delimiter)
# Read the file headers (if any)
headers = next(rows) if has_headers else []
# If specific columns have been selected, make indices for filtering and set output columns
if select:
indices = [ headers.index(colname) for colname in select ]
headers = select
records = []
for rowno, row in enumerate(rows, 1):
if not row: # Skip rows with no data
continue
# If specific column indices are selected, pick them out
if select:
row = [ row[index] for index in indices]
# Apply type conversion to the row
if types:
try:
row = [func(val) for func, val in zip(types, row)]
except ValueError as e:
if not silence_errors:
print(f"Row {rowno}: Couldn't convert {row}")
print(f"Row {rowno}: Reason {e}")
continue
# Make a dictionary or a tuple
if headers:
record = dict(zip(headers, row))
else:
record = tuple(row)
records.append(record)
return records

20
Solutions/3_16/pcost.py Normal file
View File

@@ -0,0 +1,20 @@
# pcost.py
import report
def portfolio_cost(filename):
'''
Computes the total cost (shares*price) of a portfolio file
'''
portfolio = report.read_portfolio(filename)
return sum([s['shares'] * s['price'] for s in portfolio])
def main(args):
if len(args) != 2:
raise SystemExit('Usage: %s portfoliofile' % args[0])
filename = args[1]
print('Total cost:', portfolio_cost(filename))
if __name__ == '__main__':
import sys
main(sys.argv)

62
Solutions/3_16/report.py Normal file
View File

@@ -0,0 +1,62 @@
# report.py
import fileparse
def read_portfolio(filename):
'''
Read a stock portfolio file into a list of dictionaries with keys
name, shares, and price.
'''
return fileparse.parse_csv(filename, select=['name','shares','price'], types=[str,int,float])
def read_prices(filename):
'''
Read a CSV file of price data into a dict mapping names to prices.
'''
return dict(fileparse.parse_csv(filename,types=[str,float], has_headers=False))
def make_report_data(portfolio,prices):
'''
Make a list of (name, shares, price, change) tuples given a portfolio list
and prices dictionary.
'''
rows = []
for stock in portfolio:
current_price = prices[stock['name']]
change = current_price - stock['price']
summary = (stock['name'], stock['shares'], current_price, change)
rows.append(summary)
return rows
def print_report(reportdata):
'''
Print a nicely formated table from a list of (name, shares, price, change) tuples.
'''
headers = ('Name','Shares','Price','Change')
print('%10s %10s %10s %10s' % headers)
print(('-'*10 + ' ')*len(headers))
for row in reportdata:
print('%10s %10d %10.2f %10.2f' % row)
def portfolio_report(portfoliofile, pricefile):
'''
Make a stock report given portfolio and price data files.
'''
# Read data files
portfolio = read_portfolio(portfoliofile)
prices = read_prices(pricefile)
# Create the report data
report = make_report_data(portfolio, prices)
# Print it out
print_report(report)
def main(args):
if len(args) != 3:
raise SystemExit('Usage: %s portfile pricefile' % args[0])
portfolio_report(args[1], args[2])
if __name__ == '__main__':
import sys
main(sys.argv)

View File

@@ -0,0 +1,47 @@
# fileparse.py
import csv
def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):
'''
Parse a CSV file into a list of records with type conversion.
'''
if select and not has_headers:
raise RuntimeError('select requires column headers')
rows = csv.reader(lines, delimiter=delimiter)
# Read the file headers (if any)
headers = next(rows) if has_headers else []
# If specific columns have been selected, make indices for filtering and set output columns
if select:
indices = [ headers.index(colname) for colname in select ]
headers = select
records = []
for rowno, row in enumerate(rows, 1):
if not row: # Skip rows with no data
continue
# If specific column indices are selected, pick them out
if select:
row = [ row[index] for index in indices]
# Apply type conversion to the row
if types:
try:
row = [func(val) for func, val in zip(types, row)]
except ValueError as e:
if not silence_errors:
print(f"Row {rowno}: Couldn't convert {row}")
print(f"Row {rowno}: Reason {e}")
continue
# Make a dictionary or a tuple
if headers:
record = dict(zip(headers, row))
else:
record = tuple(row)
records.append(record)
return records

20
Solutions/3_18/pcost.py Normal file
View File

@@ -0,0 +1,20 @@
# pcost.py
import report
def portfolio_cost(filename):
'''
Computes the total cost (shares*price) of a portfolio file
'''
portfolio = report.read_portfolio(filename)
return sum([s['shares'] * s['price'] for s in portfolio])
def main(args):
if len(args) != 2:
raise SystemExit('Usage: %s portfoliofile' % args[0])
filename = args[1]
print('Total cost:', portfolio_cost(filename))
if __name__ == '__main__':
import sys
main(sys.argv)

64
Solutions/3_18/report.py Normal file
View File

@@ -0,0 +1,64 @@
# report.py
import fileparse
def read_portfolio(filename):
'''
Read a stock portfolio file into a list of dictionaries with keys
name, shares, and price.
'''
with open(filename) as lines:
return fileparse.parse_csv(lines, select=['name','shares','price'], types=[str,int,float])
def read_prices(filename):
'''
Read a CSV file of price data into a dict mapping names to prices.
'''
with open(filename) as lines:
return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False))
def make_report_data(portfolio,prices):
'''
Make a list of (name, shares, price, change) tuples given a portfolio list
and prices dictionary.
'''
rows = []
for stock in portfolio:
current_price = prices[stock['name']]
change = current_price - stock['price']
summary = (stock['name'], stock['shares'], current_price, change)
rows.append(summary)
return rows
def print_report(reportdata):
'''
Print a nicely formated table from a list of (name, shares, price, change) tuples.
'''
headers = ('Name','Shares','Price','Change')
print('%10s %10s %10s %10s' % headers)
print(('-'*10 + ' ')*len(headers))
for row in reportdata:
print('%10s %10d %10.2f %10.2f' % row)
def portfolio_report(portfoliofile, pricefile):
'''
Make a stock report given portfolio and price data files.
'''
# Read data files
portfolio = read_portfolio(portfoliofile)
prices = read_prices(pricefile)
# Create the report data
report = make_report_data(portfolio, prices)
# Print it out
print_report(report)
def main(args):
if len(args) != 3:
raise SystemExit('Usage: %s portfile pricefile' % args[0])
portfolio_report(args[1], args[2])
if __name__ == '__main__':
import sys
main(sys.argv)

78
Solutions/3_2/report.py Normal file
View File

@@ -0,0 +1,78 @@
# report.py
import csv
def read_portfolio(filename):
'''
Read a stock portfolio file into a list of dictionaries with keys
name, shares, and price.
'''
portfolio = []
with open(filename) as f:
rows = csv.reader(f)
headers = next(rows)
for row in rows:
record = dict(zip(headers, row))
stock = {
'name' : record['name'],
'shares' : int(record['shares']),
'price' : float(record['price'])
}
portfolio.append(stock)
return portfolio
def read_prices(filename):
'''
Read a CSV file of price data into a dict mapping names to prices.
'''
prices = {}
with open(filename) as f:
rows = csv.reader(f)
for row in rows:
try:
prices[row[0]] = float(row[1])
except IndexError:
pass
return prices
def make_report_data(portfolio,prices):
'''
Make a list of (name, shares, price, change) tuples given a portfolio list
and prices dictionary.
'''
rows = []
for stock in portfolio:
current_price = prices[stock['name']]
change = current_price - stock['price']
summary = (stock['name'], stock['shares'], current_price, change)
rows.append(summary)
return rows
def print_report(reportdata):
'''
Print a nicely formated table from a list of (name, shares, price, change) tuples.
'''
headers = ('Name','Shares','Price','Change')
print('%10s %10s %10s %10s' % headers)
print(('-'*10 + ' ')*len(headers))
for row in reportdata:
print('%10s %10d %10.2f %10.2f' % row)
def portfolio_report(portfoliofile,pricefile):
'''
Make a stock report given portfolio and price data files.
'''
# Read data files
portfolio = read_portfolio(portfoliofile)
prices = read_prices(pricefile)
# Create the report data
report = make_report_data(portfolio,prices)
# Print it out
print_report(report)
portfolio_report('../../Work/Data/portfolio.csv',
'../../Work/Data/prices.csv')

View File

@@ -0,0 +1,39 @@
# fileparse.py
import csv
def parse_csv(filename, select=None, types=None, has_headers=True, delimiter=','):
'''
Parse a CSV file into a list of records with type conversion.
'''
with open(filename) as f:
rows = csv.reader(f, delimiter=delimiter)
# Read the file headers (if any)
headers = next(rows) if has_headers else []
# If specific columns have been selected, make indices for filtering
if select:
indices = [ headers.index(colname) for colname in select ]
headers = select
records = []
for row in rows:
if not row: # Skip rows with no data
continue
# If specific column indices are selected, pick them out
if select:
row = [ row[index] for index in indices]
# Apply type conversion to the row
if types:
row = [func(val) for func, val in zip(types, row)]
# Make a dictionary or a tuple
if headers:
record = dict(zip(headers, row))
else:
record = tuple(row)
records.append(record)
return records

View File

@@ -0,0 +1,47 @@
# fileparse.py
import csv
def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):
'''
Parse a CSV file into a list of records with type conversion.
'''
if select and not has_headers:
raise RuntimeError('select requires column headers')
rows = csv.reader(lines, delimiter=delimiter)
# Read the file headers (if any)
headers = next(rows) if has_headers else []
# If specific columns have been selected, make indices for filtering and set output columns
if select:
indices = [ headers.index(colname) for colname in select ]
headers = select
records = []
for rowno, row in enumerate(rows, 1):
if not row: # Skip rows with no data
continue
# If specific column indices are selected, pick them out
if select:
row = [ row[index] for index in indices]
# Apply type conversion to the row
if types:
try:
row = [func(val) for func, val in zip(types, row)]
except ValueError as e:
if not silence_errors:
print(f"Row {rowno}: Couldn't convert {row}")
print(f"Row {rowno}: Reason {e}")
continue
# Make a dictionary or a tuple
if headers:
record = dict(zip(headers, row))
else:
record = tuple(row)
records.append(record)
return records

20
Solutions/4_10/pcost.py Normal file
View File

@@ -0,0 +1,20 @@
# pcost.py
import report
def portfolio_cost(filename):
'''
Computes the total cost (shares*price) of a portfolio file
'''
portfolio = report.read_portfolio(filename)
return sum([s.cost() for s in portfolio])
def main(args):
if len(args) != 2:
raise SystemExit('Usage: %s portfoliofile' % args[0])
filename = args[1]
print('Total cost:', portfolio_cost(filename))
if __name__ == '__main__':
import sys
main(sys.argv)

71
Solutions/4_10/report.py Normal file
View File

@@ -0,0 +1,71 @@
# report.py
import fileparse
from stock import Stock
import tableformat
def read_portfolio(filename):
'''
Read a stock portfolio file into a list of dictionaries with keys
name, shares, and price.
'''
with open(filename) as lines:
portdicts = fileparse.parse_csv(lines,
select=['name','shares','price'],
types=[str,int,float])
portfolio = [ Stock(d['name'], d['shares'], d['price']) for d in portdicts ]
return portfolio
def read_prices(filename):
'''
Read a CSV file of price data into a dict mapping names to prices.
'''
with open(filename) as lines:
return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False))
def make_report_data(portfolio, prices):
'''
Make a list of (name, shares, price, change) tuples given a portfolio list
and prices dictionary.
'''
rows = []
for s in portfolio:
current_price = prices[s.name]
change = current_price - s.price
summary = (s.name, s.shares, current_price, change)
rows.append(summary)
return rows
def print_report(reportdata, formatter):
'''
Print a nicely formated table from a list of (name, shares, price, change) tuples.
'''
formatter.headings(['Name','Shares','Price','Change'])
for name, shares, price, change in reportdata:
rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ]
formatter.row(rowdata)
def portfolio_report(portfoliofile, pricefile, fmt='txt'):
'''
Make a stock report given portfolio and price data files.
'''
# Read data files
portfolio = read_portfolio(portfoliofile)
prices = read_prices(pricefile)
# Create the report data
report = make_report_data(portfolio, prices)
# Print it out
formatter = tableformat.create_formatter(fmt)
print_report(report, formatter)
def main(args):
if len(args) != 4:
raise SystemExit('Usage: %s portfile pricefile format' % args[0])
portfolio_report(args[1], args[2], args[3])
if __name__ == '__main__':
import sys
main(sys.argv)

26
Solutions/4_10/stock.py Normal file
View File

@@ -0,0 +1,26 @@
# stock.py
class Stock(object):
'''
An instance of a stock holding consisting of name, shares, and price.
'''
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
def __repr__(self):
return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'
def cost(self):
'''
Return the cost as shares*price
'''
return self.shares * self.price
def sell(self, nshares):
'''
Sell a number of shares
'''
self.shares -= nshares

View File

@@ -0,0 +1,81 @@
# tableformat.py
class TableFormatter(object):
def headings(self, headers):
'''
Emit the table headers
'''
raise NotImplementedError()
def row(self, rowdata):
'''
Emit a single row of table data
'''
raise NotImplementedError()
class TextTableFormatter(TableFormatter):
'''
Output data in plain-text format.
'''
def headings(self, headers):
for h in headers:
print(f'{h:>10s}', end=' ')
print()
print(('-'*10 + ' ')*len(headers))
def row(self, rowdata):
for d in rowdata:
print(f'{d:>10s}', end=' ')
print()
class CSVTableFormatter(TableFormatter):
'''
Output data in CSV format.
'''
def headings(self, headers):
print(','.join(headers))
def row(self, rowdata):
print(','.join(rowdata))
class HTMLTableFormatter(TableFormatter):
'''
Output data in HTML format.
'''
def headings(self, headers):
print('<tr>', end='')
for h in headers:
print(f'<th>{h}</th>', end='')
print('</tr>')
def row(self, rowdata):
print('<tr>', end='')
for d in rowdata:
print(f'<td>{d}</td>', end='')
print('</tr>')
class FormatError(Exception):
pass
def create_formatter(name):
'''
Create an appropriate formatter given an output format name
'''
if name == 'txt':
return TextTableFormatter()
elif name == 'csv':
return CSVTableFormatter()
elif name == 'html':
return HTMLTableFormatter()
else:
raise FormatError(f'Unknown table format {name}')
def print_table(objects, columns, formatter):
'''
Make a nicely formatted table from a list of objects and attribute names.
'''
formatter.headings(columns)
for obj in objects:
rowdata = [ str(getattr(obj, name)) for name in columns ]
formatter.row(rowdata)

View File

@@ -0,0 +1,47 @@
# fileparse.py
import csv
def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):
'''
Parse a CSV file into a list of records with type conversion.
'''
if select and not has_headers:
raise RuntimeError('select requires column headers')
rows = csv.reader(lines, delimiter=delimiter)
# Read the file headers (if any)
headers = next(rows) if has_headers else []
# If specific columns have been selected, make indices for filtering and set output columns
if select:
indices = [ headers.index(colname) for colname in select ]
headers = select
records = []
for rowno, row in enumerate(rows, 1):
if not row: # Skip rows with no data
continue
# If specific column indices are selected, pick them out
if select:
row = [ row[index] for index in indices]
# Apply type conversion to the row
if types:
try:
row = [func(val) for func, val in zip(types, row)]
except ValueError as e:
if not silence_errors:
print(f"Row {rowno}: Couldn't convert {row}")
print(f"Row {rowno}: Reason {e}")
continue
# Make a dictionary or a tuple
if headers:
record = dict(zip(headers, row))
else:
record = tuple(row)
records.append(record)
return records

20
Solutions/4_4/pcost.py Normal file
View File

@@ -0,0 +1,20 @@
# pcost.py
import report
def portfolio_cost(filename):
'''
Computes the total cost (shares*price) of a portfolio file
'''
portfolio = report.read_portfolio(filename)
return sum([s.cost() for s in portfolio])
def main(args):
if len(args) != 2:
raise SystemExit('Usage: %s portfoliofile' % args[0])
filename = args[1]
print('Total cost:', portfolio_cost(filename))
if __name__ == '__main__':
import sys
main(sys.argv)

70
Solutions/4_4/report.py Normal file
View File

@@ -0,0 +1,70 @@
# report.py
import fileparse
from stock import Stock
def read_portfolio(filename):
'''
Read a stock portfolio file into a list of dictionaries with keys
name, shares, and price.
'''
with open(filename) as lines:
portdicts = fileparse.parse_csv(lines,
select=['name','shares','price'],
types=[str,int,float])
portfolio = [ Stock(d['name'], d['shares'], d['price']) for d in portdicts ]
return portfolio
def read_prices(filename):
'''
Read a CSV file of price data into a dict mapping names to prices.
'''
with open(filename) as lines:
return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False))
def make_report_data(portfolio, prices):
'''
Make a list of (name, shares, price, change) tuples given a portfolio list
and prices dictionary.
'''
rows = []
for s in portfolio:
current_price = prices[s.name]
change = current_price - s.price
summary = (s.name, s.shares, current_price, change)
rows.append(summary)
return rows
def print_report(reportdata):
'''
Print a nicely formated table from a list of (name, shares, price, change) tuples.
'''
headers = ('Name','Shares','Price','Change')
print('%10s %10s %10s %10s' % headers)
print(('-'*10 + ' ')*len(headers))
for row in reportdata:
print('%10s %10d %10.2f %10.2f' % row)
def portfolio_report(portfoliofile, pricefile):
'''
Make a stock report given portfolio and price data files.
'''
# Read data files
portfolio = read_portfolio(portfoliofile)
prices = read_prices(pricefile)
# Create the report data
report = make_report_data(portfolio, prices)
# Print it out
print_report(report)
def main(args):
if len(args) != 3:
raise SystemExit('Usage: %s portfile pricefile' % args[0])
portfolio_report(args[1], args[2])
if __name__ == '__main__':
import sys
main(sys.argv)

23
Solutions/4_4/stock.py Normal file
View File

@@ -0,0 +1,23 @@
# stock.py
class Stock(object):
'''
An instance of a stock holding consisting of name, shares, and price.
'''
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
def cost(self):
'''
Return the cost as shares*price
'''
return self.shares * self.price
def sell(self, nshares):
'''
Sell a number of shares
'''
self.shares -= nshares

View File

@@ -0,0 +1,47 @@
# fileparse.py
import csv
def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):
'''
Parse a CSV file into a list of records with type conversion.
'''
if select and not has_headers:
raise RuntimeError('select requires column headers')
rows = csv.reader(lines, delimiter=delimiter)
# Read the file headers (if any)
headers = next(rows) if has_headers else []
# If specific columns have been selected, make indices for filtering and set output columns
if select:
indices = [ headers.index(colname) for colname in select ]
headers = select
records = []
for rowno, row in enumerate(rows, 1):
if not row: # Skip rows with no data
continue
# If specific column indices are selected, pick them out
if select:
row = [ row[index] for index in indices]
# Apply type conversion to the row
if types:
try:
row = [func(val) for func, val in zip(types, row)]
except ValueError as e:
if not silence_errors:
print(f"Row {rowno}: Couldn't convert {row}")
print(f"Row {rowno}: Reason {e}")
continue
# Make a dictionary or a tuple
if headers:
record = dict(zip(headers, row))
else:
record = tuple(row)
records.append(record)
return records

20
Solutions/5_8/pcost.py Normal file
View File

@@ -0,0 +1,20 @@
# pcost.py
import report
def portfolio_cost(filename):
'''
Computes the total cost (shares*price) of a portfolio file
'''
portfolio = report.read_portfolio(filename)
return sum([s.cost() for s in portfolio])
def main(args):
if len(args) != 2:
raise SystemExit('Usage: %s portfoliofile' % args[0])
filename = args[1]
print('Total cost:', portfolio_cost(filename))
if __name__ == '__main__':
import sys
main(sys.argv)

71
Solutions/5_8/report.py Normal file
View File

@@ -0,0 +1,71 @@
# report.py
import fileparse
from stock import Stock
import tableformat
def read_portfolio(filename):
'''
Read a stock portfolio file into a list of dictionaries with keys
name, shares, and price.
'''
with open(filename) as lines:
portdicts = fileparse.parse_csv(lines,
select=['name','shares','price'],
types=[str,int,float])
portfolio = [ Stock(d['name'], d['shares'], d['price']) for d in portdicts ]
return portfolio
def read_prices(filename):
'''
Read a CSV file of price data into a dict mapping names to prices.
'''
with open(filename) as lines:
return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False))
def make_report_data(portfolio, prices):
'''
Make a list of (name, shares, price, change) tuples given a portfolio list
and prices dictionary.
'''
rows = []
for s in portfolio:
current_price = prices[s.name]
change = current_price - s.price
summary = (s.name, s.shares, current_price, change)
rows.append(summary)
return rows
def print_report(reportdata, formatter):
'''
Print a nicely formated table from a list of (name, shares, price, change) tuples.
'''
formatter.headings(['Name','Shares','Price','Change'])
for name, shares, price, change in reportdata:
rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ]
formatter.row(rowdata)
def portfolio_report(portfoliofile, pricefile, fmt='txt'):
'''
Make a stock report given portfolio and price data files.
'''
# Read data files
portfolio = read_portfolio(portfoliofile)
prices = read_prices(pricefile)
# Create the report data
report = make_report_data(portfolio, prices)
# Print it out
formatter = tableformat.create_formatter(fmt)
print_report(report, formatter)
def main(args):
if len(args) != 4:
raise SystemExit('Usage: %s portfile pricefile format' % args[0])
portfolio_report(args[1], args[2], args[3])
if __name__ == '__main__':
import sys
main(sys.argv)

37
Solutions/5_8/stock.py Normal file
View File

@@ -0,0 +1,37 @@
# stock.py
class Stock(object):
'''
An instance of a stock holding consisting of name, shares, and price.
'''
__slots__ = ('name','_shares','price')
def __init__(self,name, shares, price):
self.name = name
self.shares = shares
self.price = price
def __repr__(self):
return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'
@property
def shares(self):
return self._shares
@shares.setter
def shares(self, value):
if not isinstance(value,int):
raise TypeError("Must be integer")
self._shares = value
@property
def cost(self):
'''
Return the cost as shares*price
'''
return self.shares * self.price
def sell(self, nshares):
'''
Sell a number of shares and return the remaining number.
'''
self.shares -= nshares

View File

@@ -0,0 +1,81 @@
# tableformat.py
class TableFormatter(object):
def headings(self, headers):
'''
Emit the table headers
'''
raise NotImplementedError()
def row(self, rowdata):
'''
Emit a single row of table data
'''
raise NotImplementedError()
class TextTableFormatter(TableFormatter):
'''
Output data in plain-text format.
'''
def headings(self, headers):
for h in headers:
print(f'{h:>10s}', end=' ')
print()
print(('-'*10 + ' ')*len(headers))
def row(self, rowdata):
for d in rowdata:
print(f'{d:>10s}', end=' ')
print()
class CSVTableFormatter(TableFormatter):
'''
Output data in CSV format.
'''
def headings(self, headers):
print(','.join(headers))
def row(self, rowdata):
print(','.join(rowdata))
class HTMLTableFormatter(TableFormatter):
'''
Output data in HTML format.
'''
def headings(self, headers):
print('<tr>', end='')
for h in headers:
print(f'<th>{h}</th>', end='')
print('</tr>')
def row(self, rowdata):
print('<tr>', end='')
for d in rowdata:
print(f'<td>{d}</td>', end='')
print('</tr>')
class FormatError(Exception):
pass
def create_formatter(name):
'''
Create an appropriate formatter given an output format name
'''
if name == 'txt':
return TextTableFormatter()
elif name == 'csv':
return CSVTableFormatter()
elif name == 'html':
return HTMLTableFormatter()
else:
raise FormatError(f'Unknown table format {name}')
def print_table(objects, columns, formatter):
'''
Make a nicely formatted table from a list of objects and attribute names.
'''
formatter.headings(columns)
for obj in objects:
rowdata = [ str(getattr(obj, name)) for name in columns ]
formatter.row(rowdata)

View File

@@ -0,0 +1,47 @@
# fileparse.py
import csv
def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):
'''
Parse a CSV file into a list of records with type conversion.
'''
if select and not has_headers:
raise RuntimeError('select requires column headers')
rows = csv.reader(lines, delimiter=delimiter)
# Read the file headers (if any)
headers = next(rows) if has_headers else []
# If specific columns have been selected, make indices for filtering and set output columns
if select:
indices = [ headers.index(colname) for colname in select ]
headers = select
records = []
for rowno, row in enumerate(rows, 1):
if not row: # Skip rows with no data
continue
# If specific column indices are selected, pick them out
if select:
row = [ row[index] for index in indices]
# Apply type conversion to the row
if types:
try:
row = [func(val) for func, val in zip(types, row)]
except ValueError as e:
if not silence_errors:
print(f"Row {rowno}: Couldn't convert {row}")
print(f"Row {rowno}: Reason {e}")
continue
# Make a dictionary or a tuple
if headers:
record = dict(zip(headers, row))
else:
record = tuple(row)
records.append(record)
return records

30
Solutions/6_12/follow.py Normal file
View File

@@ -0,0 +1,30 @@
# follow.py
import os
import time
def follow(filename):
'''
Generator that produces a sequence of lines being written at the end of a file.
'''
f = open(filename, 'r')
f.seek(0,os.SEEK_END)
while True:
line = f.readline()
if line == '':
time.sleep(0.1) # Sleep briefly to avoid busy wait
continue
yield line
# Example use
if __name__ == '__main__':
import report
portfolio = report.read_portfolio('../../Data/portfolio.csv')
for line in follow('../../Data/stocklog.csv'):
row = line.split(',')
name = row[0].strip('"')
price = float(row[1])
change = float(row[4])
if name in portfolio:
print(f'{name:>10s} {price:>10.2f} {change:>10.2f}')

20
Solutions/6_12/pcost.py Normal file
View File

@@ -0,0 +1,20 @@
# pcost.py
import report
def portfolio_cost(filename):
'''
Computes the total cost (shares*price) of a portfolio file
'''
portfolio = report.read_portfolio(filename)
return portfolio.total_cost
def main(args):
if len(args) != 2:
raise SystemExit('Usage: %s portfoliofile' % args[0])
filename = args[1]
print('Total cost:', portfolio_cost(filename))
if __name__ == '__main__':
import sys
main(sys.argv)

View File

@@ -0,0 +1,31 @@
# portfolio.py
class Portfolio(object):
def __init__(self, holdings):
self._holdings = holdings
def __iter__(self):
return self._holdings.__iter__()
def __len__(self):
return len(self._holdings)
def __getitem__(self, index):
return self._holdings[index]
def __contains__(self, name):
return any([s.name == name for s in self._holdings])
@property
def total_cost(self):
return sum([s.shares * s.price for s in self._holdings])
def tabulate_shares(self):
from collections import Counter
total_shares = Counter()
for s in self._holdings:
total_shares[s.name] += s.shares
return total_shares

72
Solutions/6_12/report.py Normal file
View File

@@ -0,0 +1,72 @@
# report.py
import fileparse
from stock import Stock
import tableformat
from portfolio import Portfolio
def read_portfolio(filename):
'''
Read a stock portfolio file into a list of dictionaries with keys
name, shares, and price.
'''
with open(filename) as lines:
portdicts = fileparse.parse_csv(lines,
select=['name','shares','price'],
types=[str,int,float])
portfolio = [ Stock(d['name'], d['shares'], d['price']) for d in portdicts ]
return Portfolio(portfolio)
def read_prices(filename):
'''
Read a CSV file of price data into a dict mapping names to prices.
'''
with open(filename) as lines:
return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False))
def make_report_data(portfolio, prices):
'''
Make a list of (name, shares, price, change) tuples given a portfolio list
and prices dictionary.
'''
rows = []
for s in portfolio:
current_price = prices[s.name]
change = current_price - s.price
summary = (s.name, s.shares, current_price, change)
rows.append(summary)
return rows
def print_report(reportdata, formatter):
'''
Print a nicely formated table from a list of (name, shares, price, change) tuples.
'''
formatter.headings(['Name','Shares','Price','Change'])
for name, shares, price, change in reportdata:
rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ]
formatter.row(rowdata)
def portfolio_report(portfoliofile, pricefile, fmt='txt'):
'''
Make a stock report given portfolio and price data files.
'''
# Read data files
portfolio = read_portfolio(portfoliofile)
prices = read_prices(pricefile)
# Create the report data
report = make_report_data(portfolio, prices)
# Print it out
formatter = tableformat.create_formatter(fmt)
print_report(report, formatter)
def main(args):
if len(args) != 4:
raise SystemExit('Usage: %s portfile pricefile format' % args[0])
portfolio_report(args[1], args[2], args[3])
if __name__ == '__main__':
import sys
main(sys.argv)

37
Solutions/6_12/stock.py Normal file
View File

@@ -0,0 +1,37 @@
# stock.py
class Stock(object):
'''
An instance of a stock holding consisting of name, shares, and price.
'''
__slots__ = ('name','_shares','price')
def __init__(self,name, shares, price):
self.name = name
self.shares = shares
self.price = price
def __repr__(self):
return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'
@property
def shares(self):
return self._shares
@shares.setter
def shares(self, value):
if not isinstance(value,int):
raise TypeError("Must be integer")
self._shares = value
@property
def cost(self):
'''
Return the cost as shares*price
'''
return self.shares * self.price
def sell(self, nshares):
'''
Sell a number of shares and return the remaining number.
'''
self.shares -= nshares

View File

@@ -0,0 +1,81 @@
# tableformat.py
class TableFormatter(object):
def headings(self, headers):
'''
Emit the table headers
'''
raise NotImplementedError()
def row(self, rowdata):
'''
Emit a single row of table data
'''
raise NotImplementedError()
class TextTableFormatter(TableFormatter):
'''
Output data in plain-text format.
'''
def headings(self, headers):
for h in headers:
print(f'{h:>10s}', end=' ')
print()
print(('-'*10 + ' ')*len(headers))
def row(self, rowdata):
for d in rowdata:
print(f'{d:>10s}', end=' ')
print()
class CSVTableFormatter(TableFormatter):
'''
Output data in CSV format.
'''
def headings(self, headers):
print(','.join(headers))
def row(self, rowdata):
print(','.join(rowdata))
class HTMLTableFormatter(TableFormatter):
'''
Output data in HTML format.
'''
def headings(self, headers):
print('<tr>', end='')
for h in headers:
print(f'<th>{h}</th>', end='')
print('</tr>')
def row(self, rowdata):
print('<tr>', end='')
for d in rowdata:
print(f'<td>{d}</td>', end='')
print('</tr>')
class FormatError(Exception):
pass
def create_formatter(name):
'''
Create an appropriate formatter given an output format name
'''
if name == 'txt':
return TextTableFormatter()
elif name == 'csv':
return CSVTableFormatter()
elif name == 'html':
return HTMLTableFormatter()
else:
raise FormatError(f'Unknown table format {name}')
def print_table(objects, columns, formatter):
'''
Make a nicely formatted table from a list of objects and attribute names.
'''
formatter.headings(columns)
for obj in objects:
rowdata = [ str(getattr(obj, name)) for name in columns ]
formatter.row(rowdata)

50
Solutions/6_12/ticker.py Normal file
View File

@@ -0,0 +1,50 @@
# ticker.py
import csv
import report
import tableformat
from follow import follow
import time
def select_columns(rows, indices):
for row in rows:
yield [row[index] for index in indices]
def convert_types(rows, types):
for row in rows:
yield [func(val) for func, val in zip(types, row)]
def make_dicts(rows, headers):
for row in rows:
yield dict(zip(headers, row))
def parse_stock_data(lines):
rows = csv.reader(lines)
rows = select_columns(rows, [0, 1, 4])
rows = convert_types(rows, [str,float,float])
rows = make_dicts(rows, ['name','price','change'])
return rows
def filter_symbols(rows, names):
for row in rows:
if row['name'] in names:
yield row
def ticker(portfile, logfile, fmt):
portfolio = report.read_portfolio(portfile)
lines = follow(logfile)
rows = parse_stock_data(lines)
rows = filter_symbols(rows, portfolio)
formatter = tableformat.create_formatter(fmt)
formatter.headings(['Name','Price','Change'])
for row in rows:
formatter.row([ row['name'], f"{row['price']:0.2f}", f"{row['change']:0.2f}"] )
def main(args):
if len(args) != 4:
raise SystemExit('Usage: %s portfoliofile logfile fmt' % args[0])
ticker(args[1], args[2], args[3])
if __name__ == '__main__':
import sys
main(sys.argv)

View File

@@ -0,0 +1,47 @@
# fileparse.py
import csv
def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):
'''
Parse a CSV file into a list of records with type conversion.
'''
if select and not has_headers:
raise RuntimeError('select requires column headers')
rows = csv.reader(lines, delimiter=delimiter)
# Read the file headers (if any)
headers = next(rows) if has_headers else []
# If specific columns have been selected, make indices for filtering and set output columns
if select:
indices = [ headers.index(colname) for colname in select ]
headers = select
records = []
for rowno, row in enumerate(rows, 1):
if not row: # Skip rows with no data
continue
# If specific column indices are selected, pick them out
if select:
row = [ row[index] for index in indices]
# Apply type conversion to the row
if types:
try:
row = [func(val) for func, val in zip(types, row)]
except ValueError as e:
if not silence_errors:
print(f"Row {rowno}: Couldn't convert {row}")
print(f"Row {rowno}: Reason {e}")
continue
# Make a dictionary or a tuple
if headers:
record = dict(zip(headers, row))
else:
record = tuple(row)
records.append(record)
return records

17
Solutions/6_15/follow.py Normal file
View File

@@ -0,0 +1,17 @@
# follow.py
import time
import os
def follow(filename):
'''
Generator that produces a sequence of lines being written at the end of a file.
'''
with open(filename,'r') as f:
f.seek(0,os.SEEK_END)
while True:
line = f.readline()
if line != '':
yield line
else:
time.sleep(0.1) # Sleep briefly to avoid busy wait

20
Solutions/6_15/pcost.py Normal file
View File

@@ -0,0 +1,20 @@
# pcost.py
import report
def portfolio_cost(filename):
'''
Computes the total cost (shares*price) of a portfolio file
'''
portfolio = report.read_portfolio(filename)
return portfolio.total_cost
def main(args):
if len(args) != 2:
raise SystemExit('Usage: %s portfoliofile' % args[0])
filename = args[1]
print('Total cost:', portfolio_cost(filename))
if __name__ == '__main__':
import sys
main(sys.argv)

View File

@@ -0,0 +1,31 @@
# portfolio.py
class Portfolio(object):
def __init__(self, holdings):
self._holdings = holdings
def __iter__(self):
return self._holdings.__iter__()
def __len__(self):
return len(self._holdings)
def __getitem__(self, index):
return self._holdings[index]
def __contains__(self, name):
return any(s.name == name for s in self._holdings)
@property
def total_cost(self):
return sum(s.shares * s.price for s in self._holdings)
def tabulate_shares(self):
from collections import Counter
total_shares = Counter()
for s in self._holdings:
total_shares[s.name] += s.shares
return total_shares

72
Solutions/6_15/report.py Normal file
View File

@@ -0,0 +1,72 @@
# report.py
import fileparse
from stock import Stock
import tableformat
from portfolio import Portfolio
def read_portfolio(filename):
'''
Read a stock portfolio file into a list of dictionaries with keys
name, shares, and price.
'''
with open(filename) as lines:
portdicts = fileparse.parse_csv(lines,
select=['name','shares','price'],
types=[str,int,float])
portfolio = [ Stock(d['name'], d['shares'], d['price']) for d in portdicts ]
return Portfolio(portfolio)
def read_prices(filename):
'''
Read a CSV file of price data into a dict mapping names to prices.
'''
with open(filename) as lines:
return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False))
def make_report_data(portfolio, prices):
'''
Make a list of (name, shares, price, change) tuples given a portfolio list
and prices dictionary.
'''
rows = []
for s in portfolio:
current_price = prices[s.name]
change = current_price - s.price
summary = (s.name, s.shares, current_price, change)
rows.append(summary)
return rows
def print_report(reportdata, formatter):
'''
Print a nicely formated table from a list of (name, shares, price, change) tuples.
'''
formatter.headings(['Name','Shares','Price','Change'])
for name, shares, price, change in reportdata:
rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ]
formatter.row(rowdata)
def portfolio_report(portfoliofile, pricefile, fmt='txt'):
'''
Make a stock report given portfolio and price data files.
'''
# Read data files
portfolio = read_portfolio(portfoliofile)
prices = read_prices(pricefile)
# Create the report data
report = make_report_data(portfolio, prices)
# Print it out
formatter = tableformat.create_formatter(fmt)
print_report(report, formatter)
def main(args):
if len(args) != 4:
raise SystemExit('Usage: %s portfile pricefile format' % args[0])
portfolio_report(args[1], args[2], args[3])
if __name__ == '__main__':
import sys
main(sys.argv)

37
Solutions/6_15/stock.py Normal file
View File

@@ -0,0 +1,37 @@
# stock.py
class Stock(object):
'''
An instance of a stock holding consisting of name, shares, and price.
'''
__slots__ = ('name','_shares','price')
def __init__(self,name, shares, price):
self.name = name
self.shares = shares
self.price = price
def __repr__(self):
return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'
@property
def shares(self):
return self._shares
@shares.setter
def shares(self, value):
if not isinstance(value,int):
raise TypeError("Must be integer")
self._shares = value
@property
def cost(self):
'''
Return the cost as shares*price
'''
return self.shares * self.price
def sell(self, nshares):
'''
Sell a number of shares and return the remaining number.
'''
self.shares -= nshares

View File

@@ -0,0 +1,81 @@
# tableformat.py
class TableFormatter(object):
def headings(self, headers):
'''
Emit the table headers
'''
raise NotImplementedError()
def row(self, rowdata):
'''
Emit a single row of table data
'''
raise NotImplementedError()
class TextTableFormatter(TableFormatter):
'''
Output data in plain-text format.
'''
def headings(self, headers):
for h in headers:
print(f'{h:>10s}', end=' ')
print()
print(('-'*10 + ' ')*len(headers))
def row(self, rowdata):
for d in rowdata:
print(f'{d:>10s}', end=' ')
print()
class CSVTableFormatter(TableFormatter):
'''
Output data in CSV format.
'''
def headings(self, headers):
print(','.join(headers))
def row(self, rowdata):
print(','.join(rowdata))
class HTMLTableFormatter(TableFormatter):
'''
Output data in HTML format.
'''
def headings(self, headers):
print('<tr>', end='')
for h in headers:
print(f'<th>{h}</th>', end='')
print('</tr>')
def row(self, rowdata):
print('<tr>', end='')
for d in rowdata:
print(f'<td>{d}</td>', end='')
print('</tr>')
class FormatError(Exception):
pass
def create_formatter(name):
'''
Create an appropriate formatter given an output format name
'''
if name == 'txt':
return TextTableFormatter()
elif name == 'csv':
return CSVTableFormatter()
elif name == 'html':
return HTMLTableFormatter()
else:
raise FormatError(f'Unknown table format {name}')
def print_table(objects, columns, formatter):
'''
Make a nicely formatted table from a list of objects and attribute names.
'''
formatter.headings(columns)
for obj in objects:
rowdata = [ str(getattr(obj, name)) for name in columns ]
formatter.row(rowdata)

44
Solutions/6_15/ticker.py Normal file
View File

@@ -0,0 +1,44 @@
# ticker.py
import csv
import report
import tableformat
from follow import follow
import time
def select_columns(rows, indices):
for row in rows:
yield [row[index] for index in indices]
def convert_types(rows, types):
for row in rows:
yield [func(val) for func, val in zip(types, row)]
def make_dicts(rows, headers):
return (dict(zip(headers,row)) for row in rows)
def parse_stock_data(lines):
rows = csv.reader(lines)
rows = select_columns(rows, [0, 1, 4])
rows = convert_types(rows, [str,float,float])
rows = make_dicts(rows, ['name','price','change'])
return rows
def ticker(portfile, logfile, fmt):
portfolio = report.read_portfolio(portfile)
lines = follow(logfile)
rows = parse_stock_data(lines)
rows = (row for row in rows if row['name'] in portfolio)
formatter = tableformat.create_formatter(fmt)
formatter.headings(['Name','Price','Change'])
for row in rows:
formatter.row([ row['name'], f"{row['price']:0.2f}", f"{row['change']:0.2f}"] )
def main(args):
if len(args) != 4:
raise SystemExit('Usage: %s portfoliofile logfile fmt' % args[0])
ticker(args[1], args[2], args[3])
if __name__ == '__main__':
import sys
main(sys.argv)

View File

@@ -0,0 +1,47 @@
# fileparse.py
import csv
def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):
'''
Parse a CSV file into a list of records with type conversion.
'''
if select and not has_headers:
raise RuntimeError('select requires column headers')
rows = csv.reader(lines, delimiter=delimiter)
# Read the file headers (if any)
headers = next(rows) if has_headers else []
# If specific columns have been selected, make indices for filtering and set output columns
if select:
indices = [ headers.index(colname) for colname in select ]
headers = select
records = []
for rowno, row in enumerate(rows, 1):
if not row: # Skip rows with no data
continue
# If specific column indices are selected, pick them out
if select:
row = [ row[index] for index in indices]
# Apply type conversion to the row
if types:
try:
row = [func(val) for func, val in zip(types, row)]
except ValueError as e:
if not silence_errors:
print(f"Row {rowno}: Couldn't convert {row}")
print(f"Row {rowno}: Reason {e}")
continue
# Make a dictionary or a tuple
if headers:
record = dict(zip(headers, row))
else:
record = tuple(row)
records.append(record)
return records

20
Solutions/6_3/pcost.py Normal file
View File

@@ -0,0 +1,20 @@
# pcost.py
import report
def portfolio_cost(filename):
'''
Computes the total cost (shares*price) of a portfolio file
'''
portfolio = report.read_portfolio(filename)
return portfolio.total_cost
def main(args):
if len(args) != 2:
raise SystemExit('Usage: %s portfoliofile' % args[0])
filename = args[1]
print('Total cost:', portfolio_cost(filename))
if __name__ == '__main__':
import sys
main(sys.argv)

View File

@@ -0,0 +1,31 @@
# portfolio.py
class Portfolio(object):
def __init__(self, holdings):
self._holdings = holdings
def __iter__(self):
return self._holdings.__iter__()
def __len__(self):
return len(self._holdings)
def __getitem__(self, index):
return self._holdings[index]
def __contains__(self, name):
return any([s.name == name for s in self._holdings])
@property
def total_cost(self):
return sum([s.shares * s.price for s in self._holdings])
def tabulate_shares(self):
from collections import Counter
total_shares = Counter()
for s in self._holdings:
total_shares[s.name] += s.shares
return total_shares

72
Solutions/6_3/report.py Normal file
View File

@@ -0,0 +1,72 @@
# report.py
import fileparse
from stock import Stock
import tableformat
from portfolio import Portfolio
def read_portfolio(filename):
'''
Read a stock portfolio file into a list of dictionaries with keys
name, shares, and price.
'''
with open(filename) as lines:
portdicts = fileparse.parse_csv(lines,
select=['name','shares','price'],
types=[str,int,float])
portfolio = [ Stock(d['name'], d['shares'], d['price']) for d in portdicts ]
return Portfolio(portfolio)
def read_prices(filename):
'''
Read a CSV file of price data into a dict mapping names to prices.
'''
with open(filename) as lines:
return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False))
def make_report_data(portfolio, prices):
'''
Make a list of (name, shares, price, change) tuples given a portfolio list
and prices dictionary.
'''
rows = []
for s in portfolio:
current_price = prices[s.name]
change = current_price - s.price
summary = (s.name, s.shares, current_price, change)
rows.append(summary)
return rows
def print_report(reportdata, formatter):
'''
Print a nicely formated table from a list of (name, shares, price, change) tuples.
'''
formatter.headings(['Name','Shares','Price','Change'])
for name, shares, price, change in reportdata:
rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ]
formatter.row(rowdata)
def portfolio_report(portfoliofile, pricefile, fmt='txt'):
'''
Make a stock report given portfolio and price data files.
'''
# Read data files
portfolio = read_portfolio(portfoliofile)
prices = read_prices(pricefile)
# Create the report data
report = make_report_data(portfolio, prices)
# Print it out
formatter = tableformat.create_formatter(fmt)
print_report(report, formatter)
def main(args):
if len(args) != 4:
raise SystemExit('Usage: %s portfile pricefile format' % args[0])
portfolio_report(args[1], args[2], args[3])
if __name__ == '__main__':
import sys
main(sys.argv)

37
Solutions/6_3/stock.py Normal file
View File

@@ -0,0 +1,37 @@
# stock.py
class Stock(object):
'''
An instance of a stock holding consisting of name, shares, and price.
'''
__slots__ = ('name','_shares','price')
def __init__(self,name, shares, price):
self.name = name
self.shares = shares
self.price = price
def __repr__(self):
return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'
@property
def shares(self):
return self._shares
@shares.setter
def shares(self, value):
if not isinstance(value,int):
raise TypeError("Must be integer")
self._shares = value
@property
def cost(self):
'''
Return the cost as shares*price
'''
return self.shares * self.price
def sell(self, nshares):
'''
Sell a number of shares and return the remaining number.
'''
self.shares -= nshares

View File

@@ -0,0 +1,81 @@
# tableformat.py
class TableFormatter(object):
def headings(self, headers):
'''
Emit the table headers
'''
raise NotImplementedError()
def row(self, rowdata):
'''
Emit a single row of table data
'''
raise NotImplementedError()
class TextTableFormatter(TableFormatter):
'''
Output data in plain-text format.
'''
def headings(self, headers):
for h in headers:
print(f'{h:>10s}', end=' ')
print()
print(('-'*10 + ' ')*len(headers))
def row(self, rowdata):
for d in rowdata:
print(f'{d:>10s}', end=' ')
print()
class CSVTableFormatter(TableFormatter):
'''
Output data in CSV format.
'''
def headings(self, headers):
print(','.join(headers))
def row(self, rowdata):
print(','.join(rowdata))
class HTMLTableFormatter(TableFormatter):
'''
Output data in HTML format.
'''
def headings(self, headers):
print('<tr>', end='')
for h in headers:
print(f'<th>{h}</th>', end='')
print('</tr>')
def row(self, rowdata):
print('<tr>', end='')
for d in rowdata:
print(f'<td>{d}</td>', end='')
print('</tr>')
class FormatError(Exception):
pass
def create_formatter(name):
'''
Create an appropriate formatter given an output format name
'''
if name == 'txt':
return TextTableFormatter()
elif name == 'csv':
return CSVTableFormatter()
elif name == 'html':
return HTMLTableFormatter()
else:
raise FormatError(f'Unknown table format {name}')
def print_table(objects, columns, formatter):
'''
Make a nicely formatted table from a list of objects and attribute names.
'''
formatter.headings(columns)
for obj in objects:
rowdata = [ str(getattr(obj, name)) for name in columns ]
formatter.row(rowdata)

View File

@@ -0,0 +1,47 @@
# fileparse.py
import csv
def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):
'''
Parse a CSV file into a list of records with type conversion.
'''
if select and not has_headers:
raise RuntimeError('select requires column headers')
rows = csv.reader(lines, delimiter=delimiter)
# Read the file headers (if any)
headers = next(rows) if has_headers else []
# If specific columns have been selected, make indices for filtering and set output columns
if select:
indices = [ headers.index(colname) for colname in select ]
headers = select
records = []
for rowno, row in enumerate(rows, 1):
if not row: # Skip rows with no data
continue
# If specific column indices are selected, pick them out
if select:
row = [ row[index] for index in indices]
# Apply type conversion to the row
if types:
try:
row = [func(val) for func, val in zip(types, row)]
except ValueError as e:
if not silence_errors:
print(f"Row {rowno}: Couldn't convert {row}")
print(f"Row {rowno}: Reason {e}")
continue
# Make a dictionary or a tuple
if headers:
record = dict(zip(headers, row))
else:
record = tuple(row)
records.append(record)
return records

30
Solutions/6_7/follow.py Normal file
View File

@@ -0,0 +1,30 @@
# follow.py
import os
import time
def follow(filename):
'''
Generator that produces a sequence of lines being written at the end of a file.
'''
f = open(filename, 'r')
f.seek(0,os.SEEK_END)
while True:
line = f.readline()
if line == '':
time.sleep(0.1) # Sleep briefly to avoid busy wait
continue
yield line
# Example use
if __name__ == '__main__':
import report
portfolio = report.read_portfolio('../../Data/portfolio.csv')
for line in follow('../../Data/stocklog.csv'):
row = line.split(',')
name = row[0].strip('"')
price = float(row[1])
change = float(row[4])
if name in portfolio:
print(f'{name:>10s} {price:>10.2f} {change:>10.2f}')

20
Solutions/6_7/pcost.py Normal file
View File

@@ -0,0 +1,20 @@
# pcost.py
import report
def portfolio_cost(filename):
'''
Computes the total cost (shares*price) of a portfolio file
'''
portfolio = report.read_portfolio(filename)
return portfolio.total_cost
def main(args):
if len(args) != 2:
raise SystemExit('Usage: %s portfoliofile' % args[0])
filename = args[1]
print('Total cost:', portfolio_cost(filename))
if __name__ == '__main__':
import sys
main(sys.argv)

View File

@@ -0,0 +1,31 @@
# portfolio.py
class Portfolio(object):
def __init__(self, holdings):
self._holdings = holdings
def __iter__(self):
return self._holdings.__iter__()
def __len__(self):
return len(self._holdings)
def __getitem__(self, index):
return self._holdings[index]
def __contains__(self, name):
return any([s.name == name for s in self._holdings])
@property
def total_cost(self):
return sum([s.shares * s.price for s in self._holdings])
def tabulate_shares(self):
from collections import Counter
total_shares = Counter()
for s in self._holdings:
total_shares[s.name] += s.shares
return total_shares

72
Solutions/6_7/report.py Normal file
View File

@@ -0,0 +1,72 @@
# report.py
import fileparse
from stock import Stock
import tableformat
from portfolio import Portfolio
def read_portfolio(filename):
'''
Read a stock portfolio file into a list of dictionaries with keys
name, shares, and price.
'''
with open(filename) as lines:
portdicts = fileparse.parse_csv(lines,
select=['name','shares','price'],
types=[str,int,float])
portfolio = [ Stock(d['name'], d['shares'], d['price']) for d in portdicts ]
return Portfolio(portfolio)
def read_prices(filename):
'''
Read a CSV file of price data into a dict mapping names to prices.
'''
with open(filename) as lines:
return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False))
def make_report_data(portfolio, prices):
'''
Make a list of (name, shares, price, change) tuples given a portfolio list
and prices dictionary.
'''
rows = []
for s in portfolio:
current_price = prices[s.name]
change = current_price - s.price
summary = (s.name, s.shares, current_price, change)
rows.append(summary)
return rows
def print_report(reportdata, formatter):
'''
Print a nicely formated table from a list of (name, shares, price, change) tuples.
'''
formatter.headings(['Name','Shares','Price','Change'])
for name, shares, price, change in reportdata:
rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ]
formatter.row(rowdata)
def portfolio_report(portfoliofile, pricefile, fmt='txt'):
'''
Make a stock report given portfolio and price data files.
'''
# Read data files
portfolio = read_portfolio(portfoliofile)
prices = read_prices(pricefile)
# Create the report data
report = make_report_data(portfolio, prices)
# Print it out
formatter = tableformat.create_formatter(fmt)
print_report(report, formatter)
def main(args):
if len(args) != 4:
raise SystemExit('Usage: %s portfile pricefile format' % args[0])
portfolio_report(args[1], args[2], args[3])
if __name__ == '__main__':
import sys
main(sys.argv)

37
Solutions/6_7/stock.py Normal file
View File

@@ -0,0 +1,37 @@
# stock.py
class Stock(object):
'''
An instance of a stock holding consisting of name, shares, and price.
'''
__slots__ = ('name','_shares','price')
def __init__(self,name, shares, price):
self.name = name
self.shares = shares
self.price = price
def __repr__(self):
return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'
@property
def shares(self):
return self._shares
@shares.setter
def shares(self, value):
if not isinstance(value,int):
raise TypeError("Must be integer")
self._shares = value
@property
def cost(self):
'''
Return the cost as shares*price
'''
return self.shares * self.price
def sell(self, nshares):
'''
Sell a number of shares and return the remaining number.
'''
self.shares -= nshares

View File

@@ -0,0 +1,81 @@
# tableformat.py
class TableFormatter(object):
def headings(self, headers):
'''
Emit the table headers
'''
raise NotImplementedError()
def row(self, rowdata):
'''
Emit a single row of table data
'''
raise NotImplementedError()
class TextTableFormatter(TableFormatter):
'''
Output data in plain-text format.
'''
def headings(self, headers):
for h in headers:
print(f'{h:>10s}', end=' ')
print()
print(('-'*10 + ' ')*len(headers))
def row(self, rowdata):
for d in rowdata:
print(f'{d:>10s}', end=' ')
print()
class CSVTableFormatter(TableFormatter):
'''
Output data in CSV format.
'''
def headings(self, headers):
print(','.join(headers))
def row(self, rowdata):
print(','.join(rowdata))
class HTMLTableFormatter(TableFormatter):
'''
Output data in HTML format.
'''
def headings(self, headers):
print('<tr>', end='')
for h in headers:
print(f'<th>{h}</th>', end='')
print('</tr>')
def row(self, rowdata):
print('<tr>', end='')
for d in rowdata:
print(f'<td>{d}</td>', end='')
print('</tr>')
class FormatError(Exception):
pass
def create_formatter(name):
'''
Create an appropriate formatter given an output format name
'''
if name == 'txt':
return TextTableFormatter()
elif name == 'csv':
return CSVTableFormatter()
elif name == 'html':
return HTMLTableFormatter()
else:
raise FormatError(f'Unknown table format {name}')
def print_table(objects, columns, formatter):
'''
Make a nicely formatted table from a list of objects and attribute names.
'''
formatter.headings(columns)
for obj in objects:
rowdata = [ str(getattr(obj, name)) for name in columns ]
formatter.row(rowdata)

View File

@@ -0,0 +1,21 @@
# timethis.py
import time
def timethis(func):
def wrapper(*args, **kwargs):
start = time.time()
try:
return func(*args,**kwargs)
finally:
end = time.time()
print("%s.%s : %f" % (func.__module__,func.__name__,end-start))
return wrapper
if __name__ == '__main__':
@timethis
def countdown(n):
while n > 0:
n-= 1
countdown(1000000)

View File

@@ -0,0 +1,47 @@
# fileparse.py
import csv
def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):
'''
Parse a CSV file into a list of records with type conversion.
'''
if select and not has_headers:
raise RuntimeError('select requires column headers')
rows = csv.reader(lines, delimiter=delimiter)
# Read the file headers (if any)
headers = next(rows) if has_headers else []
# If specific columns have been selected, make indices for filtering and set output columns
if select:
indices = [ headers.index(colname) for colname in select ]
headers = select
records = []
for rowno, row in enumerate(rows, 1):
if not row: # Skip rows with no data
continue
# If specific column indices are selected, pick them out
if select:
row = [ row[index] for index in indices]
# Apply type conversion to the row
if types:
try:
row = [func(val) for func, val in zip(types, row)]
except ValueError as e:
if not silence_errors:
print(f"Row {rowno}: Couldn't convert {row}")
print(f"Row {rowno}: Reason {e}")
continue
# Make a dictionary or a tuple
if headers:
record = dict(zip(headers, row))
else:
record = tuple(row)
records.append(record)
return records

17
Solutions/7_12/follow.py Normal file
View File

@@ -0,0 +1,17 @@
# follow.py
import time
import os
def follow(filename):
'''
Generator that produces a sequence of lines being written at the end of a file.
'''
with open(filename,'r') as f:
f.seek(0,os.SEEK_END)
while True:
line = f.readline()
if line != '':
yield line
else:
time.sleep(0.1) # Sleep briefly to avoid busy wait

20
Solutions/7_12/pcost.py Normal file
View File

@@ -0,0 +1,20 @@
# pcost.py
import report
def portfolio_cost(filename):
'''
Computes the total cost (shares*price) of a portfolio file
'''
portfolio = report.read_portfolio(filename)
return portfolio.total_cost
def main(args):
if len(args) != 2:
raise SystemExit('Usage: %s portfoliofile' % args[0])
filename = args[1]
print('Total cost:', portfolio_cost(filename))
if __name__ == '__main__':
import sys
main(sys.argv)

View File

@@ -0,0 +1,51 @@
# portfolio.py
import fileparse
import stock
class Portfolio(object):
def __init__(self):
self._holdings = []
@classmethod
def from_csv(cls, lines, **opts):
self = cls()
portdicts = fileparse.parse_csv(lines,
select=['name','shares','price'],
types=[str,int,float],
**opts)
for d in portdicts:
self.append(stock.Stock(**d))
return self
def append(self, holding):
self._holdings.append(holding)
def __iter__(self):
return self._holdings.__iter__()
def __len__(self):
return len(self._holdings)
def __getitem__(self, index):
return self._holdings[index]
def __contains__(self, name):
return any(s.name == name for s in self._holdings)
@property
def total_cost(self):
return sum(s.shares * s.price for s in self._holdings)
def tabulate_shares(self):
from collections import Counter
total_shares = Counter()
for s in self._holdings:
total_shares[s.name] += s.shares
return total_shares

67
Solutions/7_12/report.py Normal file
View File

@@ -0,0 +1,67 @@
# report.py
import fileparse
from stock import Stock
from portfolio import Portfolio
import tableformat
def read_portfolio(filename, **opts):
'''
Read a stock portfolio file into a list of dictionaries with keys
name, shares, and price.
'''
with open(filename) as lines:
return Portfolio.from_csv(lines, **opts)
def read_prices(filename, **opts):
'''
Read a CSV file of price data into a dict mapping names to prices.
'''
with open(filename) as lines:
return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False, **opts))
def make_report(portfolio, prices):
'''
Make a list of (name, shares, price, change) tuples given a portfolio list
and prices dictionary.
'''
rows = []
for s in portfolio:
current_price = prices[s.name]
change = current_price - s.price
summary = (s.name, s.shares, current_price, change)
rows.append(summary)
return rows
def print_report(reportdata, formatter):
'''
Print a nicely formated table from a list of (name, shares, price, change) tuples.
'''
formatter.headings(['Name','Shares','Price','Change'])
for name, shares, price, change in reportdata:
rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ]
formatter.row(rowdata)
def portfolio_report(portfoliofile, pricefile, fmt='txt'):
'''
Make a stock report given portfolio and price data files.
'''
# Read data files
portfolio = read_portfolio(portfoliofile)
prices = read_prices(pricefile)
# Create the report data
report = make_report(portfolio, prices)
# Print it out
formatter = tableformat.create_formatter(fmt)
print_report(report, formatter)
def main(args):
if len(args) != 4:
raise SystemExit('Usage: %s portfile pricefile format' % args[0])
portfolio_report(args[1], args[2], args[3])
if __name__ == '__main__':
import sys
main(sys.argv)

32
Solutions/7_12/stock.py Normal file
View File

@@ -0,0 +1,32 @@
# stock.py
from typedproperty import String, Integer, Float
class Stock(object):
'''
An instance of a stock holding consisting of name, shares, and price.
'''
name = String('name')
shares = Integer('shares')
price = Float('price')
def __init__(self,name, shares, price):
self.name = name
self.shares = shares
self.price = price
def __repr__(self):
return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'
@property
def cost(self):
'''
Return the cost as shares*price
'''
return self.shares * self.price
def sell(self, nshares):
'''
Sell a number of shares and return the remaining number.
'''
self.shares -= nshares

View File

@@ -0,0 +1,81 @@
# tableformat.py
class TableFormatter(object):
def headings(self, headers):
'''
Emit the table headers
'''
raise NotImplementedError()
def row(self, rowdata):
'''
Emit a single row of table data
'''
raise NotImplementedError()
class TextTableFormatter(TableFormatter):
'''
Output data in plain-text format.
'''
def headings(self, headers):
for h in headers:
print(f'{h:>10s}', end=' ')
print()
print(('-'*10 + ' ')*len(headers))
def row(self, rowdata):
for d in rowdata:
print(f'{d:>10s}', end=' ')
print()
class CSVTableFormatter(TableFormatter):
'''
Output data in CSV format.
'''
def headings(self, headers):
print(','.join(headers))
def row(self, rowdata):
print(','.join(rowdata))
class HTMLTableFormatter(TableFormatter):
'''
Output data in HTML format.
'''
def headings(self, headers):
print('<tr>', end='')
for h in headers:
print(f'<th>{h}</th>', end='')
print('</tr>')
def row(self, rowdata):
print('<tr>', end='')
for d in rowdata:
print(f'<td>{d}</td>', end='')
print('</tr>')
class FormatError(Exception):
pass
def create_formatter(name):
'''
Create an appropriate formatter given an output format name
'''
if name == 'txt':
return TextTableFormatter()
elif name == 'csv':
return CSVTableFormatter()
elif name == 'html':
return HTMLTableFormatter()
else:
raise FormatError(f'Unknown table format {name}')
def print_table(objects, columns, formatter):
'''
Make a nicely formatted table from a list of objects and attribute names.
'''
formatter.headings(columns)
for obj in objects:
rowdata = [ str(getattr(obj, name)) for name in columns ]
formatter.row(rowdata)

43
Solutions/7_12/ticker.py Normal file
View File

@@ -0,0 +1,43 @@
# ticker.py
import csv
import report
import tableformat
from follow import follow
def select_columns(rows, indices):
for row in rows:
yield [row[index] for index in indices]
def convert_types(rows, types):
for row in rows:
yield [func(val) for func, val in zip(types, row)]
def make_dicts(rows, headers):
return (dict(zip(headers,row)) for row in rows)
def parse_stock_data(lines):
rows = csv.reader(lines)
rows = select_columns(rows, [0, 1, 4])
rows = convert_types(rows, [str,float,float])
rows = make_dicts(rows, ['name','price','change'])
return rows
def ticker(portfile, logfile, fmt):
portfolio = report.read_portfolio(portfile)
lines = follow(logfile)
rows = parse_stock_data(lines)
rows = (row for row in rows if row['name'] in portfolio)
formatter = tableformat.create_formatter(fmt)
formatter.headings(['Name','Price','Change'])
for row in rows:
formatter.row([ row['name'], f"{row['price']:0.2f}", f"{row['change']:0.2f}"] )
def main(args):
if len(args) != 4:
raise SystemExit('Usage: %s portfoliofile logfile fmt' % args[0])
ticker(args[1], args[2], args[3])
if __name__ == '__main__':
import sys
main(sys.argv)

View File

@@ -0,0 +1,21 @@
# timethis.py
import time
def timethis(func):
def wrapper(*args, **kwargs):
start = time.time()
try:
return func(*args,**kwargs)
finally:
end = time.time()
print("%s.%s : %f" % (func.__module__,func.__name__,end-start))
return wrapper
if __name__ == '__main__':
@timethis
def countdown(n):
while n > 0:
n-= 1
countdown(1000000)

View File

@@ -0,0 +1,34 @@
# typedproperty.py
def typedproperty(name, expected_type):
private_name = '_' + name
@property
def prop(self):
return getattr(self, private_name)
@prop.setter
def prop(self, value):
if not isinstance(value, expected_type):
raise TypeError(f'Expected {expected_type}')
setattr(self, private_name, value)
return prop
String = lambda name: typedproperty(name, str)
Integer = lambda name: typedproperty(name, int)
Float = lambda name: typedproperty(name, float)
# Example
if __name__ == '__main__':
class Stock(object):
name = typedproperty('name', str)
shares = typedproperty('shares', int)
price = typedproperty('price', float)
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price

View File

@@ -0,0 +1,47 @@
# fileparse.py
import csv
def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):
'''
Parse a CSV file into a list of records with type conversion.
'''
if select and not has_headers:
raise RuntimeError('select requires column headers')
rows = csv.reader(lines, delimiter=delimiter)
# Read the file headers (if any)
headers = next(rows) if has_headers else []
# If specific columns have been selected, make indices for filtering and set output columns
if select:
indices = [ headers.index(colname) for colname in select ]
headers = select
records = []
for rowno, row in enumerate(rows, 1):
if not row: # Skip rows with no data
continue
# If specific column indices are selected, pick them out
if select:
row = [ row[index] for index in indices]
# Apply type conversion to the row
if types:
try:
row = [func(val) for func, val in zip(types, row)]
except ValueError as e:
if not silence_errors:
print(f"Row {rowno}: Couldn't convert {row}")
print(f"Row {rowno}: Reason {e}")
continue
# Make a dictionary or a tuple
if headers:
record = dict(zip(headers, row))
else:
record = tuple(row)
records.append(record)
return records

17
Solutions/7_4/follow.py Normal file
View File

@@ -0,0 +1,17 @@
# follow.py
import time
import os
def follow(filename):
'''
Generator that produces a sequence of lines being written at the end of a file.
'''
with open(filename,'r') as f:
f.seek(0,os.SEEK_END)
while True:
line = f.readline()
if line != '':
yield line
else:
time.sleep(0.1) # Sleep briefly to avoid busy wait

20
Solutions/7_4/pcost.py Normal file
View File

@@ -0,0 +1,20 @@
# pcost.py
import report
def portfolio_cost(filename):
'''
Computes the total cost (shares*price) of a portfolio file
'''
portfolio = report.read_portfolio(filename)
return portfolio.total_cost
def main(args):
if len(args) != 2:
raise SystemExit('Usage: %s portfoliofile' % args[0])
filename = args[1]
print('Total cost:', portfolio_cost(filename))
if __name__ == '__main__':
import sys
main(sys.argv)

View File

@@ -0,0 +1,31 @@
# portfolio.py
class Portfolio(object):
def __init__(self, holdings):
self._holdings = holdings
def __iter__(self):
return self._holdings.__iter__()
def __len__(self):
return len(self._holdings)
def __getitem__(self, index):
return self._holdings[index]
def __contains__(self, name):
return any(s.name == name for s in self._holdings)
@property
def total_cost(self):
return sum(s.shares * s.price for s in self._holdings)
def tabulate_shares(self):
from collections import Counter
total_shares = Counter()
for s in self._holdings:
total_shares[s.name] += s.shares
return total_shares

73
Solutions/7_4/report.py Normal file
View File

@@ -0,0 +1,73 @@
# report.py
import fileparse
from stock import Stock
import tableformat
from portfolio import Portfolio
def read_portfolio(filename, **opts):
'''
Read a stock portfolio file into a list of dictionaries with keys
name, shares, and price.
'''
with open(filename) as lines:
portdicts = fileparse.parse_csv(lines,
select=['name','shares','price'],
types=[str,int,float],
**opts)
portfolio = [ Stock(**d) for d in portdicts ]
return Portfolio(portfolio)
def read_prices(filename, **opts):
'''
Read a CSV file of price data into a dict mapping names to prices.
'''
with open(filename) as lines:
return dict(fileparse.parse_csv(lines,types=[str,float], has_headers=False, **opts))
def make_report_data(portfolio, prices):
'''
Make a list of (name, shares, price, change) tuples given a portfolio list
and prices dictionary.
'''
rows = []
for s in portfolio:
current_price = prices[s.name]
change = current_price - s.price
summary = (s.name, s.shares, current_price, change)
rows.append(summary)
return rows
def print_report(reportdata, formatter):
'''
Print a nicely formated table from a list of (name, shares, price, change) tuples.
'''
formatter.headings(['Name','Shares','Price','Change'])
for name, shares, price, change in reportdata:
rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ]
formatter.row(rowdata)
def portfolio_report(portfoliofile, pricefile, fmt='txt'):
'''
Make a stock report given portfolio and price data files.
'''
# Read data files
portfolio = read_portfolio(portfoliofile)
prices = read_prices(pricefile)
# Create the report data
report = make_report_data(portfolio, prices)
# Print it out
formatter = tableformat.create_formatter(fmt)
print_report(report, formatter)
def main(args):
if len(args) != 4:
raise SystemExit('Usage: %s portfile pricefile format' % args[0])
portfolio_report(args[1], args[2], args[3])
if __name__ == '__main__':
import sys
main(sys.argv)

37
Solutions/7_4/stock.py Normal file
View File

@@ -0,0 +1,37 @@
# stock.py
class Stock(object):
'''
An instance of a stock holding consisting of name, shares, and price.
'''
__slots__ = ('name','_shares','price')
def __init__(self,name, shares, price):
self.name = name
self.shares = shares
self.price = price
def __repr__(self):
return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'
@property
def shares(self):
return self._shares
@shares.setter
def shares(self, value):
if not isinstance(value,int):
raise TypeError("Must be integer")
self._shares = value
@property
def cost(self):
'''
Return the cost as shares*price
'''
return self.shares * self.price
def sell(self, nshares):
'''
Sell a number of shares and return the remaining number.
'''
self.shares -= nshares

View File

@@ -0,0 +1,81 @@
# tableformat.py
class TableFormatter(object):
def headings(self, headers):
'''
Emit the table headers
'''
raise NotImplementedError()
def row(self, rowdata):
'''
Emit a single row of table data
'''
raise NotImplementedError()
class TextTableFormatter(TableFormatter):
'''
Output data in plain-text format.
'''
def headings(self, headers):
for h in headers:
print(f'{h:>10s}', end=' ')
print()
print(('-'*10 + ' ')*len(headers))
def row(self, rowdata):
for d in rowdata:
print(f'{d:>10s}', end=' ')
print()
class CSVTableFormatter(TableFormatter):
'''
Output data in CSV format.
'''
def headings(self, headers):
print(','.join(headers))
def row(self, rowdata):
print(','.join(rowdata))
class HTMLTableFormatter(TableFormatter):
'''
Output data in HTML format.
'''
def headings(self, headers):
print('<tr>', end='')
for h in headers:
print(f'<th>{h}</th>', end='')
print('</tr>')
def row(self, rowdata):
print('<tr>', end='')
for d in rowdata:
print(f'<td>{d}</td>', end='')
print('</tr>')
class FormatError(Exception):
pass
def create_formatter(name):
'''
Create an appropriate formatter given an output format name
'''
if name == 'txt':
return TextTableFormatter()
elif name == 'csv':
return CSVTableFormatter()
elif name == 'html':
return HTMLTableFormatter()
else:
raise FormatError(f'Unknown table format {name}')
def print_table(objects, columns, formatter):
'''
Make a nicely formatted table from a list of objects and attribute names.
'''
formatter.headings(columns)
for obj in objects:
rowdata = [ str(getattr(obj, name)) for name in columns ]
formatter.row(rowdata)

44
Solutions/7_4/ticker.py Normal file
View File

@@ -0,0 +1,44 @@
# ticker.py
import csv
import report
import tableformat
from follow import follow
import time
def select_columns(rows, indices):
for row in rows:
yield [row[index] for index in indices]
def convert_types(rows, types):
for row in rows:
yield [func(val) for func, val in zip(types, row)]
def make_dicts(rows, headers):
return (dict(zip(headers,row)) for row in rows)
def parse_stock_data(lines):
rows = csv.reader(lines)
rows = select_columns(rows, [0, 1, 4])
rows = convert_types(rows, [str,float,float])
rows = make_dicts(rows, ['name','price','change'])
return rows
def ticker(portfile, logfile, fmt):
portfolio = report.read_portfolio(portfile)
lines = follow(logfile)
rows = parse_stock_data(lines)
rows = (row for row in rows if row['name'] in portfolio)
formatter = tableformat.create_formatter(fmt)
formatter.headings(['Name','Price','Change'])
for row in rows:
formatter.row([ row['name'], f"{row['price']:0.2f}", f"{row['change']:0.2f}"] )
def main(args):
if len(args) != 4:
raise SystemExit('Usage: %s portfoliofile logfile fmt' % args[0])
ticker(args[1], args[2], args[3])
if __name__ == '__main__':
import sys
main(sys.argv)

View File

@@ -0,0 +1,47 @@
# fileparse.py
import csv
def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):
'''
Parse a CSV file into a list of records with type conversion.
'''
if select and not has_headers:
raise RuntimeError('select requires column headers')
rows = csv.reader(lines, delimiter=delimiter)
# Read the file headers (if any)
headers = next(rows) if has_headers else []
# If specific columns have been selected, make indices for filtering and set output columns
if select:
indices = [ headers.index(colname) for colname in select ]
headers = select
records = []
for rowno, row in enumerate(rows, 1):
if not row: # Skip rows with no data
continue
# If specific column indices are selected, pick them out
if select:
row = [ row[index] for index in indices]
# Apply type conversion to the row
if types:
try:
row = [func(val) for func, val in zip(types, row)]
except ValueError as e:
if not silence_errors:
print(f"Row {rowno}: Couldn't convert {row}")
print(f"Row {rowno}: Reason {e}")
continue
# Make a dictionary or a tuple
if headers:
record = dict(zip(headers, row))
else:
record = tuple(row)
records.append(record)
return records

17
Solutions/7_9/follow.py Normal file
View File

@@ -0,0 +1,17 @@
# follow.py
import time
import os
def follow(filename):
'''
Generator that produces a sequence of lines being written at the end of a file.
'''
with open(filename,'r') as f:
f.seek(0,os.SEEK_END)
while True:
line = f.readline()
if line != '':
yield line
else:
time.sleep(0.1) # Sleep briefly to avoid busy wait

20
Solutions/7_9/pcost.py Normal file
View File

@@ -0,0 +1,20 @@
# pcost.py
import report
def portfolio_cost(filename):
'''
Computes the total cost (shares*price) of a portfolio file
'''
portfolio = report.read_portfolio(filename)
return portfolio.total_cost
def main(args):
if len(args) != 2:
raise SystemExit('Usage: %s portfoliofile' % args[0])
filename = args[1]
print('Total cost:', portfolio_cost(filename))
if __name__ == '__main__':
import sys
main(sys.argv)

View File

@@ -0,0 +1,31 @@
# portfolio.py
class Portfolio(object):
def __init__(self, holdings):
self._holdings = holdings
def __iter__(self):
return self._holdings.__iter__()
def __len__(self):
return len(self._holdings)
def __getitem__(self, index):
return self._holdings[index]
def __contains__(self, name):
return any(s.name == name for s in self._holdings)
@property
def total_cost(self):
return sum(s.shares * s.price for s in self._holdings)
def tabulate_shares(self):
from collections import Counter
total_shares = Counter()
for s in self._holdings:
total_shares[s.name] += s.shares
return total_shares

73
Solutions/7_9/report.py Normal file
View File

@@ -0,0 +1,73 @@
# report.py
import fileparse
from stock import Stock
import tableformat
from portfolio import Portfolio
def read_portfolio(filename, **opts):
'''
Read a stock portfolio file into a list of dictionaries with keys
name, shares, and price.
'''
with open(filename) as lines:
portdicts = fileparse.parse_csv(lines,
select=['name','shares','price'],
types=[str,int,float],
**opts)
portfolio = [ Stock(**d) for d in portdicts ]
return Portfolio(portfolio)
def read_prices(filename, **opts):
'''
Read a CSV file of price data into a dict mapping names to prices.
'''
with open(filename) as lines:
return dict(fileparse.parse_csv(lines,types=[str,float], has_headers=False, **opts))
def make_report_data(portfolio, prices):
'''
Make a list of (name, shares, price, change) tuples given a portfolio list
and prices dictionary.
'''
rows = []
for s in portfolio:
current_price = prices[s.name]
change = current_price - s.price
summary = (s.name, s.shares, current_price, change)
rows.append(summary)
return rows
def print_report(reportdata, formatter):
'''
Print a nicely formated table from a list of (name, shares, price, change) tuples.
'''
formatter.headings(['Name','Shares','Price','Change'])
for name, shares, price, change in reportdata:
rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ]
formatter.row(rowdata)
def portfolio_report(portfoliofile, pricefile, fmt='txt'):
'''
Make a stock report given portfolio and price data files.
'''
# Read data files
portfolio = read_portfolio(portfoliofile)
prices = read_prices(pricefile)
# Create the report data
report = make_report_data(portfolio, prices)
# Print it out
formatter = tableformat.create_formatter(fmt)
print_report(report, formatter)
def main(args):
if len(args) != 4:
raise SystemExit('Usage: %s portfile pricefile format' % args[0])
portfolio_report(args[1], args[2], args[3])
if __name__ == '__main__':
import sys
main(sys.argv)

32
Solutions/7_9/stock.py Normal file
View File

@@ -0,0 +1,32 @@
# stock.py
from typedproperty import String, Integer, Float
class Stock(object):
'''
An instance of a stock holding consisting of name, shares, and price.
'''
name = String('name')
shares = Integer('shares')
price = Float('price')
def __init__(self,name, shares, price):
self.name = name
self.shares = shares
self.price = price
def __repr__(self):
return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'
@property
def cost(self):
'''
Return the cost as shares*price
'''
return self.shares * self.price
def sell(self, nshares):
'''
Sell a number of shares and return the remaining number.
'''
self.shares -= nshares

View File

@@ -0,0 +1,81 @@
# tableformat.py
class TableFormatter(object):
def headings(self, headers):
'''
Emit the table headers
'''
raise NotImplementedError()
def row(self, rowdata):
'''
Emit a single row of table data
'''
raise NotImplementedError()
class TextTableFormatter(TableFormatter):
'''
Output data in plain-text format.
'''
def headings(self, headers):
for h in headers:
print(f'{h:>10s}', end=' ')
print()
print(('-'*10 + ' ')*len(headers))
def row(self, rowdata):
for d in rowdata:
print(f'{d:>10s}', end=' ')
print()
class CSVTableFormatter(TableFormatter):
'''
Output data in CSV format.
'''
def headings(self, headers):
print(','.join(headers))
def row(self, rowdata):
print(','.join(rowdata))
class HTMLTableFormatter(TableFormatter):
'''
Output data in HTML format.
'''
def headings(self, headers):
print('<tr>', end='')
for h in headers:
print(f'<th>{h}</th>', end='')
print('</tr>')
def row(self, rowdata):
print('<tr>', end='')
for d in rowdata:
print(f'<td>{d}</td>', end='')
print('</tr>')
class FormatError(Exception):
pass
def create_formatter(name):
'''
Create an appropriate formatter given an output format name
'''
if name == 'txt':
return TextTableFormatter()
elif name == 'csv':
return CSVTableFormatter()
elif name == 'html':
return HTMLTableFormatter()
else:
raise FormatError(f'Unknown table format {name}')
def print_table(objects, columns, formatter):
'''
Make a nicely formatted table from a list of objects and attribute names.
'''
formatter.headings(columns)
for obj in objects:
rowdata = [ str(getattr(obj, name)) for name in columns ]
formatter.row(rowdata)

44
Solutions/7_9/ticker.py Normal file
View File

@@ -0,0 +1,44 @@
# ticker.py
import csv
import report
import tableformat
from follow import follow
import time
def select_columns(rows, indices):
for row in rows:
yield [row[index] for index in indices]
def convert_types(rows, types):
for row in rows:
yield [func(val) for func, val in zip(types, row)]
def make_dicts(rows, headers):
return (dict(zip(headers,row)) for row in rows)
def parse_stock_data(lines):
rows = csv.reader(lines)
rows = select_columns(rows, [0, 1, 4])
rows = convert_types(rows, [str,float,float])
rows = make_dicts(rows, ['name','price','change'])
return rows
def ticker(portfile, logfile, fmt):
portfolio = report.read_portfolio(portfile)
lines = follow(logfile)
rows = parse_stock_data(lines)
rows = (row for row in rows if row['name'] in portfolio)
formatter = tableformat.create_formatter(fmt)
formatter.headings(['Name','Price','Change'])
for row in rows:
formatter.row([ row['name'], f"{row['price']:0.2f}", f"{row['change']:0.2f}"] )
def main(args):
if len(args) != 4:
raise SystemExit('Usage: %s portfoliofile logfile fmt' % args[0])
ticker(args[1], args[2], args[3])
if __name__ == '__main__':
import sys
main(sys.argv)

View File

@@ -0,0 +1,34 @@
# typedproperty.py
def typedproperty(name, expected_type):
private_name = '_' + name
@property
def prop(self):
return getattr(self, private_name)
@prop.setter
def prop(self, value):
if not isinstance(value, expected_type):
raise TypeError(f'Expected {expected_type}')
setattr(self, private_name, value)
return prop
String = lambda name: typedproperty(name, str)
Integer = lambda name: typedproperty(name, int)
Float = lambda name: typedproperty(name, float)
# Example
if __name__ == '__main__':
class Stock(object):
name = typedproperty('name', str)
shares = typedproperty('shares', int)
price = typedproperty('price', float)
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price

View File

@@ -0,0 +1,47 @@
# fileparse.py
import csv
def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):
'''
Parse a CSV file into a list of records with type conversion.
'''
if select and not has_headers:
raise RuntimeError('select requires column headers')
rows = csv.reader(lines, delimiter=delimiter)
# Read the file headers (if any)
headers = next(rows) if has_headers else []
# If specific columns have been selected, make indices for filtering and set output columns
if select:
indices = [ headers.index(colname) for colname in select ]
headers = select
records = []
for rowno, row in enumerate(rows, 1):
if not row: # Skip rows with no data
continue
# If specific column indices are selected, pick them out
if select:
row = [ row[index] for index in indices]
# Apply type conversion to the row
if types:
try:
row = [func(val) for func, val in zip(types, row)]
except ValueError as e:
if not silence_errors:
print(f"Row {rowno}: Couldn't convert {row}")
print(f"Row {rowno}: Reason {e}")
continue
# Make a dictionary or a tuple
if headers:
record = dict(zip(headers, row))
else:
record = tuple(row)
records.append(record)
return records

17
Solutions/8_1/follow.py Normal file
View File

@@ -0,0 +1,17 @@
# follow.py
import time
import os
def follow(filename):
'''
Generator that produces a sequence of lines being written at the end of a file.
'''
with open(filename,'r') as f:
f.seek(0,os.SEEK_END)
while True:
line = f.readline()
if line != '':
yield line
else:
time.sleep(0.1) # Sleep briefly to avoid busy wait

20
Solutions/8_1/pcost.py Normal file
View File

@@ -0,0 +1,20 @@
# pcost.py
import report
def portfolio_cost(filename):
'''
Computes the total cost (shares*price) of a portfolio file
'''
portfolio = report.read_portfolio(filename)
return portfolio.total_cost
def main(args):
if len(args) != 2:
raise SystemExit('Usage: %s portfoliofile' % args[0])
filename = args[1]
print('Total cost:', portfolio_cost(filename))
if __name__ == '__main__':
import sys
main(sys.argv)

View File

@@ -0,0 +1,51 @@
# portfolio.py
import fileparse
import stock
class Portfolio(object):
def __init__(self):
self._holdings = []
@classmethod
def from_csv(cls, lines, **opts):
self = cls()
portdicts = fileparse.parse_csv(lines,
select=['name','shares','price'],
types=[str,int,float],
**opts)
for d in portdicts:
self.append(stock.Stock(**d))
return self
def append(self, holding):
self._holdings.append(holding)
def __iter__(self):
return self._holdings.__iter__()
def __len__(self):
return len(self._holdings)
def __getitem__(self, index):
return self._holdings[index]
def __contains__(self, name):
return any(s.name == name for s in self._holdings)
@property
def total_cost(self):
return sum(s.shares * s.price for s in self._holdings)
def tabulate_shares(self):
from collections import Counter
total_shares = Counter()
for s in self._holdings:
total_shares[s.name] += s.shares
return total_shares

67
Solutions/8_1/report.py Normal file
View File

@@ -0,0 +1,67 @@
# report.py
import fileparse
from stock import Stock
from portfolio import Portfolio
import tableformat
def read_portfolio(filename, **opts):
'''
Read a stock portfolio file into a list of dictionaries with keys
name, shares, and price.
'''
with open(filename) as lines:
return Portfolio.from_csv(lines, **opts)
def read_prices(filename, **opts):
'''
Read a CSV file of price data into a dict mapping names to prices.
'''
with open(filename) as lines:
return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False, **opts))
def make_report(portfolio, prices):
'''
Make a list of (name, shares, price, change) tuples given a portfolio list
and prices dictionary.
'''
rows = []
for s in portfolio:
current_price = prices[s.name]
change = current_price - s.price
summary = (s.name, s.shares, current_price, change)
rows.append(summary)
return rows
def print_report(reportdata, formatter):
'''
Print a nicely formated table from a list of (name, shares, price, change) tuples.
'''
formatter.headings(['Name','Shares','Price','Change'])
for name, shares, price, change in reportdata:
rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ]
formatter.row(rowdata)
def portfolio_report(portfoliofile, pricefile, fmt='txt'):
'''
Make a stock report given portfolio and price data files.
'''
# Read data files
portfolio = read_portfolio(portfoliofile)
prices = read_prices(pricefile)
# Create the report data
report = make_report(portfolio, prices)
# Print it out
formatter = tableformat.create_formatter(fmt)
print_report(report, formatter)
def main(args):
if len(args) != 4:
raise SystemExit('Usage: %s portfile pricefile format' % args[0])
portfolio_report(args[1], args[2], args[3])
if __name__ == '__main__':
import sys
main(sys.argv)

32
Solutions/8_1/stock.py Normal file
View File

@@ -0,0 +1,32 @@
# stock.py
from typedproperty import String, Integer, Float
class Stock(object):
'''
An instance of a stock holding consisting of name, shares, and price.
'''
name = String('name')
shares = Integer('shares')
price = Float('price')
def __init__(self,name, shares, price):
self.name = name
self.shares = shares
self.price = price
def __repr__(self):
return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'
@property
def cost(self):
'''
Return the cost as shares*price
'''
return self.shares * self.price
def sell(self, nshares):
'''
Sell a number of shares and return the remaining number.
'''
self.shares -= nshares

View File

@@ -0,0 +1,81 @@
# tableformat.py
class TableFormatter(object):
def headings(self, headers):
'''
Emit the table headers
'''
raise NotImplementedError()
def row(self, rowdata):
'''
Emit a single row of table data
'''
raise NotImplementedError()
class TextTableFormatter(TableFormatter):
'''
Output data in plain-text format.
'''
def headings(self, headers):
for h in headers:
print(f'{h:>10s}', end=' ')
print()
print(('-'*10 + ' ')*len(headers))
def row(self, rowdata):
for d in rowdata:
print(f'{d:>10s}', end=' ')
print()
class CSVTableFormatter(TableFormatter):
'''
Output data in CSV format.
'''
def headings(self, headers):
print(','.join(headers))
def row(self, rowdata):
print(','.join(rowdata))
class HTMLTableFormatter(TableFormatter):
'''
Output data in HTML format.
'''
def headings(self, headers):
print('<tr>', end='')
for h in headers:
print(f'<th>{h}</th>', end='')
print('</tr>')
def row(self, rowdata):
print('<tr>', end='')
for d in rowdata:
print(f'<td>{d}</td>', end='')
print('</tr>')
class FormatError(Exception):
pass
def create_formatter(name):
'''
Create an appropriate formatter given an output format name
'''
if name == 'txt':
return TextTableFormatter()
elif name == 'csv':
return CSVTableFormatter()
elif name == 'html':
return HTMLTableFormatter()
else:
raise FormatError(f'Unknown table format {name}')
def print_table(objects, columns, formatter):
'''
Make a nicely formatted table from a list of objects and attribute names.
'''
formatter.headings(columns)
for obj in objects:
rowdata = [ str(getattr(obj, name)) for name in columns ]
formatter.row(rowdata)

View File

@@ -0,0 +1,28 @@
# test_stock.py
import unittest
import stock
class TestStock(unittest.TestCase):
def test_create(self):
s = stock.Stock('GOOG', 100, 490.1)
self.assertEqual(s.name, 'GOOG')
self.assertEqual(s.shares, 100)
self.assertEqual(s.price, 490.1)
def test_cost(self):
s = stock.Stock('GOOG', 100, 490.1)
self.assertEqual(s.cost, 49010.0)
def test_sell(self):
s = stock.Stock('GOOG', 100, 490.1)
s.sell(25)
self.assertEqual(s.shares, 75)
def test_shares_check(self):
s = stock.Stock('GOOG', 100, 490.1)
with self.assertRaises(TypeError):
s.shares = '100'
if __name__ == '__main__':
unittest.main()

43
Solutions/8_1/ticker.py Normal file
View File

@@ -0,0 +1,43 @@
# ticker.py
import csv
import report
import tableformat
from follow import follow
def select_columns(rows, indices):
for row in rows:
yield [row[index] for index in indices]
def convert_types(rows, types):
for row in rows:
yield [func(val) for func, val in zip(types, row)]
def make_dicts(rows, headers):
return (dict(zip(headers,row)) for row in rows)
def parse_stock_data(lines):
rows = csv.reader(lines)
rows = select_columns(rows, [0, 1, 4])
rows = convert_types(rows, [str,float,float])
rows = make_dicts(rows, ['name','price','change'])
return rows
def ticker(portfile, logfile, fmt):
portfolio = report.read_portfolio(portfile)
lines = follow(logfile)
rows = parse_stock_data(lines)
rows = (row for row in rows if row['name'] in portfolio)
formatter = tableformat.create_formatter(fmt)
formatter.headings(['Name','Price','Change'])
for row in rows:
formatter.row([ row['name'], f"{row['price']:0.2f}", f"{row['change']:0.2f}"] )
def main(args):
if len(args) != 4:
raise SystemExit('Usage: %s portfoliofile logfile fmt' % args[0])
ticker(args[1], args[2], args[3])
if __name__ == '__main__':
import sys
main(sys.argv)

Some files were not shown because too many files have changed in this diff Show More