diff --git a/Notes/00_Setup.md b/Notes/00_Setup.md index eb5b94db5..4861578cc 100644 --- a/Notes/00_Setup.md +++ b/Notes/00_Setup.md @@ -86,7 +86,7 @@ exercises. Feel free to look at this if you need a hint. To get the most out of the course however, you should try to create your own solutions first. -[Contents](Contents.md) +[Contents](Contents.md) \| [Next (1 Introduction to Python)](01_Introduction/00_Overview.md) diff --git a/Notes/01_Introduction/01_Python.md b/Notes/01_Introduction/01_Python.md index 0411531d5..21f9c4993 100644 --- a/Notes/01_Introduction/01_Python.md +++ b/Notes/01_Introduction/01_Python.md @@ -61,7 +61,7 @@ will be able to use it everywhere else. ### Exercise 1.1: Using Python as a Calculator -On your machine, start Python and use it as a calulator to solve the +On your machine, start Python and use it as a calculator to solve the following problem. Lucky Larry bought 75 shares of Google stock at a price of $235.14 per @@ -145,6 +145,11 @@ the class by typing in code slowly and thinking about it--not cut and pasting. ### Exercise 1.4: Where is My Bus? +Note: This was a whimsical example that was a real crowd-pleaser when +I taught this course in my office. You could query the bus and then +literally watch it pass by the window out front. Sadly, APIs rarely live +forever and it seems that this one has now ridden off into the sunset. --Dave + Try something more advanced and type these statements to find out how long people waiting on the corner of Clark street and Balmoral in Chicago will have to wait for the next northbound CTA \#22 bus: diff --git a/Notes/01_Introduction/02_Hello_world.md b/Notes/01_Introduction/02_Hello_world.md index 0a9883dbb..1cc1bcbe5 100644 --- a/Notes/01_Introduction/02_Hello_world.md +++ b/Notes/01_Introduction/02_Hello_world.md @@ -241,7 +241,7 @@ while num_bills * bill_thickness < sears_height: day = day + 1 num_bills = num_bills * 2 -print('Number of days', days) +print('Number of days', day) ``` The statements indented below the `while` will execute as long as the expression after the `while` is `true`. @@ -257,7 +257,7 @@ while num_bills * bill_thickness < sears_height: day = day + 1 num_bills = num_bills * 2 -print('Number of days', days) +print('Number of days', day) ``` Indentation groups the following statements together as the operations that repeat: diff --git a/Notes/01_Introduction/03_Numbers.md b/Notes/01_Introduction/03_Numbers.md index 1627bd4e4..c8cca87ef 100644 --- a/Notes/01_Introduction/03_Numbers.md +++ b/Notes/01_Introduction/03_Numbers.md @@ -77,7 +77,7 @@ c = -1.345e-10 Floats are represented as double precision using the native CPU representation [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754). This is the same as the `double` type in the programming language C. -> 17 digits or precision +> 17 digits of precision > Exponent from -308 to 308 Be aware that floating point numbers are inexact when representing decimals. @@ -213,14 +213,15 @@ Modify the program so that extra payment information can be more generally handl Make it so that the user can set these variables: ```python -extra_payment_start_month = 60 +extra_payment_start_month = 61 extra_payment_end_month = 108 extra_payment = 1000 ``` Make the program look at these variables and calculate the total paid appropriately. -How much will Dave pay if he pays an extra $1000/month for 4 years starting in year 5 of the mortgage? +How much will Dave pay if he pays an extra $1000/month for 4 years starting after the first +five years have already been paid? ### Exercise 1.10: Making a table @@ -234,9 +235,9 @@ The output should look something like this: 4 10736.44 497581.83 5 13420.55 496970.98 ... -308 874705.88 2971.43 -309 877389.99 299.7 -310 880074.1 -2383.16 +308 874705.88 3478.83 +309 877389.99 809.21 +310 880074.1 -1871.53 Total paid 880074.1 Months 310 ``` diff --git a/Notes/01_Introduction/04_Strings.md b/Notes/01_Introduction/04_Strings.md index 162f825f3..804f52514 100644 --- a/Notes/01_Introduction/04_Strings.md +++ b/Notes/01_Introduction/04_Strings.md @@ -28,8 +28,8 @@ Normally strings may only span a single line. Triple quotes capture all text enc including all formatting. There is no difference between using single (') versus double (") -quotes. The same type of quote used to start a string must be used to -terminate it. +quotes. *However, the same type of quote used to start a string must be used to +terminate it*. ### String escape codes diff --git a/Notes/01_Introduction/07_Functions.md b/Notes/01_Introduction/07_Functions.md index 5e891473d..6d56ec097 100644 --- a/Notes/01_Introduction/07_Functions.md +++ b/Notes/01_Introduction/07_Functions.md @@ -72,8 +72,8 @@ Exceptions can be caught and handled. To catch, use the `try - except` statement. ```python -for line in f: - fields = line.split() +for line in file: + fields = line.split(',') try: shares = int(fields[1]) except ValueError: diff --git a/Notes/02_Working_with_data/01_Datatypes.md b/Notes/02_Working_with_data/01_Datatypes.md index 5e7cfcbdd..cf79fd872 100644 --- a/Notes/02_Working_with_data/01_Datatypes.md +++ b/Notes/02_Working_with_data/01_Datatypes.md @@ -338,7 +338,7 @@ above. Change the number of shares to 75. ```python >>> d['shares'] = 75 >>> d -{'name': 'AA', 'shares': 75, 'price': 75} +{'name': 'AA', 'shares': 75, 'price': 32.2 } >>> ``` diff --git a/Notes/02_Working_with_data/02_Containers.md b/Notes/02_Working_with_data/02_Containers.md index 20ac55208..41833d4aa 100644 --- a/Notes/02_Working_with_data/02_Containers.md +++ b/Notes/02_Working_with_data/02_Containers.md @@ -195,12 +195,14 @@ unique = set(names) Additional set operations: ```python -names.add('CAT') # Add an item -names.remove('YHOO') # Remove an item - -s1 | s2 # Set union -s1 & s2 # Set intersection -s1 - s2 # Set difference +unique.add('CAT') # Add an item +unique.remove('YHOO') # Remove an item + +s1 = { 'a', 'b', 'c'} +s2 = { 'c', 'd' } +s1 | s2 # Set union { 'a', 'b', 'c', 'd' } +s1 & s2 # Set intersection { 'c' } +s1 - s2 # Set difference { 'a', 'b' } ``` ## Exercises diff --git a/Notes/02_Working_with_data/03_Formatting.md b/Notes/02_Working_with_data/03_Formatting.md index ae88c83b3..e041b530c 100644 --- a/Notes/02_Working_with_data/03_Formatting.md +++ b/Notes/02_Working_with_data/03_Formatting.md @@ -49,7 +49,8 @@ b Binary integer x Hexadecimal integer f Float as [-]m.dddddd e Float as [-]m.dddddde+-xx -g Float, but selective use of E notation s String +g Float, but selective use of E notation +s String c Character (from integer) ``` @@ -88,7 +89,7 @@ keyword arguments. ```python >>> '{name:>10s} {shares:10d} {price:10.2f}'.format(name='IBM', shares=100, price=91.1) ' IBM 100 91.10' ->>> '{:10s} {:10d} {:10.2f}'.format('IBM', 100, 91.1) +>>> '{:>10s} {:10d} {:10.2f}'.format('IBM', 100, 91.1) ' IBM 100 91.10' >>> ``` @@ -114,7 +115,9 @@ modeled after the C `printf()` as well. *Note: This is the only formatting available on byte strings.* ```python ->>> b'%s has %n messages' % (b'Dave', 37) +>>> b'%s has %d messages' % (b'Dave', 37) +b'Dave has 37 messages' +>>> b'%b has %d messages' % (b'Dave', 37) # %b may be used instead of %s b'Dave has 37 messages' >>> ``` diff --git a/Notes/02_Working_with_data/04_Sequences.md b/Notes/02_Working_with_data/04_Sequences.md index d74f352b9..51e2df4bf 100644 --- a/Notes/02_Working_with_data/04_Sequences.md +++ b/Notes/02_Working_with_data/04_Sequences.md @@ -242,7 +242,7 @@ for x, y in points: ``` When using multiple variables, each tuple is *unpacked* into a set of iteration variables. -The number of variables must match the of items in each tuple. +The number of variables must match the number of items in each tuple. ### zip() function diff --git a/Notes/02_Working_with_data/06_List_comprehension.md b/Notes/02_Working_with_data/06_List_comprehension.md index 11e5ae9e3..08dd5d13f 100644 --- a/Notes/02_Working_with_data/06_List_comprehension.md +++ b/Notes/02_Working_with_data/06_List_comprehension.md @@ -207,7 +207,7 @@ Show how you could build a list of tuples `(name, shares)` where `name` and `sha >>> ``` -If you change the the square brackets (`[`,`]`) to curly braces (`{`, `}`), you get something known as a set comprehension. +If you change the square brackets (`[`,`]`) to curly braces (`{`, `}`), you get something known as a set comprehension. This gives you unique or distinct values. For example, this determines the set of unique stock names that appear in `portfolio`: @@ -215,7 +215,7 @@ For example, this determines the set of unique stock names that appear in `portf ```python >>> names = { s['name'] for s in portfolio } >>> names -{ 'AA', 'GE', 'IBM', 'MSFT', 'CAT'] } +{ 'AA', 'GE', 'IBM', 'MSFT', 'CAT' } >>> ``` diff --git a/Notes/02_Working_with_data/07_Objects.md b/Notes/02_Working_with_data/07_Objects.md index 025ecf48f..8710e3038 100644 --- a/Notes/02_Working_with_data/07_Objects.md +++ b/Notes/02_Working_with_data/07_Objects.md @@ -11,7 +11,7 @@ Many operations in Python are related to *assigning* or *storing* values. ```python a = value # Assignment to a variable -s[n] = value # Assignment to an list +s[n] = value # Assignment to a list s.append(value) # Appending to a list d['key'] = value # Adding to a dictionary ``` diff --git a/Notes/03_Program_organization/00_Overview.md b/Notes/03_Program_organization/00_Overview.md index e860851d6..182d5ce48 100644 --- a/Notes/03_Program_organization/00_Overview.md +++ b/Notes/03_Program_organization/00_Overview.md @@ -14,7 +14,7 @@ some useful code templates for writing more useful scripts. * [3.3 Exception Handling](03_Error_checking.md) * [3.4 Modules](04_Modules.md) * [3.5 Main module](05_Main_module.md) -* [3.6 Design Discussion about Embracing Flexibilty](06_Design_discussion.md) +* [3.6 Design Discussion about Embracing Flexibility](06_Design_discussion.md) [Contents](../Contents.md) \| [Prev (2 Working With Data)](../02_Working_with_data/00_Overview.md) \| [Next (4 Classes and Objects)](../04_Classes_objects/00_Overview.md) diff --git a/Notes/03_Program_organization/01_Script.md b/Notes/03_Program_organization/01_Script.md index 11fdb1809..cd5626c5c 100644 --- a/Notes/03_Program_organization/01_Script.md +++ b/Notes/03_Program_organization/01_Script.md @@ -283,7 +283,7 @@ interactively after running your program: >>> files = ['Data/portfolio.csv', 'Data/portfolio2.csv'] >>> for name in files: print(f'{name:-^43s}') - portfolio_report(name, 'prices.csv') + portfolio_report(name, 'Data/prices.csv') print() ... look at the output ... diff --git a/Notes/03_Program_organization/02_More_functions.md b/Notes/03_Program_organization/02_More_functions.md index 318ddc802..579a4b980 100644 --- a/Notes/03_Program_organization/02_More_functions.md +++ b/Notes/03_Program_organization/02_More_functions.md @@ -209,7 +209,7 @@ of the function, it's better to use a class instead (more on this later). When you call a function, the argument variables are names that refer to the passed values. These values are NOT copies (see [section -2.7](../02_Working_with_data/07_Objects)). If mutable data types are +2.7](../02_Working_with_data/07_Objects.md)). If mutable data types are passed (e.g. lists, dicts), they can be modified *in-place*. ```python @@ -267,7 +267,7 @@ If you were doing a lot of file parsing for real, you’d probably want to clean some of this up and make it more general purpose. That's our goal. -Start this exercise by creating a new file called +Start this exercise by opening the file called `Work/fileparse.py`. This is where we will be doing our work. ### Exercise 3.3: Reading CSV Files @@ -332,13 +332,13 @@ follows: [{'price': '32.20', 'name': 'AA', 'shares': '100'}, {'price': '91.10', 'name': 'IBM', 'shares': '50'}, {'price': '83.44', 'name': 'CAT', 'shares': '150'}, {'price': '51.23', 'name': 'MSFT', 'shares': '200'}, {'price': '40.37', 'name': 'GE', 'shares': '95'}, {'price': '65.10', 'name': 'MSFT', 'shares': '50'}, {'price': '70.44', 'name': 'IBM', 'shares': '100'}] >>> # Read only some of the data ->>> shares_held = parse_csv('portfolio.csv', select=['name','shares']) +>>> shares_held = parse_csv('Data/portfolio.csv', select=['name','shares']) >>> shares_held [{'name': 'AA', 'shares': '100'}, {'name': 'IBM', 'shares': '50'}, {'name': 'CAT', 'shares': '150'}, {'name': 'MSFT', 'shares': '200'}, {'name': 'GE', 'shares': '95'}, {'name': 'MSFT', 'shares': '50'}, {'name': 'IBM', 'shares': '100'}] >>> ``` -An example of a column selector was given in [Exercise 2.23](../02_Working_with_data/06_List_comprehension). +An example of a column selector was given in [Exercise 2.23](../02_Working_with_data/06_List_comprehension.md). However, here’s one way to do it: ```python @@ -431,7 +431,7 @@ type-conversions to be applied to the returned data. For example: >>> ``` -You already explored this in [Exercise 2.24](../02_Working_with_data/07_Objects). +You already explored this in [Exercise 2.24](../02_Working_with_data/07_Objects.md). You'll need to insert the following fragment of code into your solution: ```python @@ -469,7 +469,7 @@ line of data isn’t interpreted as a header line. Also, you’ll need to make sure you don’t create dictionaries as there are no longer any column names to use for keys. -### Exercise 3.7: Picking a different column delimitier +### Exercise 3.7: Picking a different column delimiter Although CSV files are pretty common, it’s also possible that you could encounter a file that uses a different column separator such as diff --git a/Notes/03_Program_organization/03_Error_checking.md b/Notes/03_Program_organization/03_Error_checking.md index f403507c0..2c9938c5c 100644 --- a/Notes/03_Program_organization/03_Error_checking.md +++ b/Notes/03_Program_organization/03_Error_checking.md @@ -126,7 +126,7 @@ bar() There are about two-dozen built-in exceptions. Usually the name of the exception is indicative of what's wrong (e.g., a `ValueError` is raised because you supplied a bad value). This is not an -exhaustive list. Check the documentation for more. +exhaustive list. Check the [documentation](https://docs.python.org/3/library/exceptions.html) for more. ```python ArithmeticError @@ -341,7 +341,7 @@ As a general rule, it’s usually best to skip such tests and to just let the program fail on bad inputs. The traceback message will point at the source of the problem and can assist in debugging. -The main reason for adding the above check to avoid running the code +The main reason for adding the above check is to avoid running the code in a non-sensical mode (e.g., using a feature that requires column headers, but simultaneously specifying that there are no headers). diff --git a/Notes/03_Program_organization/04_Modules.md b/Notes/03_Program_organization/04_Modules.md index 260f715ca..7cc8e7a54 100644 --- a/Notes/03_Program_organization/04_Modules.md +++ b/Notes/03_Program_organization/04_Modules.md @@ -212,8 +212,8 @@ not readily accessible from the current working directory. ## Exercises For this exercise involving modules, it is critically important to -make sure you are running Python in a proper environment. Modules are -usually when programmers encounter problems with the current working +make sure you are running Python in a proper environment. Modules +often present new programmers with problems related to the current working directory or with Python's path settings. For this course, it is assumed that you're writing all of your code in the `Work/` directory. For best results, you should make sure you're also in that directory @@ -299,13 +299,13 @@ In section 2, you wrote a program `report.py` that produced a stock report like ``` Name Shares Price Change ---------- ---------- ---------- ---------- - AA 100 39.91 7.71 - IBM 50 106.11 15.01 - CAT 150 78.58 -4.86 - MSFT 200 30.47 -20.76 - GE 95 37.38 -2.99 - MSFT 50 30.47 -34.63 - IBM 100 106.11 35.67 + AA 100 9.22 -22.98 + IBM 50 106.28 15.18 + CAT 150 35.46 -47.98 + MSFT 200 20.89 -30.34 + GE 95 13.48 -26.89 + MSFT 50 20.89 -44.21 + IBM 100 106.28 35.84 ``` Take that program and modify it so that all of the input file @@ -316,6 +316,8 @@ and `read_prices()` functions to use the `parse_csv()` function. Use the interactive example at the start of this exercise as a guide. Afterwards, you should get exactly the same output as before. +### Exercise 3.13: Intentionally left blank (skip) + ### Exercise 3.14: Using more library imports In section 1, you wrote a program `pcost.py` that read a portfolio and computed its cost. diff --git a/Notes/03_Program_organization/05_Main_module.md b/Notes/03_Program_organization/05_Main_module.md index 93df5e747..c303e0fda 100644 --- a/Notes/03_Program_organization/05_Main_module.md +++ b/Notes/03_Program_organization/05_Main_module.md @@ -256,20 +256,20 @@ if __name__ == '__main__': In the file `report.py` add a `main()` function that accepts a list of command line options and produces the same output as before. You -should be able to run it interatively like this: +should be able to run it interactively like this: ```python >>> import report >>> report.main(['report.py', 'Data/portfolio.csv', 'Data/prices.csv']) Name Shares Price Change ---------- ---------- ---------- ---------- - AA 100 39.91 7.71 - IBM 50 106.11 15.01 - CAT 150 78.58 -4.86 - MSFT 200 30.47 -20.76 - GE 95 37.38 -2.99 - MSFT 50 30.47 -34.63 - IBM 100 106.11 35.67 + AA 100 9.22 -22.98 + IBM 50 106.28 15.18 + CAT 150 35.46 -47.98 + MSFT 200 20.89 -30.34 + GE 95 13.48 -26.89 + MSFT 50 20.89 -44.21 + IBM 100 106.28 35.84 >>> ``` @@ -291,16 +291,16 @@ execute as a script on the command line: bash $ python3 report.py Data/portfolio.csv Data/prices.csv Name Shares Price Change ---------- ---------- ---------- ---------- - AA 100 39.91 7.71 - IBM 50 106.11 15.01 - CAT 150 78.58 -4.86 - MSFT 200 30.47 -20.76 - GE 95 37.38 -2.99 - MSFT 50 30.47 -34.63 - IBM 100 106.11 35.67 + AA 100 9.22 -22.98 + IBM 50 106.28 15.18 + CAT 150 35.46 -47.98 + MSFT 200 20.89 -30.34 + GE 95 13.48 -26.89 + MSFT 50 20.89 -44.21 + IBM 100 106.28 35.84 bash $ python3 pcost.py Data/portfolio.csv Total cost: 44671.15 ``` -[Contents](../Contents.md) \| [Previous (3.4 Modules)](04_Modules.md) \| [Next (3.6 Design Discussion)](06_Design_discussion.md) \ No newline at end of file +[Contents](../Contents.md) \| [Previous (3.4 Modules)](04_Modules.md) \| [Next (3.6 Design Discussion)](06_Design_discussion.md) diff --git a/Notes/04_Classes_objects/02_Inheritance.md b/Notes/04_Classes_objects/02_Inheritance.md index 360635cca..13e747d6e 100644 --- a/Notes/04_Classes_objects/02_Inheritance.md +++ b/Notes/04_Classes_objects/02_Inheritance.md @@ -241,7 +241,7 @@ in your `report.py` program. It should look something like this: ```python def print_report(reportdata): ''' - Print a nicely formated table from a list of (name, shares, price, change) tuples. + Print a nicely formatted table from a list of (name, shares, price, change) tuples. ''' headers = ('Name','Shares','Price','Change') print('%10s %10s %10s %10s' % headers) @@ -277,7 +277,7 @@ inheritance instead. To start, focus on the steps that are involved in a creating a table. At the top of the table is a set of table headers. After that, rows -of table data appear. Let's take those steps and and put them into +of table data appear. Let's take those steps and put them into their own class. Create a file called `tableformat.py` and define the following class: @@ -312,7 +312,7 @@ the output. For example, like this: def print_report(reportdata, formatter): ''' - Print a nicely formated table from a list of (name, shares, price, change) tuples. + Print a nicely formatted table from a list of (name, shares, price, change) tuples. ''' formatter.headings(['Name','Shares','Price','Change']) for name, shares, price, change in reportdata: @@ -614,7 +614,7 @@ you can change the internal implementation to work in any way that you want. You can write all-custom code. You can use someone's third party package. You swap out one third-party package for a different package when you find a better one. It doesn't matter--none of -your application code will break as long as you preserve keep the +your application code will break as long as you preserve the interface. That's a powerful idea and it's one of the reasons why you might consider inheritance for something like this. diff --git a/Notes/04_Classes_objects/03_Special_methods.md b/Notes/04_Classes_objects/03_Special_methods.md index 460009ef2..72a8ef936 100644 --- a/Notes/04_Classes_objects/03_Special_methods.md +++ b/Notes/04_Classes_objects/03_Special_methods.md @@ -147,7 +147,8 @@ A method that has not yet been invoked by the function call operator `()` is kno It operates on the instance where it originated. ```python ->>> s = Stock('GOOG', 100, 490.10) >>> s +>>> s = Stock('GOOG', 100, 490.10) +>>> s >>> c = s.cost >>> c diff --git a/Notes/05_Object_model/02_Classes_encapsulation.md b/Notes/05_Object_model/02_Classes_encapsulation.md index 49feb3c81..11448db77 100644 --- a/Notes/05_Object_model/02_Classes_encapsulation.md +++ b/Notes/05_Object_model/02_Classes_encapsulation.md @@ -95,8 +95,8 @@ One approach: introduce accessor methods. class Stock: def __init__(self, name, shares, price): self.name = name - self.set_shares(shares) - self.price = price + self.set_shares(shares) + self.price = price # Function that layers the "get" operation def get_shares(self): @@ -184,7 +184,7 @@ class Stock: ... ``` -This allows you to drop the extra parantheses, hiding the fact that it's actually a method: +This allows you to drop the extra parentheses, hiding the fact that it's actually a method: ```python >>> s = Stock('GOOG', 100, 490.1) diff --git a/Notes/06_Generators/01_Iteration_protocol.md b/Notes/06_Generators/01_Iteration_protocol.md index c22fab6a2..787b02143 100644 --- a/Notes/06_Generators/01_Iteration_protocol.md +++ b/Notes/06_Generators/01_Iteration_protocol.md @@ -42,9 +42,9 @@ _iter = obj.__iter__() # Get iterator object while True: try: x = _iter.__next__() # Get next item + # statements ... except StopIteration: # No more items break - # statements ... ``` All the objects that work with the `for-loop` implement this low-level diff --git a/Notes/06_Generators/03_Producers_consumers.md b/Notes/06_Generators/03_Producers_consumers.md index ffd7845b5..5c1b8cb5f 100644 --- a/Notes/06_Generators/03_Producers_consumers.md +++ b/Notes/06_Generators/03_Producers_consumers.md @@ -20,7 +20,7 @@ def follow(f): ... # Consumer -for line in follow(f): # Consumes vale from `yield` above +for line in follow(f): # Consumes value from `yield` above ... ``` @@ -124,6 +124,7 @@ opening a file--it merely operates on a sequence of lines given to it as an argument. Now, try this: ``` +>>> from follow import follow >>> lines = follow('Data/stocklog.csv') >>> ibm = filematch(lines, 'IBM') >>> for line in ibm: diff --git a/Notes/07_Advanced_Topics/03_Returning_functions.md b/Notes/07_Advanced_Topics/03_Returning_functions.md index 30e6f0772..c5f1eb935 100644 --- a/Notes/07_Advanced_Topics/03_Returning_functions.md +++ b/Notes/07_Advanced_Topics/03_Returning_functions.md @@ -29,7 +29,7 @@ Adding 3 4 ### Local Variables -Observe how to inner function refers to variables defined by the outer +Observe how the inner function refers to variables defined by the outer function. ```python @@ -86,6 +86,7 @@ Consider a function like this: ```python def after(seconds, func): + import time time.sleep(seconds) func() ``` @@ -110,6 +111,7 @@ def add(x, y): return do_add def after(seconds, func): + import time time.sleep(seconds) func() diff --git a/Notes/07_Advanced_Topics/04_Function_decorators.md b/Notes/07_Advanced_Topics/04_Function_decorators.md index ed96ab552..4d24f0fc2 100644 --- a/Notes/07_Advanced_Topics/04_Function_decorators.md +++ b/Notes/07_Advanced_Topics/04_Function_decorators.md @@ -118,7 +118,7 @@ If you define a function, its name and module are stored in the ```python >>> def add(x,y): - return x+y + return x+y >>> add.__name__ 'add' @@ -145,9 +145,9 @@ Here is an example of how your decorator should work: >>> from timethis import timethis >>> @timethis def countdown(n): - while n > 0: - n -= 1 - + while n > 0: + n -= 1 + >>> countdown(10000000) __main__.countdown : 0.076562 >>> diff --git a/Notes/08_Testing_debugging/01_Testing.md b/Notes/08_Testing_debugging/01_Testing.md index f1138bfca..ed0793d67 100644 --- a/Notes/08_Testing_debugging/01_Testing.md +++ b/Notes/08_Testing_debugging/01_Testing.md @@ -270,7 +270,7 @@ Ran 1 tests in 0.000s OK ``` -Once you're satisifed that it works, write additional unit tests that +Once you're satisfied that it works, write additional unit tests that check for the following: - Make sure the `s.cost` property returns the correct value (49010.0) diff --git a/Notes/08_Testing_debugging/02_Logging.md b/Notes/08_Testing_debugging/02_Logging.md index 30243a705..3d9fa407c 100644 --- a/Notes/08_Testing_debugging/02_Logging.md +++ b/Notes/08_Testing_debugging/02_Logging.md @@ -269,7 +269,7 @@ You will notice that you don't see the output from the `log.debug()` operation. Type this to change the level. ``` ->>> logging.getLogger('fileparse').level = logging.DEBUG +>>> logging.getLogger('fileparse').setLevel(logging.DEBUG) >>> a = report.read_portfolio('Data/missing.csv') WARNING:fileparse:Row 4: Bad row: ['MSFT', '', '51.23'] DEBUG:fileparse:Row 4: Reason: invalid literal for int() with base 10: '' @@ -281,7 +281,7 @@ DEBUG:fileparse:Row 7: Reason: invalid literal for int() with base 10: '' Turn off all, but the most critical logging messages: ``` ->>> logging.getLogger('fileparse').level=logging.CRITICAL +>>> logging.getLogger('fileparse').setLevel(logging.CRITICAL) >>> a = report.read_portfolio('Data/missing.csv') >>> ``` diff --git a/Notes/08_Testing_debugging/03_Debugging.md b/Notes/08_Testing_debugging/03_Debugging.md index 378a6a36f..946161a19 100644 --- a/Notes/08_Testing_debugging/03_Debugging.md +++ b/Notes/08_Testing_debugging/03_Debugging.md @@ -4,7 +4,7 @@ ### Debugging Tips -So, you're program has crashed... +So, your program has crashed... ```bash bash % python3 blah.py @@ -147,7 +147,7 @@ For breakpoints location is one of the following. ```code (Pdb) b 45 # Line 45 in current file -(Pdb) b file.py:45 # Line 34 in file.py +(Pdb) b file.py:45 # Line 45 in file.py (Pdb) b foo # Function foo() in current file (Pdb) b module.foo # Function foo() in a module ``` diff --git a/Notes/09_Packages/02_Third_party.md b/Notes/09_Packages/02_Third_party.md index 2fa5e5508..2f1086cdd 100644 --- a/Notes/09_Packages/02_Third_party.md +++ b/Notes/09_Packages/02_Third_party.md @@ -50,6 +50,7 @@ the same steps as above: ```python >>> import numpy +>>> numpy >>> ``` @@ -120,7 +121,7 @@ different problem. ### Handling Third-Party Dependencies in Your Application If you have written an application and it has specific third-party -dependencies, one challange concerns the creation and preservation of +dependencies, one challenge concerns the creation and preservation of the environment that includes your code and the dependencies. Sadly, this has been an area of great confusion and frequent change over Python's lifetime. It continues to evolve even now. diff --git a/Notes/InstructorNotes.md b/Notes/InstructorNotes.md index 69808d7ea..bb9953e3c 100644 --- a/Notes/InstructorNotes.md +++ b/Notes/InstructorNotes.md @@ -47,7 +47,7 @@ topics. For example, scientists/engineers might want to know about data analysis or plotting. So, you can show them how to make a plot using matplotlib. Web programmers might want to know how to present stock market data on a web-page. So, you can talk about template -engines. Syadmins might want to do something with log files. So, you +engines. Sysadmins might want to do something with log files. So, you can point them at a log file of real-time streaming stock data. Software engineers might want to know about design. So, you can have them look at ways to encapsulate stock data inside an object or making diff --git a/README.md b/README.md index 21efa4540..2392fb3ec 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Welcome! -When I first learned Python nearly 25 years ago, I was immediately +When I first learned Python nearly 27 years ago, I was immediately struck by how I could productively apply it to all sorts of messy work projects. Fast-forward a decade and I found myself teaching others the same fun. The result of that teaching is this course--A no-nonsense @@ -8,17 +8,21 @@ treatment of Python that has been actively taught to more than 400 in-person groups since 2007. Traders, systems admins, astronomers, tinkerers, and even a few hundred rocket scientists who used Python to help land a rover on Mars--they've all taken this course. Now, I'm -pleased to make it available under a Creative Commons license. Enjoy! +pleased to make it available under a Creative Commons license--completely +free of spam, signups, and other nonsense. Enjoy! [GitHub Pages](https://dabeaz-course.github.io/practical-python) | [GitHub Repo](https://github.com/dabeaz-course/practical-python). ---David Beazley ([https://dabeaz.com](https://dabeaz.com)), [@dabeaz](https://twitter.com/dabeaz) +--David Beazley ([https://dabeaz.com](https://dabeaz.com)), [@dabeaz](https://mastodon.social/@dabeaz) + +(P.S. This course is about Python. If you want a Python course that's about programming, +you might consider [Advanced Programming with Python](https://www.dabeaz.com/advprog.html)) ## What is This? The material you see here is the heart of an instructor-led Python training course used for corporate training and professional -development. It has been in continual development since 2007 and +development. It was in continual development from 2007 to 2019 and battle tested in real-world classrooms. Usually, it's taught in-person over the span of three or four days--requiring approximately 25-35 hours of intense work. This includes the completion of @@ -36,7 +40,7 @@ doing a bit of Python programming. ## Course Objectives The goal of this course is to cover foundational aspects of Python -programming with an emphasis on script writing, data manipulation, and +programming with an emphasis on script writing, basic data manipulation, and program organization. By the end of this course, students should be able to start writing useful Python programs on their own or be able to understand and modify Python code written by their @@ -57,6 +61,9 @@ This is not a course on web development. That's a different circus. However, if you stick around for this circus, you'll still see some interesting acts--just nothing involving animals. +This is not a course on using tools that happen to be written +in Python. It's about learning the core Python language. + This is not a course for software engineers on how to write or maintain a one-million line Python application. I don't write programs like that, nor do most companies who use Python, and neither should @@ -122,6 +129,18 @@ caused everyone's head to explode or there was never enough time to cover it in the first place. Also, this is a course, not a Python reference manual. +### Q: Why isn't awesome `{command}` in awesome `{tool}` covered? + +The focus of this course is on learning the core Python language, +not learning the names of commands in tools. + +### Q: Is this course being maintained or updated? + +This course represents a "finished product" that was taught and +developed for more than decade. I have no plans to significantly +revise the material at this time, but will occasionally fix bugs and +add clarification. + ### Q: Do you accept pull requests? Bug reports are appreciated and may be filed through the diff --git a/Solutions/1_10/mortgage.py b/Solutions/1_10/mortgage.py index dabd94fc3..51e477519 100644 --- a/Solutions/1_10/mortgage.py +++ b/Solutions/1_10/mortgage.py @@ -7,7 +7,7 @@ month = 0 extra_payment = 1000.0 -extra_payment_start_month = 60 +extra_payment_start_month = 61 extra_payment_end_month = 108 while principal > 0: @@ -15,7 +15,7 @@ principal = principal * (1+rate/12) - payment total_paid = total_paid + payment - if month > extra_payment_start_month and month <= extra_payment_end_month: + if month >= extra_payment_start_month and month <= extra_payment_end_month: principal = principal - extra_payment total_paid = total_paid + extra_payment diff --git a/Work/bounce.py b/Work/bounce.py deleted file mode 100644 index 3660ddd82..000000000 --- a/Work/bounce.py +++ /dev/null @@ -1,3 +0,0 @@ -# bounce.py -# -# Exercise 1.5 diff --git a/Work/fileparse.py b/Work/fileparse.py deleted file mode 100644 index 1d499e733..000000000 --- a/Work/fileparse.py +++ /dev/null @@ -1,3 +0,0 @@ -# fileparse.py -# -# Exercise 3.3 diff --git a/Work/mortgage.py b/Work/mortgage.py deleted file mode 100644 index d527314e3..000000000 --- a/Work/mortgage.py +++ /dev/null @@ -1,3 +0,0 @@ -# mortgage.py -# -# Exercise 1.7 diff --git a/Work/pcost.py b/Work/pcost.py deleted file mode 100644 index e68aa20b4..000000000 --- a/Work/pcost.py +++ /dev/null @@ -1,3 +0,0 @@ -# pcost.py -# -# Exercise 1.27 diff --git a/Work/porty-app/MANIFEST.in b/Work/porty-app/MANIFEST.in new file mode 100644 index 000000000..d68a4959c --- /dev/null +++ b/Work/porty-app/MANIFEST.in @@ -0,0 +1 @@ +include *.csv diff --git a/Work/porty-app/README.txt b/Work/porty-app/README.txt new file mode 100644 index 000000000..8c50443e8 --- /dev/null +++ b/Work/porty-app/README.txt @@ -0,0 +1 @@ +README for the porty-app. 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-app/porty/__init__.py b/Work/porty-app/porty/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/Work/porty-app/porty/bounce.py b/Work/porty-app/porty/bounce.py new file mode 100644 index 000000000..d71020597 --- /dev/null +++ b/Work/porty-app/porty/bounce.py @@ -0,0 +1,12 @@ +# bounce.py +# +# Exercise 1.5 + +bounces = 0 +height = 100.0 + +while bounces < 10: + bounces += 1 + height *= 0.6 + print(bounces, end=" ") + print(round(height, 4)) diff --git a/Work/porty-app/porty/fileparse.py b/Work/porty-app/porty/fileparse.py new file mode 100644 index 000000000..749ea0349 --- /dev/null +++ b/Work/porty-app/porty/fileparse.py @@ -0,0 +1,59 @@ +# fileparse.py +# +# Exercise 3.3 +import csv +import logging + +log = logging.getLogger(__name__) + + +def parse_csv( + lines, + select=None, + types=None, + has_headers=True, + delimiter=",", + silence_errors=False, +): + """Parse a CSV file into a list of records""" + if select and not has_headers: + raise RuntimeError("select argument requires column headers") + + rows = csv.reader(lines, delimiter=delimiter) + + # Read the file headers + headers = next(rows) if has_headers else [] + + # If a column selector was given, find indices of the specified columns. + # Also narrow the set of headers used for resulting dictionaries. + if select: + indices = [headers.index(colname) for colname in select] + headers = select + else: + indices = [] + + records = [] + for rownum, row in enumerate(rows, start=1): + if not row: # Skip rows with no data + continue + # Filter the row if specific columns were selected + if indices: + row = [row[index] for index in indices] + + if types: + try: + row = [func(val) for func, val in zip(types, row)] + except ValueError as e: + if not silence_errors: + log.warning("Row %d: Couldn't convert %s", rownum, row) + log.debug("Row %d: Reason %s", rownum, e) + continue + + # Make a dictionary if there are headers, otherwise make a tuple. + if headers: + record = dict(zip(headers, row)) + else: + record = tuple(row) + records.append(record) + + return records diff --git a/Work/porty-app/porty/follow.py b/Work/porty-app/porty/follow.py new file mode 100644 index 000000000..d794add07 --- /dev/null +++ b/Work/porty-app/porty/follow.py @@ -0,0 +1,31 @@ +# follow.py + +import os +import time + + +def follow(filename): + """Follow a file.""" + f = open(filename) + f.seek(0, os.SEEK_END) + + while True: + line = f.readline() + if line == "": + time.sleep(0.1) # Sleep briefly and retry + continue + yield line + + +if __name__ == "__main__": + import report + + portfolio = report.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 name in portfolio: + print(f"{name:>10s} {price:>10.2f} {change:>10.2f}") diff --git a/Work/porty-app/porty/mortgage.py b/Work/porty-app/porty/mortgage.py new file mode 100644 index 000000000..da39ef5c3 --- /dev/null +++ b/Work/porty-app/porty/mortgage.py @@ -0,0 +1,32 @@ +# mortgage.py +# +# Exercise 1.7 + +# mortgage.py + +principal = 500000.0 +rate = 0.05 +payment = 2684.11 +total_paid = 0.0 +months = 0 +extra_payment_start_month = 61 +extra_payment_end_month = 108 +extra_payment = 1000 + +while principal > 0: + months += 1 + principal = principal * (1 + rate / 12) - payment + total_paid = total_paid + payment + + if months >= extra_payment_start_month and months <= extra_payment_end_month: + principal -= extra_payment + total_paid += extra_payment + + if principal < 0: + total_paid += principal + principal = 0 + + print(f"{months:3d} {total_paid:10.2f} {principal:10.2f}") + +print(f"Total paid {total_paid:0.2f}") +print(f"Months {months}") diff --git a/Work/porty-app/porty/pcost.py b/Work/porty-app/porty/pcost.py new file mode 100644 index 000000000..487f9d9e4 --- /dev/null +++ b/Work/porty-app/porty/pcost.py @@ -0,0 +1,25 @@ +# pcost.py +# +# Exercise 1.27 + +from . import report + + +def portfolio_cost(filename): + """Computes the total cost (shares*price) of a portfolio file.""" + portfolio = report.read_portfolio(filename) + return portfolio.total_cost + + +def main(args): + if len(args) != 2: + raise SystemExit("Usage: %s portfoliofile" % args[0]) + filename = args[1] + cost = portfolio_cost(filename) + print(f"Total cost: {cost:0.2f}") + + +if __name__ == "__main__": + import sys + + main(sys.argv) diff --git a/Work/porty-app/porty/portfolio.py b/Work/porty-app/porty/portfolio.py new file mode 100644 index 000000000..111938c17 --- /dev/null +++ b/Work/porty-app/porty/portfolio.py @@ -0,0 +1,49 @@ +# portfolio.py + +from . import fileparse, stock + + +class Portfolio: + def __init__(self): + self._holdings = [] + + def append(self, holding): + if not isinstance(holding, stock.Stock): + raise TypeError("Expected a Stock instance") + self._holdings.append(holding) + + @classmethod + def from_csv(cls, lines, **opts): + self = cls() + portdicts = fileparse.parse_csv( + lines, select=["name", "shares", "price"], types=[str, int, float], **opts + ) + + for d in portdicts: + self.append(stock.Stock(**d)) + + return self + + def __iter__(self): + return self._holdings.__iter__() + + def __len__(self): + return len(self._holdings) + + def __getitem__(self, index): + return self._holdings[index] + + def __contains__(self, name): + return any([s.name == name for s in self._holdings]) + + @property + def total_cost(self): + return sum([s.cost for s in self._holdings]) + + def tabulate_shares(self): + from collections import Counter + + total_shares = Counter() + for s in self._holdings: + total_shares[s.name] += s.shares + return total_shares diff --git a/Work/porty-app/porty/report.py b/Work/porty-app/porty/report.py new file mode 100644 index 000000000..1ab0f5881 --- /dev/null +++ b/Work/porty-app/porty/report.py @@ -0,0 +1,73 @@ +# report.py +# +# Exercise 2.4 + + +from . import fileparse, tableformat +from .portfolio import Portfolio + + +def read_portfolio(filename, **opts): + """Read a stock portfolio file into a list of dictionaries with keys name, shares, and price.""" + with open(filename) as lines: + portdicts = fileparse.parse_csv( + lines, select=["name", "shares", "price"], types=[str, int, float], **opts + ) + + portfolio = [stock.Stock(**d) for d in portdicts] + return Portfolio(portfolio) + + +def read_prices(filename): + """Read the prices file into a dictionary.""" + with open(filename) as lines: + return dict(fileparse.parse_csv(lines, types=[str, float], has_headers=False)) + + +def make_report(portfolio, prices): + """Returns a list of the rows of the report.""" + rows = [] + for s in portfolio: + curr_price = prices[s.name] + change = curr_price - s.price + rows.append((s.name, s.shares, curr_price, change)) + + return rows + + +def print_report(reportdata, formatter): + """Print out the report data in a formatted table.""" + formatter.headings(["Name", "Shares", "Price", "Change"]) + for name, shares, price, change in reportdata: + rowdata = [name, str(shares), f"{price:0.2f}", f"{change:0.2f}"] + formatter.row(rowdata) + + +def portfolio_report(portfolio_filename, prices_filename, fmt="txt"): + """Read portfolio and price files and print out a formatted report.""" + portfolio = read_portfolio(portfolio_filename) + prices = read_prices(prices_filename) + + report = make_report(portfolio, prices) + + formatter = tableformat.create_formatter(fmt) + print_report(report, formatter) + + +def main(args): + if len(args) != 4: + raise SystemExit("Usage: %s portfile pricefile format" % args[0]) + portfolio_report(args[1], args[2], args[3]) + + +if __name__ == "__main__": + import logging + import sys + + logging.basicConfig( + filename="app.log", # Name of the log file (omit to use stderr) + filemode="w", # File mode (use 'a' to append) + level=logging.WARNING, # Logging level (DEBUG, INFO, WARNING, ERROR, or CRITICAL) + ) + + main(sys.argv) diff --git a/Work/porty-app/porty/sears.py b/Work/porty-app/porty/sears.py new file mode 100644 index 000000000..41c44d6e4 --- /dev/null +++ b/Work/porty-app/porty/sears.py @@ -0,0 +1,15 @@ +# sears.py + +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) diff --git a/Work/porty-app/porty/stock.py b/Work/porty-app/porty/stock.py new file mode 100644 index 000000000..4428247c4 --- /dev/null +++ b/Work/porty-app/porty/stock.py @@ -0,0 +1,28 @@ +# stock.py + +from .typedproperty import Float, Integer, String + + +class Stock: + """An instance of a stock holding consisting of name, shares, and price.""" + + name = String("name") + shares = Integer("shares") + price = Float("price") + + def __init__(self, name, shares, price): + self.name = name + self.shares = shares + self.price = price + + def __repr__(self): + return f"Stock('{self.name}', {self.shares}, {self.price})" + + @property + def cost(self): + """Return the cost as shares*price.""" + return self.shares * self.price + + def sell(self, shares): + """Sell a number of shares and return the remaining number.""" + self.shares -= shares diff --git a/Work/porty-app/porty/tableformat.py b/Work/porty-app/porty/tableformat.py new file mode 100644 index 000000000..8024dba68 --- /dev/null +++ b/Work/porty-app/porty/tableformat.py @@ -0,0 +1,75 @@ +# tableformat.py + + +class TableFormatter: + def headings(self, headers): + """Emit the table headings.""" + raise NotImplementedError() + + def row(self, rowdata): + """Emit a single row of table data.""" + raise NotImplementedError() + + +class TextTableFormatter(TableFormatter): + """Emit a table in plan-text format.""" + + def headings(self, headers): + for h in headers: + print(f"{h:>10s}", end=" ") + print() + print(("-" * 10 + " ") * len(headers)) + + def row(self, rowdata): + for d in rowdata: + print(f"{d:>10s}", end=" ") + print() + + +class CSVTableFormatter(TableFormatter): + """Output portfolio data in CSV format.""" + + def headings(self, headers): + print(",".join(headers)) + + def row(self, rowdata): + print(",".join(rowdata)) + + +class HTMLTableFormatter(TableFormatter): + """Output portfolio data in HTML format.""" + + def headings(self, headers): + print("", end="") + for h in headers: + print(f"{h}", end="") + print("") + + def row(self, rowdata): + print("", end="") + for d in rowdata: + print(f"{d}", end="") + print("") + + +class FormatError(Exception): + pass + + +def create_formatter(name): + """Create an appropriate formatter given an output format name.""" + if name == "txt": + return TextTableFormatter() + if name == "csv": + return CSVTableFormatter() + if name == "html": + return HTMLTableFormatter() + raise FormatError(f"Unknown table format {name}") + + +def print_table(objects, columns, formatter): + """Make a nicely formatted table from a list of objects and attribute names.""" + formatter.headings(columns) + for obj in objects: + rowdata = [str(getattr(obj, name)) for name in columns] + formatter.row(rowdata) diff --git a/Work/porty-app/porty/test_stock.py b/Work/porty-app/porty/test_stock.py new file mode 100644 index 000000000..a12294804 --- /dev/null +++ b/Work/porty-app/porty/test_stock.py @@ -0,0 +1,31 @@ +# test_stock.py + +import unittest + +import stock + + +class TestStock(unittest.TestCase): + def test_create(self): + s = stock.Stock("GOOG", 100, 490.1) + self.assertEqual(s.name, "GOOG") + self.assertEqual(s.shares, 100) + self.assertEqual(s.price, 490.1) + + def test_cost(self): + s = stock.Stock("GOOG", 100, 490.1) + self.assertEqual(s.cost, 49010.0) + + def test_sell(self): + s = stock.Stock("GOOG", 100, 490.1) + s.sell(50) + self.assertEqual(s.shares, 50) + + def test_shares_is_integer(self): + s = stock.Stock("GOOG", 100, 490.1) + with self.assertRaises(TypeError): + s.shares = "200" + + +if __name__ == "__main__": + unittest.main() diff --git a/Work/porty-app/porty/ticker.py b/Work/porty-app/porty/ticker.py new file mode 100644 index 000000000..281024d85 --- /dev/null +++ b/Work/porty-app/porty/ticker.py @@ -0,0 +1,51 @@ +# ticker.py + +import csv + +from . import report, tableformat +from .follow import follow + + +def select_columns(rows, indices): + for row in rows: + yield [row[index] for index in indices] + + +def convert_types(rows, types): + for row in rows: + yield [func(val) for func, val in zip(types, row)] + + +def make_dicts(rows, headers): + for row in rows: + yield dict(zip(headers, row)) + + +def parse_stock_data(lines): + rows = csv.reader(lines) + rows = select_columns(rows, [0, 1, 4]) + rows = convert_types(rows, [str, float, float]) + rows = make_dicts(rows, ["name", "price", "change"]) + return rows + + +def ticker(portfile, logfile, fmt): + portfolio = report.read_portfolio(portfile) + rows = parse_stock_data(follow(logfile)) + rows = (row for row in rows if row["name"] in portfolio) + formatter = tableformat.create_formatter(fmt) + formatter.headings(["Name", "Price", "Change"]) + for row in rows: + formatter.row([row["name"], f"{row['price']:0.2f}", f"{row['change']:0.2f}"]) + + +def main(args): + if len(args) != 4: + raise SystemExit("Usage: %s portfoliofile logfile fmt" % args[0]) + ticker(args[1], args[2], args[3]) + + +if __name__ == "__main__": + import sys + + main(sys.argv) diff --git a/Work/porty-app/porty/timethis.py b/Work/porty-app/porty/timethis.py new file mode 100644 index 000000000..692533dfb --- /dev/null +++ b/Work/porty-app/porty/timethis.py @@ -0,0 +1,15 @@ +# timethis.py + +import time + + +def timethis(func): + def wrapper(*args, **kwargs): + start = time.time() + try: + return func(*args, **kwargs) + finally: + end = time.time() + print("%s.%s: %f" % (func.__module__, func.__name__, end - start)) + + return wrapper diff --git a/Work/porty-app/porty/typedproperty.py b/Work/porty-app/porty/typedproperty.py new file mode 100644 index 000000000..46ec92141 --- /dev/null +++ b/Work/porty-app/porty/typedproperty.py @@ -0,0 +1,22 @@ +# typedproperty.py + + +def typedproperty(name, expected_type): + private_name = "_" + name + + @property + def prop(self): + return getattr(self, private_name) + + @prop.setter + def prop(self, value): + if not isinstance(value, expected_type): + raise TypeError(f"Expected {expected_type}") + setattr(self, private_name, value) + + return prop + + +String = lambda name: typedproperty(name, str) +Integer = lambda name: typedproperty(name, int) +Float = lambda name: typedproperty(name, float) diff --git a/Work/porty-app/prices.csv b/Work/porty-app/prices.csv new file mode 100644 index 000000000..d317cdc15 --- /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 + diff --git a/Work/porty-app/print-report.py b/Work/porty-app/print-report.py new file mode 100644 index 000000000..0e03b472c --- /dev/null +++ b/Work/porty-app/print-report.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 +# print-report.py + +import sys + +from porty.report import main + +main(sys.argv) diff --git a/Work/porty-app/setup.py b/Work/porty-app/setup.py new file mode 100644 index 000000000..9b73c93ee --- /dev/null +++ b/Work/porty-app/setup.py @@ -0,0 +1,13 @@ +# setup.py + +import setuptools + +setuptools.setup( + name="porty", + version="0.0.1", + author="Rishi Chpora", + author_email="rqchopra@gmail.com", + description="Practical Python Code", + packages=setuptools.find_packages(), + scripts=["print-report.py"], +) diff --git a/Work/report.py b/Work/report.py deleted file mode 100644 index 47d5da7b1..000000000 --- a/Work/report.py +++ /dev/null @@ -1,3 +0,0 @@ -# report.py -# -# Exercise 2.4 diff --git a/_layouts/default.html b/_layouts/default.html index 5ab571ce5..81a8c456e 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -24,7 +24,7 @@