From 6f214cd8c0030d11bd33e01cd6a239f3c630686e Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sun, 28 Jun 2020 21:47:05 +0200 Subject: [PATCH 01/58] Exercise 1.5 --- Work/bounce.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Work/bounce.py b/Work/bounce.py index 3660ddd82..cbb24bf88 100644 --- a/Work/bounce.py +++ b/Work/bounce.py @@ -1,3 +1,10 @@ -# bounce.py -# # Exercise 1.5 + +def bounce(height): + return 3/5 * height + +height = 100 + +for i in range(1, 11): + height = bounce(height) + print(i, round(height, 4)) From 3207612ce3f5b572bc9a67403fa9b8e3383b3a30 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sun, 28 Jun 2020 21:48:40 +0200 Subject: [PATCH 02/58] Exercise 1.6 --- Work/sears.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 Work/sears.py diff --git a/Work/sears.py b/Work/sears.py new file mode 100644 index 000000000..a69e6a04f --- /dev/null +++ b/Work/sears.py @@ -0,0 +1,15 @@ +# Exercise 1.6 + +bill_thickness = 0.11 * 0.001 # Meters (0.11 mm) +sears_height = 442 # Height (meters) +num_bills = 1 +day = 1 + +while num_bills * bill_thickness < sears_height: + print(day, num_bills, num_bills * bill_thickness) + day = day + 1 + num_bills = num_bills * 2 + +print('Number of days', day) +print('Number of bills', num_bills) +print('Final height', num_bills * bill_thickness) From 6d58b5304b5b3f6a3c6ab1e2df264bffaf1263fd Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sun, 28 Jun 2020 21:50:16 +0200 Subject: [PATCH 03/58] Exercises 1.7-1.11 --- Work/mortgage.py | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/Work/mortgage.py b/Work/mortgage.py index d527314e3..df27aa6e9 100644 --- a/Work/mortgage.py +++ b/Work/mortgage.py @@ -1,3 +1,26 @@ -# mortgage.py -# -# Exercise 1.7 +# Exercises 1.7-1.11 + +principal = 500000.0 +rate = 0.05 +payment = 2684.11 +total_paid = 0.0 +month = 1 + +extra_payment_start_month = 61 +extra_payment_end_month = 108 +extra_payment = 1000.0 + +while principal > payment: + principal = principal * (1 + rate/12) - payment + total_paid += payment + if extra_payment_start_month <= month <= extra_payment_end_month: + principal -= extra_payment + total_paid += extra_payment + print(month, round(total_paid, 2), round(principal, 2)) + month += 1 + +total_paid += principal +principal = 0 +print(month, round(total_paid, 2), round(principal, 2)) + +print('Total paid', round(total_paid, 2), 'over', month, 'months') From 04e09a7dfa7a6da71959fdc03c66f6cc1c61881b Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sat, 4 Jul 2020 13:21:16 +0200 Subject: [PATCH 04/58] Exercise 1.17 --- Work/mortgage.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Work/mortgage.py b/Work/mortgage.py index df27aa6e9..59ea1f544 100644 --- a/Work/mortgage.py +++ b/Work/mortgage.py @@ -1,4 +1,4 @@ -# Exercises 1.7-1.11 +# Exercises 1.7-1.11, 1.17 principal = 500000.0 rate = 0.05 @@ -16,11 +16,9 @@ if extra_payment_start_month <= month <= extra_payment_end_month: principal -= extra_payment total_paid += extra_payment - print(month, round(total_paid, 2), round(principal, 2)) + print(f"{month:5d}{total_paid:12.2f}{principal:12.2f}") month += 1 total_paid += principal principal = 0 -print(month, round(total_paid, 2), round(principal, 2)) - -print('Total paid', round(total_paid, 2), 'over', month, 'months') +print(f"{month:5d}{total_paid:12.2f}{principal:12.2f}") From 5447c2e85f572cd0df6066e9ef97e8c69f04f1b2 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sat, 4 Jul 2020 13:21:57 +0200 Subject: [PATCH 05/58] Exercises 1.27, 1.30, and 1.31 --- Work/pcost.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/Work/pcost.py b/Work/pcost.py index e68aa20b4..d0adc68c5 100644 --- a/Work/pcost.py +++ b/Work/pcost.py @@ -1,3 +1,23 @@ -# pcost.py -# -# Exercise 1.27 +# Exercises 1.27, 1.30, 1.31 + +from sys import stderr + +def portfolio_cost(filename): + portfolio_price = 0 + with open(filename, "rt") as file: + # Skip header + next(file) + for line in file: + try: + line = line.split(",") + name = line[0].strip('"') + shares = int(line[1]) + price = float(line[2]) + purchase_price = shares * price + print(f"{name:5}: {shares:3} x {price:5.2f} = {purchase_price:8.2f}") + portfolio_price += purchase_price + except ValueError: + print("Warning: skipping", line, file=stderr) + return portfolio_price + +print("\n\u2211", portfolio_cost("Data/portfolio.csv")) From 0dadd8757985f73aac59011a1a83f2ae0f80625d Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sat, 4 Jul 2020 13:33:43 +0200 Subject: [PATCH 06/58] Exercise 1.32 --- Work/pcost.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Work/pcost.py b/Work/pcost.py index d0adc68c5..a012c20b7 100644 --- a/Work/pcost.py +++ b/Work/pcost.py @@ -1,16 +1,17 @@ -# Exercises 1.27, 1.30, 1.31 +# Exercises 1.27, 1.30-1.32 +import csv from sys import stderr def portfolio_cost(filename): portfolio_price = 0 with open(filename, "rt") as file: + lines = csv.reader(file) # Skip header - next(file) - for line in file: + next(lines) + for line in lines: try: - line = line.split(",") - name = line[0].strip('"') + name = line[0] shares = int(line[1]) price = float(line[2]) purchase_price = shares * price From 79cb3991979ff8890c0e30244bca6e1e2541402d Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sat, 4 Jul 2020 13:39:33 +0200 Subject: [PATCH 07/58] Exercise 1.33 --- Work/pcost.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Work/pcost.py b/Work/pcost.py index a012c20b7..77ccd087c 100644 --- a/Work/pcost.py +++ b/Work/pcost.py @@ -1,7 +1,7 @@ -# Exercises 1.27, 1.30-1.32 +# Exercises 1.27, 1.30-1.33 import csv -from sys import stderr +import sys def portfolio_cost(filename): portfolio_price = 0 @@ -18,7 +18,12 @@ def portfolio_cost(filename): print(f"{name:5}: {shares:3} x {price:5.2f} = {purchase_price:8.2f}") portfolio_price += purchase_price except ValueError: - print("Warning: skipping", line, file=stderr) + print("Warning: skipping", line, file=sys.stderr) return portfolio_price -print("\n\u2211", portfolio_cost("Data/portfolio.csv")) +if len(sys.argv) == 2: + filename = sys.argv[1] +else: + filename = "Data/portfolio.csv" + +print("\n\u2211", portfolio_cost(filename)) From 5f0e4d59777255765aa730d81010f8d866b63d3e Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sun, 12 Jul 2020 14:01:38 +0200 Subject: [PATCH 08/58] Exercise 2.4 --- Work/report.py | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/Work/report.py b/Work/report.py index 47d5da7b1..5aa2f6e11 100644 --- a/Work/report.py +++ b/Work/report.py @@ -1,3 +1,35 @@ -# report.py -# # Exercise 2.4 + +import csv +import sys + +def read_portfolio(filename): + portfolio = [] + with open(filename, "rt") as file: + lines = csv.reader(file) + # Skip header + next(lines) + for line in lines: + try: + portfolio.append( + (line[0], int(line[1]), float(line[2])) + ) + except ValueError: + print("Warning: skipping", line, file=sys.stderr) + return portfolio + +def portfolio_cost(filename): + portfolio = read_portfolio(filename) + portfolio_price = 0 + for name, shares, price in portfolio: + purchase_price = shares * price + print(f"{name:5}: {shares:3} x {price:5.2f} = {purchase_price:8.2f}") + portfolio_price += purchase_price + return portfolio_price + +if len(sys.argv) == 2: + filename = sys.argv[1] +else: + filename = "Data/portfolio.csv" + +print("\n\u2211", portfolio_cost(filename)) From 268c3705a13c9a9dca8730aa4aa3363086816953 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sun, 12 Jul 2020 14:17:57 +0200 Subject: [PATCH 09/58] Exercise 2.5 --- Work/report.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Work/report.py b/Work/report.py index 5aa2f6e11..2e444983d 100644 --- a/Work/report.py +++ b/Work/report.py @@ -1,4 +1,4 @@ -# Exercise 2.4 +# Exercises 2.4, 2.5 import csv import sys @@ -12,7 +12,11 @@ def read_portfolio(filename): for line in lines: try: portfolio.append( - (line[0], int(line[1]), float(line[2])) + { + "name": line[0], + "shares": int(line[1]), + "price": float(line[2]) + } ) except ValueError: print("Warning: skipping", line, file=sys.stderr) @@ -21,7 +25,8 @@ def read_portfolio(filename): def portfolio_cost(filename): portfolio = read_portfolio(filename) portfolio_price = 0 - for name, shares, price in portfolio: + for holding in portfolio: + name, shares, price = holding["name"], holding["shares"], holding["price"] purchase_price = shares * price print(f"{name:5}: {shares:3} x {price:5.2f} = {purchase_price:8.2f}") portfolio_price += purchase_price From df771f55d6ed55843afa8e19262a9360fb07d6f7 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sun, 12 Jul 2020 15:17:05 +0200 Subject: [PATCH 10/58] Exercises 2.6 and 2.7 --- Work/report.py | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/Work/report.py b/Work/report.py index 2e444983d..b33bc4b9f 100644 --- a/Work/report.py +++ b/Work/report.py @@ -1,8 +1,20 @@ -# Exercises 2.4, 2.5 +# Exercises 2.4-2.7 +from copy import deepcopy import csv import sys +def read_prices(filename): + prices = {} + with open(filename, "rt") as file: + lines = csv.reader(file) + for line in lines: + try: + prices[line[0]] = float(line[1]) + except (IndexError, ValueError): + print("Warning: skipping", line, file=sys.stderr) + return prices + def read_portfolio(filename): portfolio = [] with open(filename, "rt") as file: @@ -22,19 +34,32 @@ def read_portfolio(filename): print("Warning: skipping", line, file=sys.stderr) return portfolio -def portfolio_cost(filename): - portfolio = read_portfolio(filename) +def portfolio_cost(portfolio): portfolio_price = 0 for holding in portfolio: name, shares, price = holding["name"], holding["shares"], holding["price"] purchase_price = shares * price - print(f"{name:5}: {shares:3} x {price:5.2f} = {purchase_price:8.2f}") + print(f"{name:5}: {shares:3} x {price:6.2f} = {purchase_price:8.2f}") portfolio_price += purchase_price return portfolio_price +def portfolio_value(portfolio, prices): + portfolio = deepcopy(portfolio) + for holding in portfolio: + holding["price"] = prices[holding["name"]] + return portfolio_cost(portfolio) + if len(sys.argv) == 2: filename = sys.argv[1] else: filename = "Data/portfolio.csv" -print("\n\u2211", portfolio_cost(filename)) +portfolio = read_portfolio(filename) +cost = portfolio_cost(portfolio) +print(f"\n\u2211 {cost:.2f}\n") + +prices = read_prices("Data/prices.csv") +value = portfolio_value(portfolio, prices) +print(f"\n\u2211 {value:.2f}\n") + +print(f"{value:.2f} - {cost:.2f} = {value - cost:.2f}") From 245eadd9aa1f26a53609ca2ffc830d3a525ace5c Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sat, 25 Jul 2020 14:49:17 +0200 Subject: [PATCH 11/58] Exercises 2.9-2.12 --- Work/report.py | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/Work/report.py b/Work/report.py index b33bc4b9f..eae1a6f52 100644 --- a/Work/report.py +++ b/Work/report.py @@ -1,4 +1,5 @@ # Exercises 2.4-2.7 +# Exercises 2.9-2.12 from copy import deepcopy import csv @@ -39,7 +40,7 @@ def portfolio_cost(portfolio): for holding in portfolio: name, shares, price = holding["name"], holding["shares"], holding["price"] purchase_price = shares * price - print(f"{name:5}: {shares:3} x {price:6.2f} = {purchase_price:8.2f}") + print(f"{name:5}: {shares:3} x {price:6.2f} = {purchase_price:10,.2f}") portfolio_price += purchase_price return portfolio_price @@ -49,17 +50,41 @@ def portfolio_value(portfolio, prices): holding["price"] = prices[holding["name"]] return portfolio_cost(portfolio) +def make_report(portfolio, prices): + report = [] + for holding in portfolio: + report.append( + { + "name": holding["name"], + "shares": holding["shares"], + "price": prices[holding["name"]], + "change": prices[holding["name"]] - holding["price"] + } + ) + return report + +def print_report(portfolio, prices): + headers = ("Name", "Shares", "Price", "Change") + hlines = (10 * "-", 10 * "-", 10 * "-", 10 * "-") + print("%10s %10s %10s %10s" % headers) + print(" ".join(hlines)) + for line in make_report(portfolio, prices): + line["price"] = "$%.2f" % line["price"] + print("{name:>10s} {shares:>10d} {price:>10s} {change:>+10.2f}".format_map(line)) + if len(sys.argv) == 2: filename = sys.argv[1] else: filename = "Data/portfolio.csv" portfolio = read_portfolio(filename) -cost = portfolio_cost(portfolio) -print(f"\n\u2211 {cost:.2f}\n") +#cost = portfolio_cost(portfolio) +#print(f"\n\u2211 {cost:,.2f}\n") prices = read_prices("Data/prices.csv") -value = portfolio_value(portfolio, prices) -print(f"\n\u2211 {value:.2f}\n") +#value = portfolio_value(portfolio, prices) +#print(f"\n\u2211 {value:,.2f}\n") + +#print(f"{value:,.2f} - {cost:,.2f} = {value - cost:,.2f}") -print(f"{value:.2f} - {cost:.2f} = {value - cost:.2f}") +print_report(portfolio, prices) From 2d4712935451bc9b935e8edcad00c995e7394c70 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sun, 26 Jul 2020 09:52:27 +0200 Subject: [PATCH 12/58] Update report.py --- Work/report.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Work/report.py b/Work/report.py index eae1a6f52..810315951 100644 --- a/Work/report.py +++ b/Work/report.py @@ -65,9 +65,8 @@ def make_report(portfolio, prices): def print_report(portfolio, prices): headers = ("Name", "Shares", "Price", "Change") - hlines = (10 * "-", 10 * "-", 10 * "-", 10 * "-") print("%10s %10s %10s %10s" % headers) - print(" ".join(hlines)) + print(" ".join((10 * "-",) * 4)) for line in make_report(portfolio, prices): line["price"] = "$%.2f" % line["price"] print("{name:>10s} {shares:>10d} {price:>10s} {change:>+10.2f}".format_map(line)) From d51c9d36376815fa3126d7b6a94855c53aed4a17 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sun, 26 Jul 2020 09:57:21 +0200 Subject: [PATCH 13/58] Exercise 2.15 --- Work/pcost.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Work/pcost.py b/Work/pcost.py index 77ccd087c..dd015b832 100644 --- a/Work/pcost.py +++ b/Work/pcost.py @@ -1,4 +1,5 @@ # Exercises 1.27, 1.30-1.33 +# Exercise 2.15 import csv import sys @@ -9,7 +10,7 @@ def portfolio_cost(filename): lines = csv.reader(file) # Skip header next(lines) - for line in lines: + for lineno, line in enumerate(lines, start=1): try: name = line[0] shares = int(line[1]) @@ -18,7 +19,7 @@ def portfolio_cost(filename): print(f"{name:5}: {shares:3} x {price:5.2f} = {purchase_price:8.2f}") portfolio_price += purchase_price except ValueError: - print("Warning: skipping", line, file=sys.stderr) + print(f"Missing data in line {lineno}:", line, file=sys.stderr) return portfolio_price if len(sys.argv) == 2: From 385454c4cdefa2d5f096f72180355992aaadd4e1 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sun, 26 Jul 2020 10:14:27 +0200 Subject: [PATCH 14/58] Exercise 2.16 --- Work/pcost.py | 12 ++++++------ Work/report.py | 11 ++++++----- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Work/pcost.py b/Work/pcost.py index dd015b832..2f7566335 100644 --- a/Work/pcost.py +++ b/Work/pcost.py @@ -1,5 +1,5 @@ # Exercises 1.27, 1.30-1.33 -# Exercise 2.15 +# Exercises 2.15, 2.16 import csv import sys @@ -8,13 +8,13 @@ def portfolio_cost(filename): portfolio_price = 0 with open(filename, "rt") as file: lines = csv.reader(file) - # Skip header - next(lines) + keys = next(lines) for lineno, line in enumerate(lines, start=1): + record = dict(zip(keys, line)) try: - name = line[0] - shares = int(line[1]) - price = float(line[2]) + name = record["name"] + shares = int(record["shares"]) + price = float(record["price"]) purchase_price = shares * price print(f"{name:5}: {shares:3} x {price:5.2f} = {purchase_price:8.2f}") portfolio_price += purchase_price diff --git a/Work/report.py b/Work/report.py index 810315951..a035f3669 100644 --- a/Work/report.py +++ b/Work/report.py @@ -1,5 +1,6 @@ # Exercises 2.4-2.7 # Exercises 2.9-2.12 +# Exercise 2.16 from copy import deepcopy import csv @@ -20,15 +21,15 @@ def read_portfolio(filename): portfolio = [] with open(filename, "rt") as file: lines = csv.reader(file) - # Skip header - next(lines) + keys = next(lines) for line in lines: + record = dict(zip(keys, line)) try: portfolio.append( { - "name": line[0], - "shares": int(line[1]), - "price": float(line[2]) + "name": record["name"], + "shares": int(record["shares"]), + "price": float(record["price"]) } ) except ValueError: From aa7a80086d867b31caeaf2266fced2ec425f69ec Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sun, 26 Jul 2020 10:25:35 +0200 Subject: [PATCH 15/58] Update report.py --- Work/report.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Work/report.py b/Work/report.py index a035f3669..994db58f0 100644 --- a/Work/report.py +++ b/Work/report.py @@ -10,11 +10,11 @@ def read_prices(filename): prices = {} with open(filename, "rt") as file: lines = csv.reader(file) - for line in lines: + for lineno, line in enumerate(lines, start=1): try: prices[line[0]] = float(line[1]) except (IndexError, ValueError): - print("Warning: skipping", line, file=sys.stderr) + print(f"Skipping {filename}, line {lineno}:", line, file=sys.stderr) return prices def read_portfolio(filename): @@ -22,7 +22,7 @@ def read_portfolio(filename): with open(filename, "rt") as file: lines = csv.reader(file) keys = next(lines) - for line in lines: + for lineno, line in enumerate(lines, start=1): record = dict(zip(keys, line)) try: portfolio.append( @@ -33,7 +33,7 @@ def read_portfolio(filename): } ) except ValueError: - print("Warning: skipping", line, file=sys.stderr) + print(f"Skipping {filename}, line {lineno}:", line, file=sys.stderr) return portfolio def portfolio_cost(portfolio): From ee0e15ed93c42813824d61377f0b842b6e88f9ba Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sun, 26 Jul 2020 10:30:42 +0200 Subject: [PATCH 16/58] Update mortgage.py --- Work/mortgage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Work/mortgage.py b/Work/mortgage.py index 59ea1f544..300afb8ba 100644 --- a/Work/mortgage.py +++ b/Work/mortgage.py @@ -16,9 +16,9 @@ if extra_payment_start_month <= month <= extra_payment_end_month: principal -= extra_payment total_paid += extra_payment - print(f"{month:5d}{total_paid:12.2f}{principal:12.2f}") + print(f"{month:5d}{total_paid:12,.2f}{principal:12,.2f}") month += 1 total_paid += principal principal = 0 -print(f"{month:5d}{total_paid:12.2f}{principal:12.2f}") +print(f"{month:5d}{total_paid:12,.2f}{principal:12,.2f}") From 17873db5d58d8d9070606dcf1f10bd4e8c332376 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sun, 26 Jul 2020 12:09:54 +0200 Subject: [PATCH 17/58] Exercise 2.20 --- Work/report.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/Work/report.py b/Work/report.py index 994db58f0..04395a203 100644 --- a/Work/report.py +++ b/Work/report.py @@ -1,6 +1,6 @@ # Exercises 2.4-2.7 # Exercises 2.9-2.12 -# Exercise 2.16 +# Exercises 2.16, 2.20 from copy import deepcopy import csv @@ -36,20 +36,23 @@ def read_portfolio(filename): print(f"Skipping {filename}, line {lineno}:", line, file=sys.stderr) return portfolio -def portfolio_cost(portfolio): - portfolio_price = 0 - for holding in portfolio: - name, shares, price = holding["name"], holding["shares"], holding["price"] - purchase_price = shares * price - print(f"{name:5}: {shares:3} x {price:6.2f} = {purchase_price:10,.2f}") - portfolio_price += purchase_price - return portfolio_price +def portfolio_cost(portfolio, verbose=True): + if verbose: + portfolio_price = 0 + for holding in portfolio: + name, shares, price = holding["name"], holding["shares"], holding["price"] + purchase_price = shares * price + print(f"{name:5}: {shares:3} x {price:6.2f} = {purchase_price:10,.2f}") + portfolio_price += purchase_price + return portfolio_price + else: + return sum([holding["shares"] * holding["price"] for holding in portfolio]) -def portfolio_value(portfolio, prices): +def portfolio_value(portfolio, prices, verbose=True): portfolio = deepcopy(portfolio) for holding in portfolio: holding["price"] = prices[holding["name"]] - return portfolio_cost(portfolio) + return portfolio_cost(portfolio, verbose) def make_report(portfolio, prices): report = [] @@ -78,11 +81,11 @@ def print_report(portfolio, prices): filename = "Data/portfolio.csv" portfolio = read_portfolio(filename) -#cost = portfolio_cost(portfolio) +#cost = portfolio_cost(portfolio, verbose=False) #print(f"\n\u2211 {cost:,.2f}\n") prices = read_prices("Data/prices.csv") -#value = portfolio_value(portfolio, prices) +#value = portfolio_value(portfolio, prices, verbose=False) #print(f"\n\u2211 {value:,.2f}\n") #print(f"{value:,.2f} - {cost:,.2f} = {value - cost:,.2f}") From 059c3fd63c61aeae1f5cf8b2a53eb35d49359397 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sun, 26 Jul 2020 14:20:20 +0200 Subject: [PATCH 18/58] Exercises 2.24 and 2.25 --- Work/report.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/Work/report.py b/Work/report.py index 04395a203..fbac602f0 100644 --- a/Work/report.py +++ b/Work/report.py @@ -1,6 +1,6 @@ # Exercises 2.4-2.7 # Exercises 2.9-2.12 -# Exercises 2.16, 2.20 +# Exercises 2.16, 2.20, 2.24, 2.25 from copy import deepcopy import csv @@ -22,16 +22,14 @@ def read_portfolio(filename): with open(filename, "rt") as file: lines = csv.reader(file) keys = next(lines) + # Keys of interest and the types of their values + types = {"name": str, "shares": int, "price": float} for lineno, line in enumerate(lines, start=1): record = dict(zip(keys, line)) try: - portfolio.append( - { - "name": record["name"], - "shares": int(record["shares"]), - "price": float(record["price"]) - } - ) + portfolio.append({ + name: cast(record[name]) for name, cast in types.items() + }) except ValueError: print(f"Skipping {filename}, line {lineno}:", line, file=sys.stderr) return portfolio From 574c9e5ac6980787d01955f7adce194d8a5c0e0b Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sun, 26 Jul 2020 21:00:21 +0200 Subject: [PATCH 19/58] Exercises 3.1 and 3.2 --- Work/report.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Work/report.py b/Work/report.py index fbac602f0..0d5385f17 100644 --- a/Work/report.py +++ b/Work/report.py @@ -1,6 +1,7 @@ # Exercises 2.4-2.7 # Exercises 2.9-2.12 # Exercises 2.16, 2.20, 2.24, 2.25 +# Exercises 3.1, 3.2 from copy import deepcopy import csv @@ -68,24 +69,29 @@ def make_report(portfolio, prices): def print_report(portfolio, prices): headers = ("Name", "Shares", "Price", "Change") print("%10s %10s %10s %10s" % headers) - print(" ".join((10 * "-",) * 4)) + print(" ".join((10 * "-",) * len(headers))) for line in make_report(portfolio, prices): line["price"] = "$%.2f" % line["price"] print("{name:>10s} {shares:>10d} {price:>10s} {change:>+10.2f}".format_map(line)) +def portfolio_report(portfolio_filename, prices_filename): + portfolio = read_portfolio(portfolio_filename) + prices = read_prices(prices_filename) + print_report(portfolio, prices) + if len(sys.argv) == 2: filename = sys.argv[1] else: filename = "Data/portfolio.csv" -portfolio = read_portfolio(filename) +#portfolio = read_portfolio(filename) #cost = portfolio_cost(portfolio, verbose=False) #print(f"\n\u2211 {cost:,.2f}\n") -prices = read_prices("Data/prices.csv") +#prices = read_prices("Data/prices.csv") #value = portfolio_value(portfolio, prices, verbose=False) #print(f"\n\u2211 {value:,.2f}\n") #print(f"{value:,.2f} - {cost:,.2f} = {value - cost:,.2f}") -print_report(portfolio, prices) +portfolio_report(filename, "Data/prices.csv") From fddc1c9f4a57d0890b7c33a36e41b781cf08af21 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Tue, 28 Jul 2020 10:00:44 +0200 Subject: [PATCH 20/58] Exercises 3.3-3.7 --- Work/fileparse.py | 69 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/Work/fileparse.py b/Work/fileparse.py index 1d499e733..5da3ffbf7 100644 --- a/Work/fileparse.py +++ b/Work/fileparse.py @@ -1,3 +1,66 @@ -# fileparse.py -# -# Exercise 3.3 +# Exercises 3.3-3.7 + +import csv + +def parse_csv_dicts(filename, delimiter, select=None, types=None): + """ + Parse a CSV file into a list of dicts + """ + records = [] + with open(filename, "rt") as file: + lines = csv.reader(file, delimiter=delimiter) + keys = next(lines) + + if select: + columns = [keys.index(col) for col in select] + keys = select + else: + columns = None + + if types: + # Make sure that every column to select has a type or defaults to str + for key in keys: + if key not in types: + types[key] = str + + for line in lines: + if not line: + continue + if columns: + line = [line[i] for i in columns] + if types: + records.append({ + key: types[key](line[i]) for i, key in enumerate(keys) + }) + else: + records.append(dict(zip(keys, line))) + return records + +def parse_csv_tuples(filename, delimiter, types=None): + """ + Parse a CSV file into a list of tuples + """ + records = [] + with open(filename, "rt") as file: + lines = csv.reader(file, delimiter=delimiter) + + for line in lines: + if not line: + continue + if types: + assert len(types) == len(line) + line = [convert(val) for convert, val in zip(types, line)] + records.append(tuple(line)) + return records + +# The revenge for straying off course... +def parse_csv(filename, has_headers, delimiter=',', select=None, types=None): + if has_headers: + if types: + assert type(types) is dict + return parse_csv_dicts(filename, delimiter, select, types) + else: + assert not select, "select not supported" + if types: + assert type(types) is list + return parse_csv_tuples(filename, delimiter, types) From 12de130836a22ee8d94798f4905afb0f30e4376b Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Tue, 28 Jul 2020 22:12:46 +0200 Subject: [PATCH 21/58] Exercises 3.8-3.10 --- Work/fileparse.py | 64 +++++++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/Work/fileparse.py b/Work/fileparse.py index 5da3ffbf7..756b36d14 100644 --- a/Work/fileparse.py +++ b/Work/fileparse.py @@ -1,8 +1,9 @@ -# Exercises 3.3-3.7 +# Exercises 3.3-3.10 import csv +import sys -def parse_csv_dicts(filename, delimiter, select=None, types=None): +def parse_csv_dicts(filename, delimiter, select=None, types=None, silence_errors=False): """ Parse a CSV file into a list of dicts """ @@ -23,20 +24,26 @@ def parse_csv_dicts(filename, delimiter, select=None, types=None): if key not in types: types[key] = str - for line in lines: - if not line: - continue - if columns: - line = [line[i] for i in columns] - if types: - records.append({ - key: types[key](line[i]) for i, key in enumerate(keys) - }) - else: - records.append(dict(zip(keys, line))) + for lineno, line in enumerate(lines, start=1): + try: + if not line: + continue + if columns: + line = [line[i] for i in columns] + if types: + records.append({ + key: types[key](line[i]) for i, key in enumerate(keys) + }) + else: + records.append(dict(zip(keys, line))) + except ValueError as err: + if not silence_errors: + print(f"Skipping {filename}, line {lineno}:", line, file=sys.stderr) + print(f"Reason:", err, file=sys.stderr) + return records -def parse_csv_tuples(filename, delimiter, types=None): +def parse_csv_tuples(filename, delimiter, types=None, silence_errors=False): """ Parse a CSV file into a list of tuples """ @@ -44,23 +51,30 @@ def parse_csv_tuples(filename, delimiter, types=None): with open(filename, "rt") as file: lines = csv.reader(file, delimiter=delimiter) - for line in lines: - if not line: - continue - if types: - assert len(types) == len(line) - line = [convert(val) for convert, val in zip(types, line)] - records.append(tuple(line)) + for lineno, line in enumerate(lines, start=1): + try: + if not line: + continue + if types: + assert len(types) == len(line) + line = [convert(val) for convert, val in zip(types, line)] + records.append(tuple(line)) + except ValueError as err: + if not silence_errors: + print(f"Skipping {filename}, line {lineno}:", line, file=sys.stderr) + print(f"Reason:", err, file=sys.stderr) + return records # The revenge for straying off course... -def parse_csv(filename, has_headers, delimiter=',', select=None, types=None): +def parse_csv(filename, has_headers, delimiter=',', select=None, types=None, silence_errors=False): if has_headers: if types: assert type(types) is dict - return parse_csv_dicts(filename, delimiter, select, types) + return parse_csv_dicts(filename, delimiter, select, types, silence_errors) else: - assert not select, "select not supported" + if select: + raise RuntimeError("select requires column names") if types: assert type(types) is list - return parse_csv_tuples(filename, delimiter, types) + return parse_csv_tuples(filename, delimiter, types, silence_errors) From 3b07763bd2d4d4993e9eec90babdfd4f310f6959 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Wed, 29 Jul 2020 16:21:10 +0200 Subject: [PATCH 22/58] Exercise 3.12 --- Work/report.py | 42 ++++++++++++++++-------------------------- 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/Work/report.py b/Work/report.py index 0d5385f17..5fbbec712 100644 --- a/Work/report.py +++ b/Work/report.py @@ -1,39 +1,29 @@ # Exercises 2.4-2.7 # Exercises 2.9-2.12 # Exercises 2.16, 2.20, 2.24, 2.25 -# Exercises 3.1, 3.2 +# Exercises 3.1, 3.2, 3.12 from copy import deepcopy -import csv +from fileparse import parse_csv import sys def read_prices(filename): - prices = {} - with open(filename, "rt") as file: - lines = csv.reader(file) - for lineno, line in enumerate(lines, start=1): - try: - prices[line[0]] = float(line[1]) - except (IndexError, ValueError): - print(f"Skipping {filename}, line {lineno}:", line, file=sys.stderr) - return prices + prices = parse_csv( + filename, + has_headers=False, + types=[str, float] + ) + return { + name: price for name, price in prices + } def read_portfolio(filename): - portfolio = [] - with open(filename, "rt") as file: - lines = csv.reader(file) - keys = next(lines) - # Keys of interest and the types of their values - types = {"name": str, "shares": int, "price": float} - for lineno, line in enumerate(lines, start=1): - record = dict(zip(keys, line)) - try: - portfolio.append({ - name: cast(record[name]) for name, cast in types.items() - }) - except ValueError: - print(f"Skipping {filename}, line {lineno}:", line, file=sys.stderr) - return portfolio + return parse_csv( + filename, + has_headers=True, + select=["name", "shares", "price"], + types={"shares": int, "price": float} + ) def portfolio_cost(portfolio, verbose=True): if verbose: From 8081b51a9d12f1b27747b8b9df4e69c1c6431d5c Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Wed, 29 Jul 2020 16:38:37 +0200 Subject: [PATCH 23/58] Exercise 3.14 --- Work/pcost.py | 22 ++++++---------------- Work/report.py | 25 +++++++++++++------------ 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/Work/pcost.py b/Work/pcost.py index 2f7566335..4089f98b5 100644 --- a/Work/pcost.py +++ b/Work/pcost.py @@ -1,25 +1,15 @@ # Exercises 1.27, 1.30-1.33 # Exercises 2.15, 2.16 +# Exercise 3.14 -import csv +from report import read_portfolio import sys def portfolio_cost(filename): + portfolio = read_portfolio(filename) portfolio_price = 0 - with open(filename, "rt") as file: - lines = csv.reader(file) - keys = next(lines) - for lineno, line in enumerate(lines, start=1): - record = dict(zip(keys, line)) - try: - name = record["name"] - shares = int(record["shares"]) - price = float(record["price"]) - purchase_price = shares * price - print(f"{name:5}: {shares:3} x {price:5.2f} = {purchase_price:8.2f}") - portfolio_price += purchase_price - except ValueError: - print(f"Missing data in line {lineno}:", line, file=sys.stderr) + for holding in portfolio: + portfolio_price += holding["shares"] * holding["price"] return portfolio_price if len(sys.argv) == 2: @@ -27,4 +17,4 @@ def portfolio_cost(filename): else: filename = "Data/portfolio.csv" -print("\n\u2211", portfolio_cost(filename)) +print("\u2211", portfolio_cost(filename)) diff --git a/Work/report.py b/Work/report.py index 5fbbec712..93f534ce3 100644 --- a/Work/report.py +++ b/Work/report.py @@ -69,19 +69,20 @@ def portfolio_report(portfolio_filename, prices_filename): prices = read_prices(prices_filename) print_report(portfolio, prices) -if len(sys.argv) == 2: - filename = sys.argv[1] -else: - filename = "Data/portfolio.csv" +if __name__ == "__main__": + if len(sys.argv) == 2: + filename = sys.argv[1] + else: + filename = "Data/portfolio.csv" -#portfolio = read_portfolio(filename) -#cost = portfolio_cost(portfolio, verbose=False) -#print(f"\n\u2211 {cost:,.2f}\n") + #portfolio = read_portfolio(filename) + #cost = portfolio_cost(portfolio, verbose=False) + #print(f"\n\u2211 {cost:,.2f}\n") -#prices = read_prices("Data/prices.csv") -#value = portfolio_value(portfolio, prices, verbose=False) -#print(f"\n\u2211 {value:,.2f}\n") + #prices = read_prices("Data/prices.csv") + #value = portfolio_value(portfolio, prices, verbose=False) + #print(f"\n\u2211 {value:,.2f}\n") -#print(f"{value:,.2f} - {cost:,.2f} = {value - cost:,.2f}") + #print(f"{value:,.2f} - {cost:,.2f} = {value - cost:,.2f}") -portfolio_report(filename, "Data/prices.csv") + portfolio_report(filename, "Data/prices.csv") From 5c2aef1e4f1f10ab0eb4ac41165e0c3e4628693a Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Wed, 29 Jul 2020 16:53:09 +0200 Subject: [PATCH 24/58] Exercises 3.15 and 3.16 --- Work/pcost.py | 16 ++++++++++------ Work/report.py | 11 +++++++---- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Work/pcost.py b/Work/pcost.py index 4089f98b5..128226231 100644 --- a/Work/pcost.py +++ b/Work/pcost.py @@ -1,6 +1,6 @@ # Exercises 1.27, 1.30-1.33 # Exercises 2.15, 2.16 -# Exercise 3.14 +# Exercises 3.14-3.16 from report import read_portfolio import sys @@ -12,9 +12,13 @@ def portfolio_cost(filename): portfolio_price += holding["shares"] * holding["price"] return portfolio_price -if len(sys.argv) == 2: - filename = sys.argv[1] -else: - filename = "Data/portfolio.csv" +def main(argv): + if len(argv) == 2: + filename = argv[1] + else: + filename = "Data/portfolio.csv" -print("\u2211", portfolio_cost(filename)) + print("\u2211", portfolio_cost(filename)) + +if __name__ == "__main__": + main(sys.argv) \ No newline at end of file diff --git a/Work/report.py b/Work/report.py index 93f534ce3..c67b72a3a 100644 --- a/Work/report.py +++ b/Work/report.py @@ -1,7 +1,7 @@ # Exercises 2.4-2.7 # Exercises 2.9-2.12 # Exercises 2.16, 2.20, 2.24, 2.25 -# Exercises 3.1, 3.2, 3.12 +# Exercises 3.1, 3.2, 3.12, 3.15, 3.16 from copy import deepcopy from fileparse import parse_csv @@ -69,9 +69,9 @@ def portfolio_report(portfolio_filename, prices_filename): prices = read_prices(prices_filename) print_report(portfolio, prices) -if __name__ == "__main__": - if len(sys.argv) == 2: - filename = sys.argv[1] +def main(argv): + if len(argv) == 2: + filename = argv[1] else: filename = "Data/portfolio.csv" @@ -86,3 +86,6 @@ def portfolio_report(portfolio_filename, prices_filename): #print(f"{value:,.2f} - {cost:,.2f} = {value - cost:,.2f}") portfolio_report(filename, "Data/prices.csv") + +if __name__ == "__main__": + main(sys.argv) From cafd68dad68cb8420cc36ec94ed28d1db0b9f683 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sat, 1 Aug 2020 14:32:06 +0200 Subject: [PATCH 25/58] Exercises 3.17 and 3.18 --- Work/fileparse.py | 101 +++++++++++++++++++++++----------------------- Work/report.py | 31 +++++++------- 2 files changed, 66 insertions(+), 66 deletions(-) diff --git a/Work/fileparse.py b/Work/fileparse.py index 756b36d14..aa80ffde6 100644 --- a/Work/fileparse.py +++ b/Work/fileparse.py @@ -1,80 +1,79 @@ -# Exercises 3.3-3.10 +# Exercises 3.3-3.10, 3.17 import csv import sys -def parse_csv_dicts(filename, delimiter, select=None, types=None, silence_errors=False): +def parse_csv_dicts(lines, delimiter, select=None, types=None, silence_errors=False): """ - Parse a CSV file into a list of dicts + Parse lines in CSV format into a list of dicts """ records = [] - with open(filename, "rt") as file: - lines = csv.reader(file, delimiter=delimiter) - keys = next(lines) + lines = csv.reader(lines, delimiter=delimiter) + keys = next(lines) - if select: - columns = [keys.index(col) for col in select] - keys = select - else: - columns = None + if select: + columns = [keys.index(col) for col in select] + keys = select + else: + columns = None - if types: - # Make sure that every column to select has a type or defaults to str - for key in keys: - if key not in types: - types[key] = str + if types: + # Make sure that every column to select has a type or defaults to str + for key in keys: + if key not in types: + types[key] = str - for lineno, line in enumerate(lines, start=1): - try: - if not line: - continue - if columns: - line = [line[i] for i in columns] - if types: - records.append({ - key: types[key](line[i]) for i, key in enumerate(keys) - }) - else: - records.append(dict(zip(keys, line))) - except ValueError as err: - if not silence_errors: - print(f"Skipping {filename}, line {lineno}:", line, file=sys.stderr) - print(f"Reason:", err, file=sys.stderr) + for lineno, line in enumerate(lines, start=1): + try: + if not line: + continue + if columns: + line = [line[i] for i in columns] + if types: + records.append({ + key: types[key](line[i]) for i, key in enumerate(keys) + }) + else: + records.append(dict(zip(keys, line))) + except ValueError as err: + if not silence_errors: + print(f"Skipping line {lineno}:", line, file=sys.stderr) + print(f"Reason:", err, file=sys.stderr) return records -def parse_csv_tuples(filename, delimiter, types=None, silence_errors=False): +def parse_csv_tuples(lines, delimiter, types=None, silence_errors=False): """ - Parse a CSV file into a list of tuples + Parse lines in CSV format into a list of tuples """ records = [] - with open(filename, "rt") as file: - lines = csv.reader(file, delimiter=delimiter) + lines = csv.reader(lines, delimiter=delimiter) - for lineno, line in enumerate(lines, start=1): - try: - if not line: - continue - if types: - assert len(types) == len(line) - line = [convert(val) for convert, val in zip(types, line)] - records.append(tuple(line)) - except ValueError as err: - if not silence_errors: - print(f"Skipping {filename}, line {lineno}:", line, file=sys.stderr) - print(f"Reason:", err, file=sys.stderr) + for lineno, line in enumerate(lines, start=1): + try: + if not line: + continue + if types: + assert len(types) == len(line) + line = [convert(val) for convert, val in zip(types, line)] + records.append(tuple(line)) + except ValueError as err: + if not silence_errors: + print(f"Skipping line {lineno}:", line, file=sys.stderr) + print(f"Reason:", err, file=sys.stderr) return records # The revenge for straying off course... -def parse_csv(filename, has_headers, delimiter=',', select=None, types=None, silence_errors=False): +def parse_csv(lines, has_headers, delimiter=',', select=None, types=None, silence_errors=False): + assert type(lines) is not str if has_headers: if types: assert type(types) is dict - return parse_csv_dicts(filename, delimiter, select, types, silence_errors) + return parse_csv_dicts(lines, delimiter, select, types, silence_errors) else: if select: raise RuntimeError("select requires column names") if types: assert type(types) is list - return parse_csv_tuples(filename, delimiter, types, silence_errors) + return parse_csv_tuples(lines, delimiter, types, silence_errors) diff --git a/Work/report.py b/Work/report.py index c67b72a3a..d27e8d7f5 100644 --- a/Work/report.py +++ b/Work/report.py @@ -1,29 +1,30 @@ # Exercises 2.4-2.7 # Exercises 2.9-2.12 # Exercises 2.16, 2.20, 2.24, 2.25 -# Exercises 3.1, 3.2, 3.12, 3.15, 3.16 +# Exercises 3.1, 3.2, 3.12, 3.15, 3.16, 3.18 from copy import deepcopy from fileparse import parse_csv import sys def read_prices(filename): - prices = parse_csv( - filename, - has_headers=False, - types=[str, float] - ) - return { - name: price for name, price in prices - } + with open(filename, "rt") as file: + return { + name: price for name, price in parse_csv( + file, + has_headers=False, + types=[str, float] + ) + } def read_portfolio(filename): - return parse_csv( - filename, - has_headers=True, - select=["name", "shares", "price"], - types={"shares": int, "price": float} - ) + with open(filename, "rt") as file: + return parse_csv( + file, + has_headers=True, + select=["name", "shares", "price"], + types={"shares": int, "price": float} + ) def portfolio_cost(portfolio, verbose=True): if verbose: From e59f2ff73d0e013d89b504b6bd1c6bf4dff8762d Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sun, 2 Aug 2020 13:10:32 +0200 Subject: [PATCH 26/58] Exercises 4.1 and 4.2 --- Work/stock.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 Work/stock.py diff --git a/Work/stock.py b/Work/stock.py new file mode 100644 index 000000000..1ce32432e --- /dev/null +++ b/Work/stock.py @@ -0,0 +1,14 @@ +# Exercises 4.1, 4.2 + +class Stock: + def __init__(self, name, shares, price): + self.name = name + self.shares = shares + self.price = price + + def cost(self): + return self.shares * self.price + + def sell(self, shares): + shares = min(shares, self.shares) + self.shares -= shares From 76aeca7b63260f872fdcb2277155cd36f5b668ba Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sun, 2 Aug 2020 13:12:05 +0200 Subject: [PATCH 27/58] Exercises 4.3 and 4.4 --- Work/pcost.py | 3 ++- Work/report.py | 39 ++++++++++++++++++++++----------------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/Work/pcost.py b/Work/pcost.py index 128226231..52b1a7b31 100644 --- a/Work/pcost.py +++ b/Work/pcost.py @@ -1,6 +1,7 @@ # Exercises 1.27, 1.30-1.33 # Exercises 2.15, 2.16 # Exercises 3.14-3.16 +# Exercises 4.3, 4.4 from report import read_portfolio import sys @@ -9,7 +10,7 @@ def portfolio_cost(filename): portfolio = read_portfolio(filename) portfolio_price = 0 for holding in portfolio: - portfolio_price += holding["shares"] * holding["price"] + portfolio_price += holding.cost() return portfolio_price def main(argv): diff --git a/Work/report.py b/Work/report.py index d27e8d7f5..5059a2bbd 100644 --- a/Work/report.py +++ b/Work/report.py @@ -2,46 +2,51 @@ # Exercises 2.9-2.12 # Exercises 2.16, 2.20, 2.24, 2.25 # Exercises 3.1, 3.2, 3.12, 3.15, 3.16, 3.18 +# Exercises 4.3, 4.4 from copy import deepcopy from fileparse import parse_csv +from stock import Stock import sys def read_prices(filename): with open(filename, "rt") as file: - return { - name: price for name, price in parse_csv( - file, - has_headers=False, - types=[str, float] - ) - } + prices = parse_csv( + file, + has_headers=False, + types=[str, float] + ) + return { + name: price for name, price in prices + } def read_portfolio(filename): with open(filename, "rt") as file: - return parse_csv( + portfolio = parse_csv( file, has_headers=True, select=["name", "shares", "price"], types={"shares": int, "price": float} ) + return [ + Stock(holding["name"], holding["shares"], holding["price"]) for holding in portfolio + ] def portfolio_cost(portfolio, verbose=True): if verbose: portfolio_price = 0 for holding in portfolio: - name, shares, price = holding["name"], holding["shares"], holding["price"] - purchase_price = shares * price - print(f"{name:5}: {shares:3} x {price:6.2f} = {purchase_price:10,.2f}") + purchase_price = holding.cost() + print(f"{holding.name:5}: {holding.shares:3} x {holding.price:6.2f} = {purchase_price:10,.2f}") portfolio_price += purchase_price return portfolio_price else: - return sum([holding["shares"] * holding["price"] for holding in portfolio]) + return sum([holding.cost() for holding in portfolio]) def portfolio_value(portfolio, prices, verbose=True): portfolio = deepcopy(portfolio) for holding in portfolio: - holding["price"] = prices[holding["name"]] + holding.price = prices[holding.name] return portfolio_cost(portfolio, verbose) def make_report(portfolio, prices): @@ -49,10 +54,10 @@ def make_report(portfolio, prices): for holding in portfolio: report.append( { - "name": holding["name"], - "shares": holding["shares"], - "price": prices[holding["name"]], - "change": prices[holding["name"]] - holding["price"] + "name": holding.name, + "shares": holding.shares, + "price": prices[holding.name], + "change": prices[holding.name] - holding.price } ) return report From cadb3d338d6f3eb94439ed7487aa3319e562880c Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sun, 2 Aug 2020 16:38:23 +0200 Subject: [PATCH 28/58] Exercises 4.5 and 4.6 --- Work/report.py | 20 ++++++++++++-------- Work/tableformat.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 Work/tableformat.py diff --git a/Work/report.py b/Work/report.py index 5059a2bbd..7de90779a 100644 --- a/Work/report.py +++ b/Work/report.py @@ -2,12 +2,14 @@ # Exercises 2.9-2.12 # Exercises 2.16, 2.20, 2.24, 2.25 # Exercises 3.1, 3.2, 3.12, 3.15, 3.16, 3.18 -# Exercises 4.3, 4.4 +# Exercises 4.3-4.6 from copy import deepcopy from fileparse import parse_csv from stock import Stock + import sys +import tableformat def read_prices(filename): with open(filename, "rt") as file: @@ -62,18 +64,20 @@ def make_report(portfolio, prices): ) return report -def print_report(portfolio, prices): - headers = ("Name", "Shares", "Price", "Change") - print("%10s %10s %10s %10s" % headers) - print(" ".join((10 * "-",) * len(headers))) +def print_report(portfolio, prices, formatter): + formatter.headings(["Name", "Shares", "Price", "Change"]) for line in make_report(portfolio, prices): - line["price"] = "$%.2f" % line["price"] - print("{name:>10s} {shares:>10d} {price:>10s} {change:>+10.2f}".format_map(line)) + formatter.row([ + line["name"], + str(line["shares"]), + "$%.2f" % line["price"], + "%.2f" % line["change"] + ]) def portfolio_report(portfolio_filename, prices_filename): portfolio = read_portfolio(portfolio_filename) prices = read_prices(prices_filename) - print_report(portfolio, prices) + print_report(portfolio, prices, tableformat.TextTableFormatter()) def main(argv): if len(argv) == 2: diff --git a/Work/tableformat.py b/Work/tableformat.py new file mode 100644 index 000000000..01ae26265 --- /dev/null +++ b/Work/tableformat.py @@ -0,0 +1,35 @@ +# Exercises 4.5, 4.6 + +# Abstract base class +class TableFormatter: + def headings(self, headers): + raise NotImplementedError() + + def row(self, data): + raise NotImplementedError() + +class TextTableFormatter(TableFormatter): + def headings(self, headers): + print(" ".join([f"{header:>10s}" for header in headers])) + print(" ".join((10 * "-",) * len(headers))) + + def row(self, data): + print(" ".join([f"{field:>10s}" for field in data])) + +class CSVTableFormatter(TableFormatter): + def __init__(self, delimiter=','): + super().__init__() + self.delimiter = delimiter + + def headings(self, headers): + print(self.delimiter.join(headers)) + + def row(self, data): + print(self.delimiter.join(data)) + +class HTMLTableFormatter(TableFormatter): + def headings(self, headers): + print("" + "".join([f"{header}" for header in headers]) + "") + + def row(self, data): + print("" + "".join([f"{field}" for field in data]) + "") \ No newline at end of file From 5e9b354a2aba11d2a133b62447886be0f086beb4 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sun, 2 Aug 2020 17:48:31 +0200 Subject: [PATCH 29/58] Exercises 4.7 and 4.8 --- Work/report.py | 16 +++++++++------- Work/tableformat.py | 14 ++++++++++++-- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/Work/report.py b/Work/report.py index 7de90779a..7de787b16 100644 --- a/Work/report.py +++ b/Work/report.py @@ -2,7 +2,7 @@ # Exercises 2.9-2.12 # Exercises 2.16, 2.20, 2.24, 2.25 # Exercises 3.1, 3.2, 3.12, 3.15, 3.16, 3.18 -# Exercises 4.3-4.6 +# Exercises 4.3-4.8 from copy import deepcopy from fileparse import parse_csv @@ -74,16 +74,18 @@ def print_report(portfolio, prices, formatter): "%.2f" % line["change"] ]) -def portfolio_report(portfolio_filename, prices_filename): +def portfolio_report(portfolio_filename, prices_filename, fmt="txt"): portfolio = read_portfolio(portfolio_filename) prices = read_prices(prices_filename) - print_report(portfolio, prices, tableformat.TextTableFormatter()) + print_report(portfolio, prices, tableformat.create_formatter(fmt)()) def main(argv): - if len(argv) == 2: - filename = argv[1] + assert len(argv) >= 2 + filename = argv[1] + if len(argv) > 2: + fmt = argv[2] else: - filename = "Data/portfolio.csv" + fmt = "txt" #portfolio = read_portfolio(filename) #cost = portfolio_cost(portfolio, verbose=False) @@ -95,7 +97,7 @@ def main(argv): #print(f"{value:,.2f} - {cost:,.2f} = {value - cost:,.2f}") - portfolio_report(filename, "Data/prices.csv") + portfolio_report(filename, "Data/prices.csv", fmt) if __name__ == "__main__": main(sys.argv) diff --git a/Work/tableformat.py b/Work/tableformat.py index 01ae26265..87c703d86 100644 --- a/Work/tableformat.py +++ b/Work/tableformat.py @@ -1,4 +1,4 @@ -# Exercises 4.5, 4.6 +# Exercises 4.5-4.7 # Abstract base class class TableFormatter: @@ -32,4 +32,14 @@ def headings(self, headers): print("" + "".join([f"{header}" for header in headers]) + "") def row(self, data): - print("" + "".join([f"{field}" for field in data]) + "") \ No newline at end of file + print("" + "".join([f"{field}" for field in data]) + "") + +def create_formatter(fmt): + if fmt == "txt": + return TextTableFormatter + elif fmt == "csv": + return CSVTableFormatter + elif fmt == "html": + return HTMLTableFormatter + else: + raise RuntimeError("Unknown format " + fmt) From 1a8b762a92c3521fbd9c63e78ada03abefb66870 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sun, 2 Aug 2020 18:09:31 +0200 Subject: [PATCH 30/58] Update report.py --- Work/report.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Work/report.py b/Work/report.py index 7de787b16..01b1bb89b 100644 --- a/Work/report.py +++ b/Work/report.py @@ -7,9 +7,8 @@ from copy import deepcopy from fileparse import parse_csv from stock import Stock - +from tableformat import create_formatter import sys -import tableformat def read_prices(filename): with open(filename, "rt") as file: @@ -77,7 +76,7 @@ def print_report(portfolio, prices, formatter): def portfolio_report(portfolio_filename, prices_filename, fmt="txt"): portfolio = read_portfolio(portfolio_filename) prices = read_prices(prices_filename) - print_report(portfolio, prices, tableformat.create_formatter(fmt)()) + print_report(portfolio, prices, create_formatter(fmt)()) def main(argv): assert len(argv) >= 2 From 6c064af038f36be1524c98316b9cd2e98e88e90e Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sun, 2 Aug 2020 18:36:48 +0200 Subject: [PATCH 31/58] Exercise 4.9 --- Work/stock.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Work/stock.py b/Work/stock.py index 1ce32432e..b538e7636 100644 --- a/Work/stock.py +++ b/Work/stock.py @@ -1,4 +1,4 @@ -# Exercises 4.1, 4.2 +# Exercises 4.1, 4.2, 4.9 class Stock: def __init__(self, name, shares, price): @@ -6,6 +6,9 @@ def __init__(self, name, shares, price): self.shares = shares self.price = price + def __repr__(self): + return f"Stock({self.name}, {self.shares}, {self.price:.2f})" + def cost(self): return self.shares * self.price From feed76a6c9b570910b928dc8b734107cb87d602c Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sun, 2 Aug 2020 18:37:18 +0200 Subject: [PATCH 32/58] Exercise 4.10 --- Work/tableformat.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Work/tableformat.py b/Work/tableformat.py index 87c703d86..dd1bcc80d 100644 --- a/Work/tableformat.py +++ b/Work/tableformat.py @@ -1,4 +1,4 @@ -# Exercises 4.5-4.7 +# Exercises 4.5-4.7, 4.10 # Abstract base class class TableFormatter: @@ -43,3 +43,9 @@ def create_formatter(fmt): return HTMLTableFormatter else: raise RuntimeError("Unknown format " + fmt) + +def print_table(table, columns, formatter): + formatter.headings(columns) + table = [[str(getattr(row, column)) for column in columns] for row in table] + for row in table: + formatter.row(row) From dd83a3de28a503ad3ddfb38e5fb9e53a38ed1586 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sun, 2 Aug 2020 18:43:31 +0200 Subject: [PATCH 33/58] Exercise 4.11 --- Work/tableformat.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Work/tableformat.py b/Work/tableformat.py index dd1bcc80d..15a2377cf 100644 --- a/Work/tableformat.py +++ b/Work/tableformat.py @@ -1,4 +1,4 @@ -# Exercises 4.5-4.7, 4.10 +# Exercises 4.5-4.7, 4.10, 4.11 # Abstract base class class TableFormatter: @@ -34,6 +34,9 @@ def headings(self, headers): def row(self, data): print("" + "".join([f"{field}" for field in data]) + "") +class FormatError(Exception): + pass + def create_formatter(fmt): if fmt == "txt": return TextTableFormatter @@ -42,7 +45,7 @@ def create_formatter(fmt): elif fmt == "html": return HTMLTableFormatter else: - raise RuntimeError("Unknown format " + fmt) + raise FormatError("Unknown format " + fmt) def print_table(table, columns, formatter): formatter.headings(columns) From 4484387f037440800bb79e70403cddd3a4d92ec4 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Tue, 4 Aug 2020 11:20:15 +0200 Subject: [PATCH 34/58] Exercise 5.6 --- Work/pcost.py | 2 +- Work/report.py | 5 +++-- Work/stock.py | 2 ++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Work/pcost.py b/Work/pcost.py index 52b1a7b31..1b374e3d1 100644 --- a/Work/pcost.py +++ b/Work/pcost.py @@ -10,7 +10,7 @@ def portfolio_cost(filename): portfolio = read_portfolio(filename) portfolio_price = 0 for holding in portfolio: - portfolio_price += holding.cost() + portfolio_price += holding.cost return portfolio_price def main(argv): diff --git a/Work/report.py b/Work/report.py index 01b1bb89b..e72b6189e 100644 --- a/Work/report.py +++ b/Work/report.py @@ -3,6 +3,7 @@ # Exercises 2.16, 2.20, 2.24, 2.25 # Exercises 3.1, 3.2, 3.12, 3.15, 3.16, 3.18 # Exercises 4.3-4.8 +# Exercise 5.6 from copy import deepcopy from fileparse import parse_csv @@ -37,12 +38,12 @@ def portfolio_cost(portfolio, verbose=True): if verbose: portfolio_price = 0 for holding in portfolio: - purchase_price = holding.cost() + purchase_price = holding.cost print(f"{holding.name:5}: {holding.shares:3} x {holding.price:6.2f} = {purchase_price:10,.2f}") portfolio_price += purchase_price return portfolio_price else: - return sum([holding.cost() for holding in portfolio]) + return sum([holding.cost for holding in portfolio]) def portfolio_value(portfolio, prices, verbose=True): portfolio = deepcopy(portfolio) diff --git a/Work/stock.py b/Work/stock.py index b538e7636..35a986c6c 100644 --- a/Work/stock.py +++ b/Work/stock.py @@ -1,4 +1,5 @@ # Exercises 4.1, 4.2, 4.9 +# Exercise 5.6 class Stock: def __init__(self, name, shares, price): @@ -9,6 +10,7 @@ def __init__(self, name, shares, price): def __repr__(self): return f"Stock({self.name}, {self.shares}, {self.price:.2f})" + @property def cost(self): return self.shares * self.price From 38ba36d5ed170536941a5abc5c22c404ef5dc513 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Tue, 4 Aug 2020 11:50:16 +0200 Subject: [PATCH 35/58] Exercise 5.7 --- Work/stock.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Work/stock.py b/Work/stock.py index 35a986c6c..d02a113fc 100644 --- a/Work/stock.py +++ b/Work/stock.py @@ -1,5 +1,5 @@ # Exercises 4.1, 4.2, 4.9 -# Exercise 5.6 +# Exercises 5.6, 5.7 class Stock: def __init__(self, name, shares, price): @@ -10,6 +10,16 @@ def __init__(self, name, shares, price): def __repr__(self): return f"Stock({self.name}, {self.shares}, {self.price:.2f})" + @property + def shares(self): + return self._shares + + @shares.setter + def shares(self, shares): + if type(shares) is not int: + raise TypeError("Expected int") + self._shares = shares + @property def cost(self): return self.shares * self.price From 718875e0bf0959175b21d88e810269a1dcebced4 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Tue, 4 Aug 2020 11:59:22 +0200 Subject: [PATCH 36/58] Exercise 5.8 --- Work/stock.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Work/stock.py b/Work/stock.py index d02a113fc..baa9ececa 100644 --- a/Work/stock.py +++ b/Work/stock.py @@ -1,7 +1,9 @@ # Exercises 4.1, 4.2, 4.9 -# Exercises 5.6, 5.7 +# Exercises 5.6-5.8 class Stock: + __slots__ = ("name", "_shares", "price") + def __init__(self, name, shares, price): self.name = name self.shares = shares From 9899c68bb441d8cad5756eea6b3814cad0968841 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Tue, 4 Aug 2020 22:39:22 +0200 Subject: [PATCH 37/58] Exercise 6.2 --- Work/pcost.py | 6 ++---- Work/portfolio.py | 19 +++++++++++++++++++ Work/report.py | 8 +++++--- 3 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 Work/portfolio.py diff --git a/Work/pcost.py b/Work/pcost.py index 1b374e3d1..e816381ad 100644 --- a/Work/pcost.py +++ b/Work/pcost.py @@ -2,16 +2,14 @@ # Exercises 2.15, 2.16 # Exercises 3.14-3.16 # Exercises 4.3, 4.4 +# Exercise 6.2 from report import read_portfolio import sys def portfolio_cost(filename): portfolio = read_portfolio(filename) - portfolio_price = 0 - for holding in portfolio: - portfolio_price += holding.cost - return portfolio_price + return portfolio.total_cost def main(argv): if len(argv) == 2: diff --git a/Work/portfolio.py b/Work/portfolio.py new file mode 100644 index 000000000..b292cde6e --- /dev/null +++ b/Work/portfolio.py @@ -0,0 +1,19 @@ +# Exercise 6.2 + +class Portfolio: + def __init__(self, holdings): + self._holdings = holdings + + def __iter__(self): + return iter(self._holdings) + + @property + def total_cost(self): + return sum([holding.cost for holding in self._holdings]) + + def tabulate_shares(self): + from collections import Counter + total_shares = Counter() + for holding in self._holdings: + total_shares[holding.name] += holding.shares + return total_shares diff --git a/Work/report.py b/Work/report.py index e72b6189e..fe8af3c54 100644 --- a/Work/report.py +++ b/Work/report.py @@ -4,9 +4,11 @@ # Exercises 3.1, 3.2, 3.12, 3.15, 3.16, 3.18 # Exercises 4.3-4.8 # Exercise 5.6 +# Exercise 6.2 from copy import deepcopy from fileparse import parse_csv +from portfolio import Portfolio from stock import Stock from tableformat import create_formatter import sys @@ -30,9 +32,9 @@ def read_portfolio(filename): select=["name", "shares", "price"], types={"shares": int, "price": float} ) - return [ + return Portfolio([ Stock(holding["name"], holding["shares"], holding["price"]) for holding in portfolio - ] + ]) def portfolio_cost(portfolio, verbose=True): if verbose: @@ -43,7 +45,7 @@ def portfolio_cost(portfolio, verbose=True): portfolio_price += purchase_price return portfolio_price else: - return sum([holding.cost for holding in portfolio]) + return portfolio.total_cost def portfolio_value(portfolio, prices, verbose=True): portfolio = deepcopy(portfolio) From 2b85a2d8b4c664bb70a9650f95814faec542e633 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Tue, 4 Aug 2020 22:46:16 +0200 Subject: [PATCH 38/58] Clean up report.py --- Work/report.py | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/Work/report.py b/Work/report.py index fe8af3c54..c3a6b3824 100644 --- a/Work/report.py +++ b/Work/report.py @@ -6,7 +6,6 @@ # Exercise 5.6 # Exercise 6.2 -from copy import deepcopy from fileparse import parse_csv from portfolio import Portfolio from stock import Stock @@ -36,23 +35,6 @@ def read_portfolio(filename): Stock(holding["name"], holding["shares"], holding["price"]) for holding in portfolio ]) -def portfolio_cost(portfolio, verbose=True): - if verbose: - portfolio_price = 0 - for holding in portfolio: - purchase_price = holding.cost - print(f"{holding.name:5}: {holding.shares:3} x {holding.price:6.2f} = {purchase_price:10,.2f}") - portfolio_price += purchase_price - return portfolio_price - else: - return portfolio.total_cost - -def portfolio_value(portfolio, prices, verbose=True): - portfolio = deepcopy(portfolio) - for holding in portfolio: - holding.price = prices[holding.name] - return portfolio_cost(portfolio, verbose) - def make_report(portfolio, prices): report = [] for holding in portfolio: @@ -89,16 +71,6 @@ def main(argv): else: fmt = "txt" - #portfolio = read_portfolio(filename) - #cost = portfolio_cost(portfolio, verbose=False) - #print(f"\n\u2211 {cost:,.2f}\n") - - #prices = read_prices("Data/prices.csv") - #value = portfolio_value(portfolio, prices, verbose=False) - #print(f"\n\u2211 {value:,.2f}\n") - - #print(f"{value:,.2f} - {cost:,.2f} = {value - cost:,.2f}") - portfolio_report(filename, "Data/prices.csv", fmt) if __name__ == "__main__": From 56c855756bfd994bbfab192aa5a05655e1774a65 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Tue, 4 Aug 2020 22:55:56 +0200 Subject: [PATCH 39/58] Exercise 6.3 --- Work/portfolio.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Work/portfolio.py b/Work/portfolio.py index b292cde6e..e637adbb8 100644 --- a/Work/portfolio.py +++ b/Work/portfolio.py @@ -1,4 +1,4 @@ -# Exercise 6.2 +# Exercises 6.2, 6.3 class Portfolio: def __init__(self, holdings): @@ -7,6 +7,15 @@ def __init__(self, holdings): def __iter__(self): return iter(self._holdings) + def __len__(self): + return len(self._holdings) + + def __getitem__(self, index): + return self._holdings[index] + + def __contains__(self, name): + return any([name == holding.name for holding in self._holdings]) + @property def total_cost(self): return sum([holding.cost for holding in self._holdings]) From c8c479420970b7c16e6334f6f38aae94c37896fa Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Wed, 5 Aug 2020 15:55:17 +0200 Subject: [PATCH 40/58] Exercise 6.5 --- Work/follow.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 Work/follow.py diff --git a/Work/follow.py b/Work/follow.py new file mode 100644 index 000000000..8b85891c5 --- /dev/null +++ b/Work/follow.py @@ -0,0 +1,19 @@ +# Exercise 6.5 + +import os +import time + +file = open("Data/stocklog.csv") +file.seek(0, os.SEEK_END) + +while True: + line = file.readline() + if not line: + time.sleep(0.1) + continue + fields = line.split(",") + name = fields[0].strip('"') + price = float(fields[1]) + change = float(fields[4]) + if change < 0: + print(f"{name:>10s} {price:>10.2f} {change:>10.2f}") \ No newline at end of file From 78d45645d0b8917ac028d0d35dc3f4ee5a80d0dc Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Wed, 5 Aug 2020 16:03:38 +0200 Subject: [PATCH 41/58] Exercise 6.6 --- Work/follow.py | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/Work/follow.py b/Work/follow.py index 8b85891c5..8f7e4b35f 100644 --- a/Work/follow.py +++ b/Work/follow.py @@ -1,19 +1,23 @@ -# Exercise 6.5 +# Exercises 6.5, 6.6 import os import time -file = open("Data/stocklog.csv") -file.seek(0, os.SEEK_END) +def follow(filename): + with open(filename, "rt") as file: + file.seek(0, os.SEEK_END) + while True: + line = file.readline() + if not line: + time.sleep(0.1) + continue + yield line -while True: - line = file.readline() - if not line: - time.sleep(0.1) - continue - fields = line.split(",") - name = fields[0].strip('"') - price = float(fields[1]) - change = float(fields[4]) - if change < 0: - print(f"{name:>10s} {price:>10.2f} {change:>10.2f}") \ No newline at end of file +if __name__ == "__main__": + for line in follow("Data/stocklog.csv"): + fields = line.split(",") + name = fields[0].strip('"') + price = float(fields[1]) + change = float(fields[4]) + if change < 0: + print(f"{name:>10s} {price:>10.2f} {change:>10.2f}") From b8a5f284027d4c2bd8293520d661e2e0be9db7c0 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Wed, 5 Aug 2020 16:08:31 +0200 Subject: [PATCH 42/58] Exercise 6.7 --- Work/follow.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Work/follow.py b/Work/follow.py index 8f7e4b35f..489c03c52 100644 --- a/Work/follow.py +++ b/Work/follow.py @@ -1,4 +1,4 @@ -# Exercises 6.5, 6.6 +# Exercises 6.5-6.7 import os import time @@ -14,10 +14,14 @@ def follow(filename): yield line if __name__ == "__main__": + from report import read_portfolio + + portfolio = read_portfolio("Data/portfolio.csv") + for line in follow("Data/stocklog.csv"): fields = line.split(",") name = fields[0].strip('"') price = float(fields[1]) change = float(fields[4]) - if change < 0: + if name in portfolio: print(f"{name:>10s} {price:>10.2f} {change:>10.2f}") From 16942d74d2d0ad09bfce35047230b2c06734366a Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Wed, 5 Aug 2020 16:30:51 +0200 Subject: [PATCH 43/58] Exercise 6.10 --- Work/ticker.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 Work/ticker.py diff --git a/Work/ticker.py b/Work/ticker.py new file mode 100644 index 000000000..f01d2be55 --- /dev/null +++ b/Work/ticker.py @@ -0,0 +1,29 @@ +# Exercise 6.10 + +import csv +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 [convert(val) for convert, 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 + +if __name__ == "__main__": + lines = follow("Data/stocklog.csv") + rows = parse_stock_data(lines) + for row in rows: + print(row) From a564f77310d93303e7dd7cf313458774f6aad343 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Wed, 5 Aug 2020 16:35:30 +0200 Subject: [PATCH 44/58] Exercise 6.11 --- Work/ticker.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Work/ticker.py b/Work/ticker.py index f01d2be55..36a678c18 100644 --- a/Work/ticker.py +++ b/Work/ticker.py @@ -1,4 +1,4 @@ -# Exercise 6.10 +# Exercises 6.10, 6.11 import csv from follow import follow @@ -15,6 +15,11 @@ def make_dicts(rows, headers): for row in rows: yield dict(zip(headers, row)) +def filter_names(rows, names): + for row in rows: + if row["name"] in names: + yield row + def parse_stock_data(lines): rows = csv.reader(lines) rows = select_columns(rows, [0, 1, 4]) @@ -23,7 +28,11 @@ def parse_stock_data(lines): return rows if __name__ == "__main__": + from report import read_portfolio + + portfolio = read_portfolio("Data/portfolio.csv") lines = follow("Data/stocklog.csv") rows = parse_stock_data(lines) + rows = filter_names(rows, portfolio) for row in rows: print(row) From 20cf717eb83316f3254639ff6973d4a786326fb9 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Wed, 5 Aug 2020 16:49:20 +0200 Subject: [PATCH 45/58] Exercise 6.12 --- Work/ticker.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/Work/ticker.py b/Work/ticker.py index 36a678c18..2055dc413 100644 --- a/Work/ticker.py +++ b/Work/ticker.py @@ -1,4 +1,4 @@ -# Exercises 6.10, 6.11 +# Exercises 6.10-6.12 import csv from follow import follow @@ -27,12 +27,24 @@ def parse_stock_data(lines): rows = make_dicts(rows, ["name", "price", "change"]) return rows -if __name__ == "__main__": +def ticker(filename, logfilename, fmt): from report import read_portfolio + from tableformat import create_formatter - portfolio = read_portfolio("Data/portfolio.csv") - lines = follow("Data/stocklog.csv") + portfolio = read_portfolio(filename) + lines = follow(logfilename) rows = parse_stock_data(lines) rows = filter_names(rows, portfolio) + formatter = create_formatter(fmt)() + + formatter.headings(["Name", "Price", "Change"]) + for row in rows: - print(row) + formatter.row([ + row["name"], + "$%.2f" % row["price"], + "%.2f" % row["change"] + ]) + +if __name__ == "__main__": + ticker("Data/portfolio.csv", "Data/stocklog.csv", "txt") From 7b8de7b38ec2f132b59e8e2d812a8e9c9d6997e4 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Wed, 5 Aug 2020 16:59:46 +0200 Subject: [PATCH 46/58] Exercise 6.14 --- Work/portfolio.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Work/portfolio.py b/Work/portfolio.py index e637adbb8..aa41278b7 100644 --- a/Work/portfolio.py +++ b/Work/portfolio.py @@ -1,4 +1,4 @@ -# Exercises 6.2, 6.3 +# Exercises 6.2, 6.3, 6.14 class Portfolio: def __init__(self, holdings): @@ -14,11 +14,11 @@ def __getitem__(self, index): return self._holdings[index] def __contains__(self, name): - return any([name == holding.name for holding in self._holdings]) + return any(name == holding.name for holding in self._holdings) @property def total_cost(self): - return sum([holding.cost for holding in self._holdings]) + return sum(holding.cost for holding in self._holdings) def tabulate_shares(self): from collections import Counter From 3440302fc32e995585794ab9c8a71d96be5551c7 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sat, 8 Aug 2020 13:39:10 +0200 Subject: [PATCH 47/58] Exercise 7.3 --- Work/report.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Work/report.py b/Work/report.py index c3a6b3824..7bc64cfbb 100644 --- a/Work/report.py +++ b/Work/report.py @@ -5,6 +5,7 @@ # Exercises 4.3-4.8 # Exercise 5.6 # Exercise 6.2 +# Exercise 7.3 from fileparse import parse_csv from portfolio import Portfolio @@ -32,7 +33,7 @@ def read_portfolio(filename): types={"shares": int, "price": float} ) return Portfolio([ - Stock(holding["name"], holding["shares"], holding["price"]) for holding in portfolio + Stock(**holding) for holding in portfolio ]) def make_report(portfolio, prices): From 7f4b937d73f3a6203f2ac4c9cdc7603507d611f7 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sat, 8 Aug 2020 13:43:33 +0200 Subject: [PATCH 48/58] Exercise 7.4 --- Work/report.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Work/report.py b/Work/report.py index 7bc64cfbb..f47e70f46 100644 --- a/Work/report.py +++ b/Work/report.py @@ -5,7 +5,7 @@ # Exercises 4.3-4.8 # Exercise 5.6 # Exercise 6.2 -# Exercise 7.3 +# Exercises 7.3, 7.4 from fileparse import parse_csv from portfolio import Portfolio @@ -13,24 +13,26 @@ from tableformat import create_formatter import sys -def read_prices(filename): +def read_prices(filename, **opts): with open(filename, "rt") as file: prices = parse_csv( file, has_headers=False, - types=[str, float] + types=[str, float], + **opts ) return { name: price for name, price in prices } -def read_portfolio(filename): +def read_portfolio(filename, **opts): with open(filename, "rt") as file: portfolio = parse_csv( file, has_headers=True, select=["name", "shares", "price"], - types={"shares": int, "price": float} + types={"shares": int, "price": float}, + **opts ) return Portfolio([ Stock(**holding) for holding in portfolio @@ -60,8 +62,8 @@ def print_report(portfolio, prices, formatter): ]) def portfolio_report(portfolio_filename, prices_filename, fmt="txt"): - portfolio = read_portfolio(portfolio_filename) - prices = read_prices(prices_filename) + portfolio = read_portfolio(portfolio_filename, silence_errors=True) + prices = read_prices(prices_filename, silence_errors=True) print_report(portfolio, prices, create_formatter(fmt)()) def main(argv): From 155aba4604832d8398e8920f34c7184375d69dbd Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sat, 8 Aug 2020 14:27:35 +0200 Subject: [PATCH 49/58] Exercises 7.7-7.9 --- Work/stock.py | 19 ++++++++----------- Work/typedproperty.py | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+), 11 deletions(-) create mode 100644 Work/typedproperty.py diff --git a/Work/stock.py b/Work/stock.py index baa9ececa..384c9f7a5 100644 --- a/Work/stock.py +++ b/Work/stock.py @@ -1,8 +1,15 @@ # Exercises 4.1, 4.2, 4.9 # Exercises 5.6-5.8 +# Exercises 7.7-7.9 + +from typedproperty import String, Integer, Float class Stock: - __slots__ = ("name", "_shares", "price") + __slots__ = ("_name", "_shares", "_price") + + name = String("name") + shares = Integer("shares") + price = Float("price") def __init__(self, name, shares, price): self.name = name @@ -12,16 +19,6 @@ def __init__(self, name, shares, price): def __repr__(self): return f"Stock({self.name}, {self.shares}, {self.price:.2f})" - @property - def shares(self): - return self._shares - - @shares.setter - def shares(self, shares): - if type(shares) is not int: - raise TypeError("Expected int") - self._shares = shares - @property def cost(self): return self.shares * self.price diff --git a/Work/typedproperty.py b/Work/typedproperty.py new file mode 100644 index 000000000..fa5d1e102 --- /dev/null +++ b/Work/typedproperty.py @@ -0,0 +1,20 @@ +# Exercises 7.7, 7.8 + +def typedproperty(name, expected_type): + private_name = '_' + name + + @property + def prop(self): + return getattr(self, private_name) + + @prop.setter + def prop(self, value): + if type(value) is not 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) From b390d87ef6b28b76d63f0701f078d991f0e4717b Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sat, 8 Aug 2020 14:45:28 +0200 Subject: [PATCH 50/58] Exercise 7.10 --- Work/timethis.py | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 Work/timethis.py diff --git a/Work/timethis.py b/Work/timethis.py new file mode 100644 index 000000000..b72fc6446 --- /dev/null +++ b/Work/timethis.py @@ -0,0 +1,11 @@ +# Exercise 7.10 + +from time import time + +def timethis(func): + def timed_func(*args, **kwargs): + start = time() + func(*args, **kwargs) + end = time() + print(f"{func.__module__}.{func.__name__}: {end - start:.2f} s") + return timed_func From 50b24ee032b29cc807478f4775beb3a9cf2bc737 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Sat, 8 Aug 2020 15:06:05 +0200 Subject: [PATCH 51/58] Exercise 7.11 --- Work/portfolio.py | 30 ++++++++++++++++++++++++++++-- Work/report.py | 14 ++------------ 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/Work/portfolio.py b/Work/portfolio.py index aa41278b7..9db0f2547 100644 --- a/Work/portfolio.py +++ b/Work/portfolio.py @@ -1,8 +1,12 @@ # Exercises 6.2, 6.3, 6.14 +# Exercise 7.11 + +from fileparse import parse_csv +from stock import Stock class Portfolio: - def __init__(self, holdings): - self._holdings = holdings + def __init__(self): + self._holdings = [] def __iter__(self): return iter(self._holdings) @@ -16,6 +20,28 @@ def __getitem__(self, index): def __contains__(self, name): return any(name == holding.name for holding in self._holdings) + def append(self, holding): + if not isinstance(holding, Stock): + raise TypeError("Expected a Stock instance") + self._holdings.append(holding) + + @classmethod + def from_csv(cls, lines, **opts): + self = cls() + + portfolio = parse_csv( + lines, + has_headers=True, + select=["name", "shares", "price"], + types={"shares": int, "price": float}, + **opts + ) + + for holding in portfolio: + self.append(Stock(**holding)) + + return self + @property def total_cost(self): return sum(holding.cost for holding in self._holdings) diff --git a/Work/report.py b/Work/report.py index f47e70f46..83ddc5289 100644 --- a/Work/report.py +++ b/Work/report.py @@ -5,11 +5,10 @@ # Exercises 4.3-4.8 # Exercise 5.6 # Exercise 6.2 -# Exercises 7.3, 7.4 +# Exercises 7.3, 7.4, 7.11 from fileparse import parse_csv from portfolio import Portfolio -from stock import Stock from tableformat import create_formatter import sys @@ -27,16 +26,7 @@ def read_prices(filename, **opts): def read_portfolio(filename, **opts): with open(filename, "rt") as file: - portfolio = parse_csv( - file, - has_headers=True, - select=["name", "shares", "price"], - types={"shares": int, "price": float}, - **opts - ) - return Portfolio([ - Stock(**holding) for holding in portfolio - ]) + return Portfolio.from_csv(file, **opts) def make_report(portfolio, prices): report = [] From acf06f439a835c8b6ee001f9d11aa004749a0cac Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Thu, 13 Aug 2020 19:17:28 +0200 Subject: [PATCH 52/58] Exercise 8.1 --- Work/test_stock.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 Work/test_stock.py diff --git a/Work/test_stock.py b/Work/test_stock.py new file mode 100644 index 000000000..7b870fe91 --- /dev/null +++ b/Work/test_stock.py @@ -0,0 +1,32 @@ +# Exercise 8.1 + +from stock import Stock +import unittest + +class TestStock(unittest.TestCase): + def test_create(self): + s = Stock("GOOG", 100, 490.1) + self.assertEqual(s.name, "GOOG") + self.assertEqual(s.shares, 100) + self.assertAlmostEqual(s.price, 490.1) + + def test_cost(self): + s = Stock("GOOG", 100, 490.1) + self.assertEqual(s.cost, 49010) + + def test_sell(self): + s = Stock("GOOG", 100, 490.1) + s.sell(50) + self.assertEqual(s.shares, 50) + s.sell(30) + self.assertEqual(s.shares, 20) + s.sell(30) + self.assertEqual(s.shares, 0) + + def test_shares(self): + s = Stock("GOOG", 100, 490.1) + with self.assertRaises(TypeError): + s.shares = "100" + +if __name__ == "__main__": + unittest.main() From cd7bd79124ab338f65959c69a0cb9f7602b88bc9 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Thu, 13 Aug 2020 19:47:12 +0200 Subject: [PATCH 53/58] Exercise 8.2 --- Work/fileparse.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Work/fileparse.py b/Work/fileparse.py index aa80ffde6..45ecbc98b 100644 --- a/Work/fileparse.py +++ b/Work/fileparse.py @@ -1,7 +1,10 @@ # Exercises 3.3-3.10, 3.17 +# Exercise 8.2 import csv -import sys +import logging + +log = logging.getLogger(__name__) def parse_csv_dicts(lines, delimiter, select=None, types=None, silence_errors=False): """ @@ -37,8 +40,8 @@ def parse_csv_dicts(lines, delimiter, select=None, types=None, silence_errors=Fa records.append(dict(zip(keys, line))) except ValueError as err: if not silence_errors: - print(f"Skipping line {lineno}:", line, file=sys.stderr) - print(f"Reason:", err, file=sys.stderr) + log.warning("Skipping line %d: %s", lineno, line) + log.debug("Reason: %s", err) return records @@ -59,8 +62,8 @@ def parse_csv_tuples(lines, delimiter, types=None, silence_errors=False): records.append(tuple(line)) except ValueError as err: if not silence_errors: - print(f"Skipping line {lineno}:", line, file=sys.stderr) - print(f"Reason:", err, file=sys.stderr) + log.warning("Skipping line %d: %s", lineno, line) + log.debug("Reason: %s", err) return records From b69fdb082830c3a6666235dcba3b528ebfd2ad88 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Thu, 13 Aug 2020 21:20:50 +0200 Subject: [PATCH 54/58] Exercise 8.3 --- Work/fileparse.py | 20 +++++++++----------- Work/pcost.py | 3 +++ Work/report.py | 7 +++++-- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/Work/fileparse.py b/Work/fileparse.py index 45ecbc98b..6535c6a52 100644 --- a/Work/fileparse.py +++ b/Work/fileparse.py @@ -6,7 +6,7 @@ log = logging.getLogger(__name__) -def parse_csv_dicts(lines, delimiter, select=None, types=None, silence_errors=False): +def parse_csv_dicts(lines, delimiter, select=None, types=None): """ Parse lines in CSV format into a list of dicts """ @@ -39,13 +39,12 @@ def parse_csv_dicts(lines, delimiter, select=None, types=None, silence_errors=Fa else: records.append(dict(zip(keys, line))) except ValueError as err: - if not silence_errors: - log.warning("Skipping line %d: %s", lineno, line) - log.debug("Reason: %s", err) + log.warning("Skipping line %d: %s", lineno, line) + log.debug("Reason: %s", err) return records -def parse_csv_tuples(lines, delimiter, types=None, silence_errors=False): +def parse_csv_tuples(lines, delimiter, types=None): """ Parse lines in CSV format into a list of tuples """ @@ -61,22 +60,21 @@ def parse_csv_tuples(lines, delimiter, types=None, silence_errors=False): line = [convert(val) for convert, val in zip(types, line)] records.append(tuple(line)) except ValueError as err: - if not silence_errors: - log.warning("Skipping line %d: %s", lineno, line) - log.debug("Reason: %s", err) + log.warning("Skipping line %d: %s", lineno, line) + log.debug("Reason: %s", err) return records # The revenge for straying off course... -def parse_csv(lines, has_headers, delimiter=',', select=None, types=None, silence_errors=False): +def parse_csv(lines, has_headers, delimiter=',', select=None, types=None): assert type(lines) is not str if has_headers: if types: assert type(types) is dict - return parse_csv_dicts(lines, delimiter, select, types, silence_errors) + return parse_csv_dicts(lines, delimiter, select, types) else: if select: raise RuntimeError("select requires column names") if types: assert type(types) is list - return parse_csv_tuples(lines, delimiter, types, silence_errors) + return parse_csv_tuples(lines, delimiter, types) diff --git a/Work/pcost.py b/Work/pcost.py index e816381ad..1e4ee6602 100644 --- a/Work/pcost.py +++ b/Work/pcost.py @@ -3,6 +3,7 @@ # Exercises 3.14-3.16 # Exercises 4.3, 4.4 # Exercise 6.2 +# Exercise 8.3 from report import read_portfolio import sys @@ -20,4 +21,6 @@ def main(argv): print("\u2211", portfolio_cost(filename)) if __name__ == "__main__": + import logging + logging.basicConfig(level=logging.ERROR) main(sys.argv) \ No newline at end of file diff --git a/Work/report.py b/Work/report.py index 83ddc5289..5ed8fb676 100644 --- a/Work/report.py +++ b/Work/report.py @@ -6,6 +6,7 @@ # Exercise 5.6 # Exercise 6.2 # Exercises 7.3, 7.4, 7.11 +# Exercise 8.3 from fileparse import parse_csv from portfolio import Portfolio @@ -52,8 +53,8 @@ def print_report(portfolio, prices, formatter): ]) def portfolio_report(portfolio_filename, prices_filename, fmt="txt"): - portfolio = read_portfolio(portfolio_filename, silence_errors=True) - prices = read_prices(prices_filename, silence_errors=True) + portfolio = read_portfolio(portfolio_filename) + prices = read_prices(prices_filename) print_report(portfolio, prices, create_formatter(fmt)()) def main(argv): @@ -67,4 +68,6 @@ def main(argv): portfolio_report(filename, "Data/prices.csv", fmt) if __name__ == "__main__": + import logging + logging.basicConfig(level=logging.ERROR) main(sys.argv) From 6d325de5fbc20bd982196b953e22dc3ddfbb1a5d Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Fri, 14 Aug 2020 11:06:58 +0200 Subject: [PATCH 55/58] Exercise 9.1 --- Work/porty/__init__.py | 0 Work/{ => porty}/fileparse.py | 0 Work/{ => porty}/follow.py | 3 ++- Work/{ => porty}/pcost.py | 5 +++-- Work/{ => porty}/portfolio.py | 5 +++-- Work/{ => porty}/report.py | 7 ++++--- Work/{ => porty}/stock.py | 3 ++- Work/{ => porty}/tableformat.py | 0 Work/{ => porty}/test_stock.py | 3 ++- Work/{ => porty}/ticker.py | 7 ++++--- Work/{ => porty}/typedproperty.py | 0 11 files changed, 20 insertions(+), 13 deletions(-) create mode 100644 Work/porty/__init__.py rename Work/{ => porty}/fileparse.py (100%) rename Work/{ => porty}/follow.py (92%) rename Work/{ => porty}/pcost.py (87%) rename Work/{ => porty}/portfolio.py (94%) rename Work/{ => porty}/report.py (93%) rename Work/{ => porty}/stock.py (90%) rename Work/{ => porty}/tableformat.py (100%) rename Work/{ => porty}/test_stock.py (95%) rename Work/{ => porty}/ticker.py (90%) rename Work/{ => porty}/typedproperty.py (100%) diff --git a/Work/porty/__init__.py b/Work/porty/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/Work/fileparse.py b/Work/porty/fileparse.py similarity index 100% rename from Work/fileparse.py rename to Work/porty/fileparse.py diff --git a/Work/follow.py b/Work/porty/follow.py similarity index 92% rename from Work/follow.py rename to Work/porty/follow.py index 489c03c52..efc7a4231 100644 --- a/Work/follow.py +++ b/Work/porty/follow.py @@ -1,4 +1,5 @@ # Exercises 6.5-6.7 +# Exercise 9.1 import os import time @@ -14,7 +15,7 @@ def follow(filename): yield line if __name__ == "__main__": - from report import read_portfolio + from .report import read_portfolio portfolio = read_portfolio("Data/portfolio.csv") diff --git a/Work/pcost.py b/Work/porty/pcost.py similarity index 87% rename from Work/pcost.py rename to Work/porty/pcost.py index 1e4ee6602..385c0c245 100644 --- a/Work/pcost.py +++ b/Work/porty/pcost.py @@ -4,8 +4,9 @@ # Exercises 4.3, 4.4 # Exercise 6.2 # Exercise 8.3 +# Exercise 9.1 -from report import read_portfolio +from .report import read_portfolio import sys def portfolio_cost(filename): @@ -23,4 +24,4 @@ def main(argv): if __name__ == "__main__": import logging logging.basicConfig(level=logging.ERROR) - main(sys.argv) \ No newline at end of file + main(sys.argv) diff --git a/Work/portfolio.py b/Work/porty/portfolio.py similarity index 94% rename from Work/portfolio.py rename to Work/porty/portfolio.py index 9db0f2547..01810846b 100644 --- a/Work/portfolio.py +++ b/Work/porty/portfolio.py @@ -1,8 +1,9 @@ # Exercises 6.2, 6.3, 6.14 # Exercise 7.11 +# Exercise 9.1 -from fileparse import parse_csv -from stock import Stock +from .fileparse import parse_csv +from .stock import Stock class Portfolio: def __init__(self): diff --git a/Work/report.py b/Work/porty/report.py similarity index 93% rename from Work/report.py rename to Work/porty/report.py index 5ed8fb676..4e7ed6291 100644 --- a/Work/report.py +++ b/Work/porty/report.py @@ -7,10 +7,11 @@ # Exercise 6.2 # Exercises 7.3, 7.4, 7.11 # Exercise 8.3 +# Exercise 9.1 -from fileparse import parse_csv -from portfolio import Portfolio -from tableformat import create_formatter +from .fileparse import parse_csv +from .portfolio import Portfolio +from .tableformat import create_formatter import sys def read_prices(filename, **opts): diff --git a/Work/stock.py b/Work/porty/stock.py similarity index 90% rename from Work/stock.py rename to Work/porty/stock.py index 384c9f7a5..6d4831c9f 100644 --- a/Work/stock.py +++ b/Work/porty/stock.py @@ -1,8 +1,9 @@ # Exercises 4.1, 4.2, 4.9 # Exercises 5.6-5.8 # Exercises 7.7-7.9 +# Exercise 9.1 -from typedproperty import String, Integer, Float +from .typedproperty import String, Integer, Float class Stock: __slots__ = ("_name", "_shares", "_price") diff --git a/Work/tableformat.py b/Work/porty/tableformat.py similarity index 100% rename from Work/tableformat.py rename to Work/porty/tableformat.py diff --git a/Work/test_stock.py b/Work/porty/test_stock.py similarity index 95% rename from Work/test_stock.py rename to Work/porty/test_stock.py index 7b870fe91..7bc824a8c 100644 --- a/Work/test_stock.py +++ b/Work/porty/test_stock.py @@ -1,6 +1,7 @@ # Exercise 8.1 +# Exercise 9.1 -from stock import Stock +from .stock import Stock import unittest class TestStock(unittest.TestCase): diff --git a/Work/ticker.py b/Work/porty/ticker.py similarity index 90% rename from Work/ticker.py rename to Work/porty/ticker.py index 2055dc413..ab683f9ef 100644 --- a/Work/ticker.py +++ b/Work/porty/ticker.py @@ -1,7 +1,8 @@ # Exercises 6.10-6.12 +# Exercise 9.1 import csv -from follow import follow +from .follow import follow def select_columns(rows, indices): for row in rows: @@ -28,8 +29,8 @@ def parse_stock_data(lines): return rows def ticker(filename, logfilename, fmt): - from report import read_portfolio - from tableformat import create_formatter + from .report import read_portfolio + from .tableformat import create_formatter portfolio = read_portfolio(filename) lines = follow(logfilename) diff --git a/Work/typedproperty.py b/Work/porty/typedproperty.py similarity index 100% rename from Work/typedproperty.py rename to Work/porty/typedproperty.py From 3b40d160b38a849c1a3e6f4c49a5db5b35394c18 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Fri, 14 Aug 2020 11:17:52 +0200 Subject: [PATCH 56/58] Exercise 9.2 --- Work/porty-app/README.md | 1 + Work/porty-app/portfolio.csv | 8 ++++++ Work/{ => porty-app}/porty/__init__.py | 0 Work/{ => porty-app}/porty/fileparse.py | 0 Work/{ => porty-app}/porty/follow.py | 0 Work/{ => porty-app}/porty/pcost.py | 0 Work/{ => porty-app}/porty/portfolio.py | 0 Work/{ => porty-app}/porty/report.py | 4 +-- Work/{ => porty-app}/porty/stock.py | 0 Work/{ => porty-app}/porty/tableformat.py | 0 Work/{ => porty-app}/porty/test_stock.py | 0 Work/{ => porty-app}/porty/ticker.py | 0 Work/{ => porty-app}/porty/typedproperty.py | 0 Work/porty-app/prices.csv | 31 +++++++++++++++++++++ 14 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 Work/porty-app/README.md create mode 100755 Work/porty-app/portfolio.csv rename Work/{ => porty-app}/porty/__init__.py (100%) rename Work/{ => porty-app}/porty/fileparse.py (100%) rename Work/{ => porty-app}/porty/follow.py (100%) rename Work/{ => porty-app}/porty/pcost.py (100%) rename Work/{ => porty-app}/porty/portfolio.py (100%) rename Work/{ => porty-app}/porty/report.py (96%) rename Work/{ => porty-app}/porty/stock.py (100%) rename Work/{ => porty-app}/porty/tableformat.py (100%) rename Work/{ => porty-app}/porty/test_stock.py (100%) rename Work/{ => porty-app}/porty/ticker.py (100%) rename Work/{ => porty-app}/porty/typedproperty.py (100%) create mode 100644 Work/porty-app/prices.csv diff --git a/Work/porty-app/README.md b/Work/porty-app/README.md new file mode 100644 index 000000000..123ace650 --- /dev/null +++ b/Work/porty-app/README.md @@ -0,0 +1 @@ +# PORTY diff --git a/Work/porty-app/portfolio.csv b/Work/porty-app/portfolio.csv new file mode 100755 index 000000000..6c16f65b5 --- /dev/null +++ b/Work/porty-app/portfolio.csv @@ -0,0 +1,8 @@ +name,shares,price +"AA",100,32.20 +"IBM",50,91.10 +"CAT",150,83.44 +"MSFT",200,51.23 +"GE",95,40.37 +"MSFT",50,65.10 +"IBM",100,70.44 diff --git a/Work/porty/__init__.py b/Work/porty-app/porty/__init__.py similarity index 100% rename from Work/porty/__init__.py rename to Work/porty-app/porty/__init__.py diff --git a/Work/porty/fileparse.py b/Work/porty-app/porty/fileparse.py similarity index 100% rename from Work/porty/fileparse.py rename to Work/porty-app/porty/fileparse.py diff --git a/Work/porty/follow.py b/Work/porty-app/porty/follow.py similarity index 100% rename from Work/porty/follow.py rename to Work/porty-app/porty/follow.py diff --git a/Work/porty/pcost.py b/Work/porty-app/porty/pcost.py similarity index 100% rename from Work/porty/pcost.py rename to Work/porty-app/porty/pcost.py diff --git a/Work/porty/portfolio.py b/Work/porty-app/porty/portfolio.py similarity index 100% rename from Work/porty/portfolio.py rename to Work/porty-app/porty/portfolio.py diff --git a/Work/porty/report.py b/Work/porty-app/porty/report.py similarity index 96% rename from Work/porty/report.py rename to Work/porty-app/porty/report.py index 4e7ed6291..675ffe330 100644 --- a/Work/porty/report.py +++ b/Work/porty-app/porty/report.py @@ -7,7 +7,7 @@ # Exercise 6.2 # Exercises 7.3, 7.4, 7.11 # Exercise 8.3 -# Exercise 9.1 +# Exercises 9.1, 9.2 from .fileparse import parse_csv from .portfolio import Portfolio @@ -66,7 +66,7 @@ def main(argv): else: fmt = "txt" - portfolio_report(filename, "Data/prices.csv", fmt) + portfolio_report(filename, "prices.csv", fmt) if __name__ == "__main__": import logging diff --git a/Work/porty/stock.py b/Work/porty-app/porty/stock.py similarity index 100% rename from Work/porty/stock.py rename to Work/porty-app/porty/stock.py diff --git a/Work/porty/tableformat.py b/Work/porty-app/porty/tableformat.py similarity index 100% rename from Work/porty/tableformat.py rename to Work/porty-app/porty/tableformat.py diff --git a/Work/porty/test_stock.py b/Work/porty-app/porty/test_stock.py similarity index 100% rename from Work/porty/test_stock.py rename to Work/porty-app/porty/test_stock.py diff --git a/Work/porty/ticker.py b/Work/porty-app/porty/ticker.py similarity index 100% rename from Work/porty/ticker.py rename to Work/porty-app/porty/ticker.py diff --git a/Work/porty/typedproperty.py b/Work/porty-app/porty/typedproperty.py similarity index 100% rename from Work/porty/typedproperty.py rename to Work/porty-app/porty/typedproperty.py diff --git a/Work/porty-app/prices.csv b/Work/porty-app/prices.csv new file mode 100644 index 000000000..6bbcb2099 --- /dev/null +++ b/Work/porty-app/prices.csv @@ -0,0 +1,31 @@ +"AA",9.22 +"AXP",24.85 +"BA",44.85 +"BAC",11.27 +"C",3.72 +"CAT",35.46 +"CVX",66.67 +"DD",28.47 +"DIS",24.22 +"GE",13.48 +"GM",0.75 +"HD",23.16 +"HPQ",34.35 +"IBM",106.28 +"INTC",15.72 +"JNJ",55.16 +"JPM",36.90 +"KFT",26.11 +"KO",49.16 +"MCD",58.99 +"MMM",57.10 +"MRK",27.58 +"MSFT",20.89 +"PFE",15.19 +"PG",51.94 +"T",24.79 +"UTX",52.61 +"VZ",29.26 +"WMT",49.74 +"XOM",69.35 + From 78da9578fb6eae070bf29a841da14572437d87e3 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Fri, 14 Aug 2020 11:25:12 +0200 Subject: [PATCH 57/58] Exercise 9.3 --- Work/porty-app/porty/pcost.py | 7 ++++--- Work/porty-app/porty/report.py | 7 ++++--- Work/porty-app/print-report.py | 6 ++++++ 3 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 Work/porty-app/print-report.py diff --git a/Work/porty-app/porty/pcost.py b/Work/porty-app/porty/pcost.py index 385c0c245..c183bedbe 100644 --- a/Work/porty-app/porty/pcost.py +++ b/Work/porty-app/porty/pcost.py @@ -4,7 +4,7 @@ # Exercises 4.3, 4.4 # Exercise 6.2 # Exercise 8.3 -# Exercise 9.1 +# Exercises 9.1, 9.3 from .report import read_portfolio import sys @@ -14,6 +14,9 @@ def portfolio_cost(filename): return portfolio.total_cost def main(argv): + import logging + logging.basicConfig(level=logging.ERROR) + if len(argv) == 2: filename = argv[1] else: @@ -22,6 +25,4 @@ def main(argv): print("\u2211", portfolio_cost(filename)) if __name__ == "__main__": - import logging - logging.basicConfig(level=logging.ERROR) main(sys.argv) diff --git a/Work/porty-app/porty/report.py b/Work/porty-app/porty/report.py index 675ffe330..81b78e76d 100644 --- a/Work/porty-app/porty/report.py +++ b/Work/porty-app/porty/report.py @@ -7,7 +7,7 @@ # Exercise 6.2 # Exercises 7.3, 7.4, 7.11 # Exercise 8.3 -# Exercises 9.1, 9.2 +# Exercises 9.1-9.3 from .fileparse import parse_csv from .portfolio import Portfolio @@ -59,6 +59,9 @@ def portfolio_report(portfolio_filename, prices_filename, fmt="txt"): print_report(portfolio, prices, create_formatter(fmt)()) def main(argv): + import logging + logging.basicConfig(level=logging.ERROR) + assert len(argv) >= 2 filename = argv[1] if len(argv) > 2: @@ -69,6 +72,4 @@ def main(argv): portfolio_report(filename, "prices.csv", fmt) if __name__ == "__main__": - import logging - logging.basicConfig(level=logging.ERROR) main(sys.argv) diff --git a/Work/porty-app/print-report.py b/Work/porty-app/print-report.py new file mode 100644 index 000000000..7c01e746c --- /dev/null +++ b/Work/porty-app/print-report.py @@ -0,0 +1,6 @@ +# Exercise 9.3 + +from porty.report import main +import sys + +main(sys.argv) From e7962142845559ef4cc70e88c6b588ee15dc9017 Mon Sep 17 00:00:00 2001 From: Andreas Prell Date: Fri, 14 Aug 2020 12:16:53 +0200 Subject: [PATCH 58/58] Exercise 9.5 --- Work/porty-app/MANIFEST.in | 1 + Work/porty-app/README.md | 2 +- Work/porty-app/setup.py | 12 ++++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 Work/porty-app/MANIFEST.in create mode 100644 Work/porty-app/setup.py diff --git a/Work/porty-app/MANIFEST.in b/Work/porty-app/MANIFEST.in new file mode 100644 index 000000000..3dff22d17 --- /dev/null +++ b/Work/porty-app/MANIFEST.in @@ -0,0 +1 @@ +include *.csv \ No newline at end of file diff --git a/Work/porty-app/README.md b/Work/porty-app/README.md index 123ace650..936cbb37e 100644 --- a/Work/porty-app/README.md +++ b/Work/porty-app/README.md @@ -1 +1 @@ -# PORTY +# porty diff --git a/Work/porty-app/setup.py b/Work/porty-app/setup.py new file mode 100644 index 000000000..36e68b8a0 --- /dev/null +++ b/Work/porty-app/setup.py @@ -0,0 +1,12 @@ +# Exercise 9.5 + +import setuptools + +setuptools.setup( + name="porty", + version="0.0.1", + author="Porty Portfolio", + author_email="porty@portfolio.com", + description="Practical Python Programming", + packages=setuptools.find_packages(), +)