Editing. Added images

This commit is contained in:
David Beazley
2020-05-28 08:49:50 -05:00
parent 3bfa365519
commit a5cae9cdc2
9 changed files with 377 additions and 256 deletions

View File

@@ -1,5 +1,9 @@
[Contents](../Contents) \| [Previous (1.6 Files)](../01_Introduction/06_Files) \| [Next (2.2 Containers)](02_Containers)
# 2.1 Datatypes and Data structures # 2.1 Datatypes and Data structures
This section introduces data structures in the form of tuples and dictionaries.
### Primitive Datatypes ### Primitive Datatypes
Python has a few primitive types of data: Python has a few primitive types of data:
@@ -16,7 +20,8 @@ We learned about these in the introduction.
email_address = None email_address = None
``` ```
This type is often used as a placeholder for optional or missing value. `None` is often used as a placeholder for optional or missing value. It
evaluates as `False` in conditionals.
```python ```python
if email_address: if email_address:
@@ -25,8 +30,7 @@ if email_address:
### Data Structures ### Data Structures
Real programs have more complex data than the ones that can be easily represented by the datatypes learned so far. Real programs have more complex data. For example information about a stock holding:
For example information about a stock:
```code ```code
100 shares of GOOG at $490.10 100 shares of GOOG at $490.10
@@ -61,7 +65,7 @@ t = () # An empty tuple
w = ('GOOG', ) # A 1-item tuple w = ('GOOG', ) # A 1-item tuple
``` ```
Tuples are usually used to represent *simple* records or structures. Tuples are often used to represent *simple* records or structures.
Typically, it is a single *object* of multiple parts. A good analogy: *A tuple is like a single row in a database table.* Typically, it is a single *object* of multiple parts. A good analogy: *A tuple is like a single row in a database table.*
Tuple contents are ordered (like an array). Tuple contents are ordered (like an array).
@@ -73,9 +77,9 @@ shares = s[1] # 100
price = s[2] # 490.1 price = s[2] # 490.1
``` ```
However, th contents can't be modified. However, the contents can't be modified.
```pycon ```python
>>> s[1] = 75 >>> s[1] = 75
TypeError: object does not support item assignment TypeError: object does not support item assignment
``` ```
@@ -88,7 +92,7 @@ s = (s[0], 75, s[2])
### Tuple Packing ### Tuple Packing
Tuples are focused more on packing related items together into a single *entity*. Tuples are more about packing related items together into a single *entity*.
```python ```python
s = ('GOOG', 100, 490.1) s = ('GOOG', 100, 490.1)
@@ -105,7 +109,7 @@ name, shares, price = s
print('Cost', shares * price) print('Cost', shares * price)
``` ```
The number of variables must match the tuple structure. The number of variables on the left must match the tuple structure.
```python ```python
name, shares = s # ERROR name, shares = s # ERROR
@@ -116,19 +120,20 @@ ValueError: too many values to unpack
### Tuples vs. Lists ### Tuples vs. Lists
Tuples are NOT just read-only lists. Tuples are most ofter used for a *single item* consisting of multiple parts. Tuples look like read-only lists. However, tuples are most often used
Lists are usually a collection of distinct items, usually all of the same type. for a *single item* consisting of multiple parts. Lists are usually a
collection of distinct items, usually all of the same type.
```python ```python
record = ('GOOG', 100, 490.1) # A tuple representing a stock in a portfolio record = ('GOOG', 100, 490.1) # A tuple representing a record in a portfolio
symbols = [ 'GOOG', 'AAPL', 'IBM' ] # A List representing three stock symbols symbols = [ 'GOOG', 'AAPL', 'IBM' ] # A List representing three stock symbols
``` ```
### Dictionaries ### Dictionaries
A dictionary is a hash table or associative array. A dictionary is mapping of keys to values. It's also sometimes called a hash table or
It is a collection of values indexed by *keys*. These keys serve as field names. associative array. The keys serve as indices for accessing values.
```python ```python
s = { s = {
@@ -140,9 +145,9 @@ s = {
### Common operations ### Common operations
To read values from a dictionary use the key names. To get values from a dictionary use the key names.
```pycon ```python
>>> print(s['name'], s['shares']) >>> print(s['name'], s['shares'])
GOOG 100 GOOG 100
>>> s['price'] >>> s['price']
@@ -152,7 +157,7 @@ GOOG 100
To add or modify values assign using the key names. To add or modify values assign using the key names.
```pycon ```python
>>> s['shares'] = 75 >>> s['shares'] = 75
>>> s['date'] = '6/6/2007' >>> s['date'] = '6/6/2007'
>>> >>>
@@ -160,7 +165,7 @@ To add or modify values assign using the key names.
To delete a value use the `del` statement. To delete a value use the `del` statement.
```pycon ```python
>>> del s['date'] >>> del s['date']
>>> >>>
``` ```
@@ -178,11 +183,11 @@ s[2]
## Exercises ## Exercises
### Note In the last few exercises, you wrote a program that read a datafile
`Data/portfolio.csv`. Using the `csv` module, it is easy to read the
file row-by-row.
In the last few exercises, you wrote a program that read a datafile `Data/portfolio.csv`. Using the `csv` module, it is easy to read the file row-by-row. ```python
```pycon
>>> import csv >>> import csv
>>> f = open('Data/portfolio.csv') >>> f = open('Data/portfolio.csv')
>>> rows = csv.reader(f) >>> rows = csv.reader(f)
@@ -194,11 +199,13 @@ In the last few exercises, you wrote a program that read a datafile `Data/portfo
>>> >>>
``` ```
Although reading the file is easy, you often want to do more with the data than read it. Although reading the file is easy, you often want to do more with the
For instance, perhaps you want to store it and start performing some calculations on it. data than read it. For instance, perhaps you want to store it and
Unfortunately, a raw "row" of data doesnt give you enough to work with. For example, even a simple math calculation doesnt work: start performing some calculations on it. Unfortunately, a raw "row"
of data doesnt give you enough to work with. For example, even a
simple math calculation doesnt work:
```pycon ```python
>>> row = ['AA', '100', '32.20'] >>> row = ['AA', '100', '32.20']
>>> cost = row[1] * row[2] >>> cost = row[1] * row[2]
Traceback (most recent call last): Traceback (most recent call last):
@@ -207,8 +214,9 @@ TypeError: can't multiply sequence by non-int of type 'str'
>>> >>>
``` ```
To do more, you typically want to interpret the raw data in some way and turn it into a more useful kind of object so that you can work with it later. To do more, you typically want to interpret the raw data in some way
Two simple options are tuples or dictionaries. and turn it into a more useful kind of object so that you can work
with it later. Two simple options are tuples or dictionaries.
### Exercise 2.1: Tuples ### Exercise 2.1: Tuples
@@ -216,16 +224,17 @@ At the interactive prompt, create the following tuple that represents
the above row, but with the numeric columns converted to proper the above row, but with the numeric columns converted to proper
numbers: numbers:
```pycon ```python
>>> t = (row[0], int(row[1]), float(row[2])) >>> t = (row[0], int(row[1]), float(row[2]))
>>> t >>> t
('AA', 100, 32.2) ('AA', 100, 32.2)
>>> >>>
``` ```
Using this, you can now calculate the total cost by multiplying the shares and the price: Using this, you can now calculate the total cost by multiplying the
shares and the price:
```pycon ```python
>>> cost = t[1] * t[2] >>> cost = t[1] * t[2]
>>> cost >>> cost
3220.0000000000005 3220.0000000000005
@@ -244,15 +253,16 @@ surprising if you havent seen it before.
This happens in all programming languages that use floating point This happens in all programming languages that use floating point
decimals, but it often gets hidden when printing. For example: decimals, but it often gets hidden when printing. For example:
```pycon ```python
>>> print(f'{cost:0.2f}') >>> print(f'{cost:0.2f}')
3220.00 3220.00
>>> >>>
``` ```
Tuples are read-only. Verify this by trying to change the number of shares to 75. Tuples are read-only. Verify this by trying to change the number of
shares to 75.
```pycon ```python
>>> t[1] = 75 >>> t[1] = 75
Traceback (most recent call last): Traceback (most recent call last):
File "<stdin>", line 1, in <module> File "<stdin>", line 1, in <module>
@@ -260,9 +270,10 @@ TypeError: 'tuple' object does not support item assignment
>>> >>>
``` ```
Although you cant change tuple contents, you can always create a completely new tuple that replaces the old one. Although you cant change tuple contents, you can always create a
completely new tuple that replaces the old one.
```pycon ```python
>>> t = (t[0], 75, t[2]) >>> t = (t[0], 75, t[2])
>>> t >>> t
('AA', 75, 32.2) ('AA', 75, 32.2)
@@ -274,9 +285,10 @@ value is discarded. Although the above assignment might look like you
are modifying the tuple, you are actually creating a new tuple and are modifying the tuple, you are actually creating a new tuple and
throwing the old one away. throwing the old one away.
Tuples are often used to pack and unpack values into variables. Try the following: Tuples are often used to pack and unpack values into variables. Try
the following:
```pycon ```python
>>> name, shares, price = t >>> name, shares, price = t
>>> name >>> name
'AA' 'AA'
@@ -289,7 +301,7 @@ Tuples are often used to pack and unpack values into variables. Try the followin
Take the above variables and pack them back into a tuple Take the above variables and pack them back into a tuple
```pycon ```python
>>> t = (name, 2*shares, price) >>> t = (name, 2*shares, price)
>>> t >>> t
('AA', 150, 32.2) ('AA', 150, 32.2)
@@ -300,7 +312,7 @@ Take the above variables and pack them back into a tuple
An alternative to a tuple is to create a dictionary instead. An alternative to a tuple is to create a dictionary instead.
```pycon ```python
>>> d = { >>> d = {
'name' : row[0], 'name' : row[0],
'shares' : int(row[1]), 'shares' : int(row[1]),
@@ -313,25 +325,27 @@ An alternative to a tuple is to create a dictionary instead.
Calculate the total cost of this holding: Calculate the total cost of this holding:
```pycon ```python
>>> cost = d['shares'] * d['price'] >>> cost = d['shares'] * d['price']
>>> cost >>> cost
3220.0000000000005 3220.0000000000005
>>> >>>
``` ```
Compare this example with the same calculation involving tuples above. Change the number of shares to 75. Compare this example with the same calculation involving tuples
above. Change the number of shares to 75.
```pycon ```python
>>> d['shares'] = 75 >>> d['shares'] = 75
>>> d >>> d
{'name': 'AA', 'shares': 75, 'price': 75} {'name': 'AA', 'shares': 75, 'price': 75}
>>> >>>
``` ```
Unlike tuples, dictionaries can be freely modified. Add some attributes: Unlike tuples, dictionaries can be freely modified. Add some
attributes:
```pycon ```python
>>> d['date'] = (6, 11, 2007) >>> d['date'] = (6, 11, 2007)
>>> d['account'] = 12345 >>> d['account'] = 12345
>>> d >>> d
@@ -343,15 +357,16 @@ Unlike tuples, dictionaries can be freely modified. Add some attributes:
If you turn a dictionary into a list, youll get all of its keys: If you turn a dictionary into a list, youll get all of its keys:
```pycon ```python
>>> list(d) >>> list(d)
['name', 'shares', 'price', 'date', 'account'] ['name', 'shares', 'price', 'date', 'account']
>>> >>>
``` ```
Similarly, if you use the `for` statement to iterate on a dictionary, you will get the keys: Similarly, if you use the `for` statement to iterate on a dictionary,
you will get the keys:
```pycon ```python
>>> for k in d: >>> for k in d:
print('k =', k) print('k =', k)
@@ -365,7 +380,7 @@ k = account
Try this variant that performs a lookup at the same time: Try this variant that performs a lookup at the same time:
```pycon ```python
>>> for k in d: >>> for k in d:
print(k, '=', d[k]) print(k, '=', d[k])
@@ -379,7 +394,7 @@ account = 12345
You can also obtain all of the keys using the `keys()` method: You can also obtain all of the keys using the `keys()` method:
```pycon ```python
>>> keys = d.keys() >>> keys = d.keys()
>>> keys >>> keys
dict_keys(['name', 'shares', 'price', 'date', 'account']) dict_keys(['name', 'shares', 'price', 'date', 'account'])
@@ -388,20 +403,24 @@ dict_keys(['name', 'shares', 'price', 'date', 'account'])
`keys()` is a bit unusual in that it returns a special `dict_keys` object. `keys()` is a bit unusual in that it returns a special `dict_keys` object.
This is an overlay on the original dictionary that always gives you the current keys—even if the dictionary changes. For example, try this: This is an overlay on the original dictionary that always gives you
the current keys—even if the dictionary changes. For example, try
this:
```pycon ```python
>>> del d['account'] >>> del d['account']
>>> keys >>> keys
dict_keys(['name', 'shares', 'price', 'date']) dict_keys(['name', 'shares', 'price', 'date'])
>>> >>>
``` ```
Carefully notice that the `'account'` disappeared from `keys` even though you didnt call `d.keys()` again. Carefully notice that the `'account'` disappeared from `keys` even
though you didnt call `d.keys()` again.
A more elegant way to work with keys and values together is to use the `items()` method. This gives you `(key, value)` tuples: A more elegant way to work with keys and values together is to use the
`items()` method. This gives you `(key, value)` tuples:
```pycon ```python
>>> items = d.items() >>> items = d.items()
>>> items >>> items
dict_items([('name', 'AA'), ('shares', 75), ('price', 32.2), ('date', (6, 11, 2007))]) dict_items([('name', 'AA'), ('shares', 75), ('price', 32.2), ('date', (6, 11, 2007))])
@@ -415,9 +434,10 @@ date = (6, 11, 2007)
>>> >>>
``` ```
If you have tuples such as `items`, you can create a dictionary using the `dict()` function. Try it: If you have tuples such as `items`, you can create a dictionary using
the `dict()` function. Try it:
```pycon ```python
>>> items >>> items
dict_items([('name', 'AA'), ('shares', 75), ('price', 32.2), ('date', (6, 11, 2007))]) dict_items([('name', 'AA'), ('shares', 75), ('price', 32.2), ('date', (6, 11, 2007))])
>>> d = dict(items) >>> d = dict(items)

View File

@@ -1,5 +1,9 @@
[Contents](../Contents) \| [Previous (2.1 Datatypes)](01_Datatypes) \| [Next (2.3 Formatting)](03_Formatting)
# 2.2 Containers # 2.2 Containers
This section discusses lists, dictionaries, and sets.
### Overview ### Overview
Programs often have to work with many objects. Programs often have to work with many objects.
@@ -11,11 +15,11 @@ There are three main choices to use.
* Lists. Ordered data. * Lists. Ordered data.
* Dictionaries. Unordered data. * Dictionaries. Unordered data.
* Sets. Unordered collection * Sets. Unordered collection of unique items.
### Lists as a Container ### Lists as a Container
Use a list when the order of the data matters. Remember that lists can hold any kind of objects. Use a list when the order of the data matters. Remember that lists can hold any kind of object.
For example, a list of tuples. For example, a list of tuples.
```python ```python
@@ -47,7 +51,7 @@ An example when reading records from a file.
```python ```python
records = [] # Initial empty list records = [] # Initial empty list
with open('portfolio.csv', 'rt') as f: with open('Data/portfolio.csv', 'rt') as f:
for line in f: for line in f:
row = line.split(',') row = line.split(',')
records.append((row[0], int(row[1])), float(row[2])) records.append((row[0], int(row[1])), float(row[2]))
@@ -69,7 +73,7 @@ prices = {
Here are some simple lookups: Here are some simple lookups:
```pycon ```python
>>> prices['IBM'] >>> prices['IBM']
93.37 93.37
>>> prices['GOOG'] >>> prices['GOOG']
@@ -95,7 +99,7 @@ An example populating the dict from the contents of a file.
```python ```python
prices = {} # Initial empty dict prices = {} # Initial empty dict
with open('prices.csv', 'rt') as f: with open('Data/prices.csv', 'rt') as f:
for line in f: for line in f:
row = line.split(',') row = line.split(',')
prices[row[0]] = float(row[1]) prices[row[0]] = float(row[1])
@@ -143,12 +147,13 @@ holidays = {
Then to access: Then to access:
```pycon ```python
>>> holidays[3, 14] 'Pi day' >>> holidays[3, 14]
'Pi day'
>>> >>>
``` ```
*Neither a list nor another dictionary can serve as a dictionary key, because lists and dictionaries are mutable.* *Neither a list, a set, nor another dictionary can serve as a dictionary key, because lists and dictionaries are mutable.*
### Sets ### Sets
@@ -162,7 +167,7 @@ tech_stocks = set(['IBM', 'AAPL', 'MSFT'])
Sets are useful for membership tests. Sets are useful for membership tests.
```pycon ```python
>>> tech_stocks >>> tech_stocks
set(['AAPL', 'IBM', 'MSFT']) set(['AAPL', 'IBM', 'MSFT'])
>>> 'IBM' in tech_stocks >>> 'IBM' in tech_stocks
@@ -194,6 +199,9 @@ s1 - s2 # Set difference
## Exercises ## Exercises
In these exercises, you start building one of the major programs used
for the rest of this course. Do your work in the file `Work/report.py`.
### Exercise 2.4: A list of tuples ### Exercise 2.4: A list of tuples
The file `Data/portfolio.csv` contains a list of stocks in a The file `Data/portfolio.csv` contains a list of stocks in a
@@ -227,7 +235,8 @@ that file, define a function `read_portfolio(filename)` that opens a
given portfolio file and reads it into a list of tuples. To do this, given portfolio file and reads it into a list of tuples. To do this,
youre going to make a few minor modifications to the above code. youre going to make a few minor modifications to the above code.
First, instead of defining `total_cost = 0`, youll make a variable thats initially set to an empty list. For example: First, instead of defining `total_cost = 0`, youll make a variable
thats initially set to an empty list. For example:
```python ```python
portfolio = [] portfolio = []
@@ -251,7 +260,7 @@ interpreter):
*Hint: Use `-i` when executing the file in the terminal* *Hint: Use `-i` when executing the file in the terminal*
```pycon ```python
>>> portfolio = read_portfolio('Data/portfolio.csv') >>> portfolio = read_portfolio('Data/portfolio.csv')
>>> portfolio >>> portfolio
[('AA', 100, 32.2), ('IBM', 50, 91.1), ('CAT', 150, 83.44), ('MSFT', 200, 51.23), [('AA', 100, 32.2), ('IBM', 50, 91.1), ('CAT', 150, 83.44), ('MSFT', 200, 51.23),
@@ -291,14 +300,15 @@ That said, you can also rewrite the last for-loop using a statement like this:
### Exercise 2.5: List of Dictionaries ### Exercise 2.5: List of Dictionaries
Take the function you wrote in part (a) and modify to represent each Take the function you wrote in Exercise 2.4 and modify to represent each
stock in the portfolio with a dictionary instead of a tuple. In this stock in the portfolio with a dictionary instead of a tuple. In this
dictionary use the field names of "name", "shares", and "price" to dictionary use the field names of "name", "shares", and "price" to
represent the different columns in the input file. represent the different columns in the input file.
Experiment with this new function in the same manner as you did in Exercise 2.4. Experiment with this new function in the same manner as you did in
Exercise 2.4.
```pycon ```python
>>> portfolio = read_portfolio('portfolio.csv') >>> portfolio = read_portfolio('portfolio.csv')
>>> portfolio >>> portfolio
[{'name': 'AA', 'shares': 100, 'price': 32.2}, {'name': 'IBM', 'shares': 50, 'price': 91.1}, [{'name': 'AA', 'shares': 100, 'price': 32.2}, {'name': 'IBM', 'shares': 50, 'price': 91.1},
@@ -327,7 +337,7 @@ often preferred because the resulting code is easier to read later.
Viewing large dictionaries and lists can be messy. To clean up the Viewing large dictionaries and lists can be messy. To clean up the
output for debugging, considering using the `pprint` function. output for debugging, considering using the `pprint` function.
```pycon ```python
>>> from pprint import pprint >>> from pprint import pprint
>>> pprint(portfolio) >>> pprint(portfolio)
[{'name': 'AA', 'price': 32.2, 'shares': 100}, [{'name': 'AA', 'price': 32.2, 'shares': 100},
@@ -346,7 +356,7 @@ A dictionary is a useful way to keep track of items where you want to
look up items using an index other than an integer. In the Python look up items using an index other than an integer. In the Python
shell, try playing with a dictionary: shell, try playing with a dictionary:
```pycon ```python
>>> prices = { } >>> prices = { }
>>> prices['IBM'] = 92.45 >>> prices['IBM'] = 92.45
>>> prices['MSFT'] = 45.12 >>> prices['MSFT'] = 45.12
@@ -388,7 +398,7 @@ A few little tips that youll need for this part. First, make sure you
use the `csv` module just as you did before—theres no need to use the `csv` module just as you did before—theres no need to
reinvent the wheel here. reinvent the wheel here.
```pycon ```python
>>> import csv >>> import csv
>>> f = open('Data/prices.csv', 'r') >>> f = open('Data/prices.csv', 'r')
>>> rows = csv.reader(f) >>> rows = csv.reader(f)
@@ -409,7 +419,8 @@ an empty list—meaning no data was present on that line.
Theres a possibility that this could cause your program to die with Theres a possibility that this could cause your program to die with
an exception. Use the `try` and `except` statements to catch this as an exception. Use the `try` and `except` statements to catch this as
appropriate. appropriate. Thought: would it be better to guard against bad data with
an `if`-statement instead?
Once you have written your `read_prices()` function, test it Once you have written your `read_prices()` function, test it
interactively to make sure it works: interactively to make sure it works:
@@ -425,10 +436,11 @@ interactively to make sure it works:
### Exercise 2.7: Finding out if you can retire ### Exercise 2.7: Finding out if you can retire
Tie all of this work together by adding the statements to your Tie all of this work together by adding a few additional statements to
`report.py` program. It takes the list of stocks in Exercise 2.5 and your `report.py` program that compute gain/loss. These statements
the dictionary of prices in Exercise 2.6 and computes the current should take the list of stocks in Exercise 2.5 and the dictionary of
value of the portfolio along with the gain/loss. prices in Exercise 2.6 and computes the current value of the portfolio
along with the gain/loss.
[Contents](../Contents) \| [Previous (2.1 Datatypes)](01_Datatypes) \| [Next (2.3 Formatting)](03_Formatting) [Contents](../Contents) \| [Previous (2.1 Datatypes)](01_Datatypes) \| [Next (2.3 Formatting)](03_Formatting)

View File

@@ -1,7 +1,9 @@
[Contents](../Contents) \| [Previous (2.2 Containers)](02_Containers) \| [Next (2.4 Sequences)](04_Sequences)
# 2.3 Formatting # 2.3 Formatting
This is a slight digression, but when you work with data, you often want to This section is a slight digression, but when you work with data, you
produce structured output (tables, etc.). For example: often want to produce structured output (tables, etc.). For example:
```code ```code
Name Shares Price Name Shares Price
@@ -61,7 +63,7 @@ Common modifiers adjust the field width and decimal precision. This is a partia
### Dictionary Formatting ### Dictionary Formatting
You can use the `format_map()` method on strings. You can use the `format_map()` method to apply string formatting to a dictionary of values:
```python ```python
>>> s = { >>> s = {
@@ -74,7 +76,23 @@ You can use the `format_map()` method on strings.
>>> >>>
``` ```
It uses the same `f-strings` but takes the values from the supplied dictionary. It uses the same codes as `f-strings` but takes the values from the
supplied dictionary.
### format() method
There is a method `format()` that can apply formatting to arguments or
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)
' IBM 100 91.10'
>>>
```
Frankly, `format()` is a bit verbose. I prefer f-strings.
### C-Style Formatting ### C-Style Formatting
@@ -89,7 +107,8 @@ You can also use the formatting operator `%`.
'3.14' '3.14'
``` ```
This requires a single item or a tuple on the right. Format codes are modeled after the C `printf()` as well. This requires a single item or a tuple on the right. Format codes are
modeled after the C `printf()` as well.
*Note: This is the only formatting available on byte strings.* *Note: This is the only formatting available on byte strings.*
@@ -101,25 +120,6 @@ b'Dave has 37 messages'
## Exercises ## Exercises
In Exercise 2.7, you wrote a program called `report.py` that computed the gain/loss of a
stock portfolio. In this exercise, you're going to modify it to produce a table like this:
```code
Name Shares Price Change
---------- ---------- ---------- ----------
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
```
In this report, "Price" is the current share price of the stock and
"Change" is the change in the share price from the initial purchase
price.
### Exercise 2.8: How to format numbers ### Exercise 2.8: How to format numbers
A common problem with printing numbers is specifying the number of A common problem with printing numbers is specifying the number of
@@ -145,7 +145,7 @@ Full documentation on the formatting codes used f-strings can be found
[here](https://docs.python.org/3/library/string.html#format-specification-mini-language). Formatting [here](https://docs.python.org/3/library/string.html#format-specification-mini-language). Formatting
is also sometimes performed using the `%` operator of strings. is also sometimes performed using the `%` operator of strings.
```pycon ```python
>>> print('%0.4f' % value) >>> print('%0.4f' % value)
42863.1000 42863.1000
>>> print('%16.2f' % value) >>> print('%16.2f' % value)
@@ -159,7 +159,7 @@ Documentation on various codes used with `%` can be found
Although its commonly used with `print`, string formatting is not tied to printing. Although its commonly used with `print`, string formatting is not tied to printing.
If you want to save a formatted string. Just assign it to a variable. If you want to save a formatted string. Just assign it to a variable.
```pycon ```python
>>> f = '%0.4f' % value >>> f = '%0.4f' % value
>>> f >>> f
'42863.1000' '42863.1000'
@@ -168,6 +168,26 @@ If you want to save a formatted string. Just assign it to a variable.
### Exercise 2.9: Collecting Data ### Exercise 2.9: Collecting Data
In Exercise 2.7, you wrote a program called `report.py` that computed the gain/loss of a
stock portfolio. In this exercise, you're going to start modifying it to produce a table like this:
```
Name Shares Price Change
---------- ---------- ---------- ----------
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
```
In this report, "Price" is the current share price of the stock and
"Change" is the change in the share price from the initial purchase
price.
In order to generate the above report, youll first want to collect In order to generate the above report, youll first want to collect
all of the data shown in the table. Write a function `make_report()` all of the data shown in the table. Write a function `make_report()`
that takes a list of stocks and dictionary of prices as input and that takes a list of stocks and dictionary of prices as input and
@@ -176,7 +196,7 @@ returns a list of tuples containing the rows of the above table.
Add this function to your `report.py` file. Heres how it should work Add this function to your `report.py` file. Heres how it should work
if you try it interactively: if you try it interactively:
```pycon ```python
>>> portfolio = read_portfolio('Data/portfolio.csv') >>> portfolio = read_portfolio('Data/portfolio.csv')
>>> prices = read_prices('Data/prices.csv') >>> prices = read_prices('Data/prices.csv')
>>> report = make_report(portfolio, prices) >>> report = make_report(portfolio, prices)
@@ -197,7 +217,7 @@ if you try it interactively:
Redo the for-loop in Exercise 2.9, but change the print statement to Redo the for-loop in Exercise 2.9, but change the print statement to
format the tuples. format the tuples.
```pycon ```python
>>> for r in report: >>> for r in report:
print('%10s %10d %10.2f %10.2f' % r) print('%10s %10d %10.2f %10.2f' % r)
@@ -211,7 +231,7 @@ format the tuples.
You can also expand the values and use f-strings. For example: You can also expand the values and use f-strings. For example:
```pycon ```python
>>> for name, shares, price, change in report: >>> for name, shares, price, change in report:
print(f'{name:>10s} {shares:>10d} {price:>10.2f} {change:>10.2f}') print(f'{name:>10s} {shares:>10d} {price:>10.2f} {change:>10.2f}')
@@ -251,32 +271,32 @@ This string is just a bunch of "-" characters under each field name. For example
When youre done, your program should produce the table shown at the top of this exercise. When youre done, your program should produce the table shown at the top of this exercise.
```code ```
Name Shares Price Change Name Shares Price Change
---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
AA 100 9.22 -22.98 AA 100 9.22 -22.98
IBM 50 106.28 15.18 IBM 50 106.28 15.18
CAT 150 35.46 -47.98 CAT 150 35.46 -47.98
MSFT 200 20.89 -30.34 MSFT 200 20.89 -30.34
GE 95 13.48 -26.89 GE 95 13.48 -26.89
MSFT 50 20.89 -44.21 MSFT 50 20.89 -44.21
IBM 100 106.28 35.84 IBM 100 106.28 35.84
``` ```
### Exercise 2.12: Formatting Challenge ### Exercise 2.12: Formatting Challenge
How would you modify your code so that the price includes the currency symbol ($) and the output looks like this: How would you modify your code so that the price includes the currency symbol ($) and the output looks like this:
```code ```
Name Shares Price Change Name Shares Price Change
---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
AA 100 $9.22 -22.98 AA 100 $9.22 -22.98
IBM 50 $106.28 15.18 IBM 50 $106.28 15.18
CAT 150 $35.46 -47.98 CAT 150 $35.46 -47.98
MSFT 200 $20.89 -30.34 MSFT 200 $20.89 -30.34
GE 95 $13.48 -26.89 GE 95 $13.48 -26.89
MSFT 50 $20.89 -44.21 MSFT 50 $20.89 -44.21
IBM 100 $106.28 35.84 IBM 100 $106.28 35.84
``` ```
[Contents](../Contents) \| [Previous (2.2 Containers)](02_Containers) \| [Next (2.4 Sequences)](04_Sequences) [Contents](../Contents) \| [Previous (2.2 Containers)](02_Containers) \| [Next (2.4 Sequences)](04_Sequences)

View File

@@ -1,14 +1,16 @@
[Contents](../Contents) \| [Previous (2.3 Formatting)](03_Formatting) \| [Next (2.5 Collections)](05_Collections)
# 2.4 Sequences # 2.4 Sequences
### Sequence Datatypes ### Sequence Datatypes
Python has three *sequence* datatypes. Python has three *sequence* datatypes.
* String: `'Hello'`. A string is considered a sequence of characters. * String: `'Hello'`. A string is a sequence of characters.
* List: `[1, 4, 5]`. * List: `[1, 4, 5]`.
* Tuple: `('GOOG', 100, 490.1)`. * Tuple: `('GOOG', 100, 490.1)`.
All sequences are ordered and have length. All sequences are ordered, indexed by integers, and have a length.
```python ```python
a = 'Hello' # String a = 'Hello' # String
@@ -28,7 +30,7 @@ len(c) # 3
Sequences can be replicated: `s * n`. Sequences can be replicated: `s * n`.
```pycon ```python
>>> a = 'Hello' >>> a = 'Hello'
>>> a * 3 >>> a * 3
'HelloHelloHello' 'HelloHelloHello'
@@ -40,7 +42,7 @@ Sequences can be replicated: `s * n`.
Sequences of the same type can be concatenated: `s + t`. Sequences of the same type can be concatenated: `s + t`.
```pycon ```python
>>> a = (1, 2, 3) >>> a = (1, 2, 3)
>>> b = (4, 5) >>> b = (4, 5)
>>> a + b >>> a + b
@@ -56,7 +58,7 @@ TypeError: can only concatenate tuple (not "list") to tuple
### Slicing ### Slicing
Slicing means to take a subsequence from a sequence. Slicing means to take a subsequence from a sequence.
The syntax used is `s[start:end]`. Where `start` and `end` are the indexes of the subsequence you want. The syntax is `s[start:end]`. Where `start` and `end` are the indexes of the subsequence you want.
```python ```python
a = [0,1,2,3,4,5,6,7,8] a = [0,1,2,3,4,5,6,7,8]
@@ -67,12 +69,12 @@ a[:3] # [0,1,2]
``` ```
* Indices `start` and `end` must be integers. * Indices `start` and `end` must be integers.
* Slices do *not* include the end value. * Slices do *not* include the end value. It is like a half-open interval from math.
* If indices are omitted, they default to the beginning or end of the list. * If indices are omitted, they default to the beginning or end of the list.
### Slice re-assignment ### Slice re-assignment
Slices can also be reassigned and deleted. On lists, slices can be reassigned and deleted.
```python ```python
# Reassignment # Reassignment
@@ -90,9 +92,9 @@ del a[2:4] # [0,1,4,5,6,7,8]
### Sequence Reductions ### Sequence Reductions
There are some functions to reduce a sequence to a single value. There are some common functions to reduce a sequence to a single value.
```pycon ```python
>>> s = [1, 2, 3, 4] >>> s = [1, 2, 3, 4]
>>> sum(s) >>> sum(s)
10 10
@@ -106,9 +108,9 @@ There are some functions to reduce a sequence to a single value.
### Iteration over a sequence ### Iteration over a sequence
The for-loop iterates over the elements in the sequence. The for-loop iterates over the elements in a sequence.
```pycon ```python
>>> s = [1, 4, 9, 16] >>> s = [1, 4, 9, 16]
>>> for i in s: >>> for i in s:
... print(i) ... print(i)
@@ -121,7 +123,7 @@ The for-loop iterates over the elements in the sequence.
``` ```
On each iteration of the loop, you get a new item to work with. On each iteration of the loop, you get a new item to work with.
This new value is placed into an iteration variable. In this example, the This new value is placed into the iteration variable. In this example, the
iteration variable is `x`: iteration variable is `x`:
```python ```python
@@ -129,12 +131,12 @@ for x in s: # `x` is an iteration variable
...statements ...statements
``` ```
In each iteration, it overwrites the previous value (if any). On each iteration, the previous value of the iteration variable is overwritten (if any).
After the loop finishes, the variable retains the last value. After the loop finishes, the variable retains the last value.
### `break` statement ### break statement
You can use the `break` statement to break out of a loop before it finishes iterating all of the elements. You can use the `break` statement to break out of a loop early.
```python ```python
for name in namelist: for name in namelist:
@@ -145,14 +147,14 @@ for name in namelist:
statements statements
``` ```
When the `break` statement is executed, it will exit the loop and move When the `break` statement executes, it exits the loop and moves
on the next `statements`. The `break` statement only applies to the on the next `statements`. The `break` statement only applies to the
inner-most loop. If this loop is within another loop, it will not inner-most loop. If this loop is within another loop, it will not
break the outer loop. break the outer loop.
### `continue` statement ### continue statement
To skip one element and move to the next one you use the `continue` statement. To skip one element and move to the next one, use the `continue` statement.
```python ```python
for line in lines: for line in lines:
@@ -188,10 +190,11 @@ for k in range(10,50,2):
* The ending value is never included. It mirrors the behavior of slices. * The ending value is never included. It mirrors the behavior of slices.
* `start` is optional. Default `0`. * `start` is optional. Default `0`.
* `step` is optional. Default `1`. * `step` is optional. Default `1`.
* `range()` computes values as needed. It does not actually store a large range of numbers.
### `enumerate()` function ### enumerate() function
The `enumerate` function provides a loop with an extra counter value. The `enumerate` function adds an extra counter value to iteration.
```python ```python
names = ['Elwood', 'Jake', 'Curtis'] names = ['Elwood', 'Jake', 'Curtis']
@@ -201,7 +204,7 @@ for i, name in enumerate(names):
# i = 2, name = 'Curtis' # i = 2, name = 'Curtis'
``` ```
How to use enumerate: `enumerate(sequence [, start = 0])`. `start` is optional. The general form is `enumerate(sequence [, start = 0])`. `start` is optional.
A good example of using `enumerate()` is tracking line numbers while reading a file: A good example of using `enumerate()` is tracking line numbers while reading a file:
```python ```python
@@ -223,7 +226,7 @@ Using `enumerate` is less typing and runs slightly faster.
### For and tuples ### For and tuples
You can loop with multiple iteration variables. You can iterate with multiple iteration variables.
```python ```python
points = [ points = [
@@ -236,11 +239,12 @@ for x, y in points:
# ... # ...
``` ```
When using multiple variables, each tuple will be *unpacked* into a set of iteration variables. 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.
### `zip()` function ### zip() function
The `zip` function takes sequences and makes an iterator that combines them. The `zip` function takes multiple sequences and makes an iterator that combines them.
```python ```python
columns = ['name', 'shares', 'price'] columns = ['name', 'shares', 'price']
@@ -268,7 +272,7 @@ d = dict(zip(columns, values))
Try some basic counting examples: Try some basic counting examples:
```pycon ```python
>>> for n in range(10): # Count 0 ... 9 >>> for n in range(10): # Count 0 ... 9
print(n, end=' ') print(n, end=' ')
@@ -288,7 +292,7 @@ Try some basic counting examples:
Interactively experiment with some of the sequence reduction operations. Interactively experiment with some of the sequence reduction operations.
```pycon ```python
>>> data = [4, 9, 1, 25, 16, 100, 49] >>> data = [4, 9, 1, 25, 16, 100, 49]
>>> min(data) >>> min(data)
1 1
@@ -301,7 +305,7 @@ Interactively experiment with some of the sequence reduction operations.
Try looping over the data. Try looping over the data.
```pycon ```python
>>> for x in data: >>> for x in data:
print(x) print(x)
@@ -322,7 +326,7 @@ Sometimes the `for` statement, `len()`, and `range()` get used by
novices in some kind of horrible code fragment that looks like it novices in some kind of horrible code fragment that looks like it
emerged from the depths of a rusty C program. emerged from the depths of a rusty C program.
```pycon ```python
>>> for n in range(len(data)): >>> for n in range(len(data)):
print(data[n]) print(data[n])
@@ -338,10 +342,10 @@ its inefficient with memory and it runs a lot slower. Just use a
normal `for` loop if you want to iterate over data. Use `enumerate()` normal `for` loop if you want to iterate over data. Use `enumerate()`
if you happen to need the index for some reason. if you happen to need the index for some reason.
### Exercise 2.15: A practical `enumerate()` example ### Exercise 2.15: A practical enumerate() example
Recall that the file `Data/missing.csv` contains data for a stock Recall that the file `Data/missing.csv` contains data for a stock
portfolio, but has some rows with missing data. Using `enumerate()` portfolio, but has some rows with missing data. Using `enumerate()`,
modify your `pcost.py` program so that it prints a line number with modify your `pcost.py` program so that it prints a line number with
the warning message when it encounters bad input. the warning message when it encounters bad input.
@@ -352,7 +356,7 @@ Row 7: Couldn't convert: ['IBM', '', '70.44']
>>> >>>
``` ```
To do this, youll need to change just a few parts of your code. To do this, youll need to change a few parts of your code.
```python ```python
... ...
@@ -363,12 +367,12 @@ for rowno, row in enumerate(rows, start=1):
print(f'Row {rowno}: Bad row: {row}') print(f'Row {rowno}: Bad row: {row}')
``` ```
### Exercise 2.16: Using the `zip()` function ### Exercise 2.16: Using the zip() function
In the file `portfolio.csv`, the first line contains column In the file `Data/portfolio.csv`, the first line contains column
headers. In all previous code, weve been discarding them. headers. In all previous code, weve been discarding them.
```pycon ```python
>>> f = open('Data/portfolio.csv') >>> f = open('Data/portfolio.csv')
>>> rows = csv.reader(f) >>> rows = csv.reader(f)
>>> headers = next(rows) >>> headers = next(rows)
@@ -381,7 +385,7 @@ However, what if you could use the headers for something useful? This
is where the `zip()` function enters the picture. First try this to is where the `zip()` function enters the picture. First try this to
pair the file headers with a row of data: pair the file headers with a row of data:
```pycon ```python
>>> row = next(rows) >>> row = next(rows)
>>> row >>> row
['AA', '100', '32.20'] ['AA', '100', '32.20']
@@ -395,10 +399,10 @@ Weve used `list()` here to turn the result into a list so that you
can see it. Normally, `zip()` creates an iterator that must be can see it. Normally, `zip()` creates an iterator that must be
consumed by a for-loop. consumed by a for-loop.
This pairing is just an intermediate step to building a This pairing is an intermediate step to building a
dictionary. Now try this: dictionary. Now try this:
```pycon ```python
>>> record = dict(zip(headers, row)) >>> record = dict(zip(headers, row))
>>> record >>> record
{'price': '32.20', 'name': 'AA', 'shares': '100'} {'price': '32.20', 'name': 'AA', 'shares': '100'}
@@ -462,14 +466,14 @@ out of it. As long as the file has the required columns, the code will work.
Modify the `report.py` program you wrote in Section 2.3 that it uses Modify the `report.py` program you wrote in Section 2.3 that it uses
the same technique to pick out column headers. the same technique to pick out column headers.
Try running the `report.py` program on the `Data/portfoliodate.csv` file and see that it Try running the `report.py` program on the `Data/portfoliodate.csv`
produces the same answer as before. file and see that it produces the same answer as before.
### Exercise 2.17: Inverting a dictionary ### Exercise 2.17: Inverting a dictionary
A dictionary maps keys to values. For example, a dictionary of stock prices. A dictionary maps keys to values. For example, a dictionary of stock prices.
```pycon ```python
>>> prices = { >>> prices = {
'GOOG' : 490.1, 'GOOG' : 490.1,
'AA' : 23.45, 'AA' : 23.45,
@@ -481,7 +485,7 @@ A dictionary maps keys to values. For example, a dictionary of stock prices.
If you use the `items()` method, you can get `(key,value)` pairs: If you use the `items()` method, you can get `(key,value)` pairs:
```pycon ```python
>>> prices.items() >>> prices.items()
dict_items([('GOOG', 490.1), ('AA', 23.45), ('IBM', 91.1), ('MSFT', 34.23)]) dict_items([('GOOG', 490.1), ('AA', 23.45), ('IBM', 91.1), ('MSFT', 34.23)])
>>> >>>
@@ -490,7 +494,7 @@ dict_items([('GOOG', 490.1), ('AA', 23.45), ('IBM', 91.1), ('MSFT', 34.23)])
However, what if you wanted to get a list of `(value, key)` pairs instead? However, what if you wanted to get a list of `(value, key)` pairs instead?
*Hint: use `zip()`.* *Hint: use `zip()`.*
```pycon ```python
>>> pricelist = list(zip(prices.values(),prices.keys())) >>> pricelist = list(zip(prices.values(),prices.keys()))
>>> pricelist >>> pricelist
[(490.1, 'GOOG'), (23.45, 'AA'), (91.1, 'IBM'), (34.23, 'MSFT')] [(490.1, 'GOOG'), (23.45, 'AA'), (91.1, 'IBM'), (34.23, 'MSFT')]
@@ -500,7 +504,7 @@ However, what if you wanted to get a list of `(value, key)` pairs instead?
Why would you do this? For one, it allows you to perform certain kinds Why would you do this? For one, it allows you to perform certain kinds
of data processing on the dictionary data. of data processing on the dictionary data.
```pycon ```python
>>> min(pricelist) >>> min(pricelist)
(23.45, 'AA') (23.45, 'AA')
>>> max(pricelist) >>> max(pricelist)
@@ -523,7 +527,7 @@ values.
Note that `zip()` is not limited to pairs. For example, you can use it Note that `zip()` is not limited to pairs. For example, you can use it
with any number of input lists: with any number of input lists:
```pycon ```python
>>> a = [1, 2, 3, 4] >>> a = [1, 2, 3, 4]
>>> b = ['w', 'x', 'y', 'z'] >>> b = ['w', 'x', 'y', 'z']
>>> c = [0.2, 0.4, 0.6, 0.8] >>> c = [0.2, 0.4, 0.6, 0.8]
@@ -534,7 +538,7 @@ with any number of input lists:
Also, be aware that `zip()` stops once the shortest input sequence is exhausted. Also, be aware that `zip()` stops once the shortest input sequence is exhausted.
```pycon ```python
>>> a = [1, 2, 3, 4, 5, 6] >>> a = [1, 2, 3, 4, 5, 6]
>>> b = ['x', 'y', 'z'] >>> b = ['x', 'y', 'z']
>>> list(zip(a,b)) >>> list(zip(a,b))

View File

@@ -1,3 +1,5 @@
[Contents](../Contents) \| [Previous (2.4 Sequences)](04_Sequences) \| [Next (2.6 List Comprehensions)](06_List_comprehension)
# 2.5 collections module # 2.5 collections module
The `collections` module provides a number of useful objects for data handling. The `collections` module provides a number of useful objects for data handling.
@@ -20,6 +22,8 @@ portfolio = [
There are two `IBM` entries and two `GOOG` entries in this list. The shares need to be combined together somehow. There are two `IBM` entries and two `GOOG` entries in this list. The shares need to be combined together somehow.
### Counters
Solution: Use a `Counter`. Solution: Use a `Counter`.
```python ```python
@@ -94,7 +98,7 @@ bash % python3 -i report.py
Suppose you wanted to tabulate the total number of shares of each stock. Suppose you wanted to tabulate the total number of shares of each stock.
This is easy using `Counter` objects. Try it: This is easy using `Counter` objects. Try it:
```pycon ```python
>>> portfolio = read_portfolio('Data/portfolio.csv') >>> portfolio = read_portfolio('Data/portfolio.csv')
>>> from collections import Counter >>> from collections import Counter
>>> holdings = Counter() >>> holdings = Counter()
@@ -129,7 +133,7 @@ If you want to rank the values, do this:
Lets grab another portfolio of stocks and make a new Counter: Lets grab another portfolio of stocks and make a new Counter:
```pycon ```python
>>> portfolio2 = read_portfolio('Data/portfolio2.csv') >>> portfolio2 = read_portfolio('Data/portfolio2.csv')
>>> holdings2 = Counter() >>> holdings2 = Counter()
>>> for s in portfolio2: >>> for s in portfolio2:
@@ -142,7 +146,7 @@ Counter({'HPQ': 250, 'GE': 125, 'AA': 50, 'MSFT': 25})
Finally, lets combine all of the holdings doing one simple operation: Finally, lets combine all of the holdings doing one simple operation:
```pycon ```python
>>> holdings >>> holdings
Counter({'MSFT': 250, 'IBM': 150, 'CAT': 150, 'AA': 100, 'GE': 95}) Counter({'MSFT': 250, 'IBM': 150, 'CAT': 150, 'AA': 100, 'GE': 95})
>>> holdings2 >>> holdings2
@@ -157,4 +161,11 @@ This is only a small taste of what counters provide. However, if you
ever find yourself needing to tabulate values, you should consider ever find yourself needing to tabulate values, you should consider
using one. using one.
### Commentary: collections module
The `collections` module is one of the most useful library modules
in all of Python. In fact, we could do an extended tutorial on just
that. However, doing so now would also be a distraction. For now,
put `collections` on your list of bedtime reading for later.
[Contents](../Contents) \| [Previous (2.4 Sequences)](04_Sequences) \| [Next (2.6 List Comprehensions)](06_List_comprehension) [Contents](../Contents) \| [Previous (2.4 Sequences)](04_Sequences) \| [Next (2.6 List Comprehensions)](06_List_comprehension)

View File

@@ -1,13 +1,16 @@
[Contents](../Contents) \| [Previous (2.5 Collections)](05_Collections) \| [Next (2.7 Object Model)](07_Objects)
# 2.6 List Comprehensions # 2.6 List Comprehensions
A common task is processing items in a list. This section introduces list comprehensions, A common task is processing items in a list. This section introduces list comprehensions,
a useful tool for doing just that. a powerful tool for doing just that.
### Creating new lists ### Creating new lists
A list comprehension creates a new list by applying an operation to each element of a sequence. A list comprehension creates a new list by applying an operation to
each element of a sequence.
```pycon ```python
>>> a = [1, 2, 3, 4, 5] >>> a = [1, 2, 3, 4, 5]
>>> b = [2*x for x in a ] >>> b = [2*x for x in a ]
>>> b >>> b
@@ -17,7 +20,7 @@ A list comprehension creates a new list by applying an operation to each element
Another example: Another example:
```pycon ```python
>>> names = ['Elwood', 'Jake'] >>> names = ['Elwood', 'Jake']
>>> a = [name.lower() for name in names] >>> a = [name.lower() for name in names]
>>> a >>> a
@@ -31,7 +34,7 @@ The general syntax is: `[ <expression> for <variable_name> in <sequence> ]`.
You can also filter during the list comprehension. You can also filter during the list comprehension.
```pycon ```python
>>> a = [1, -5, 4, 2, -2, 10] >>> a = [1, -5, 4, 2, -2, 10]
>>> b = [2*x for x in a if x > 0 ] >>> b = [2*x for x in a if x > 0 ]
>>> b >>> b
@@ -42,7 +45,7 @@ You can also filter during the list comprehension.
### Use cases ### Use cases
List comprehensions are hugely useful. For example, you can collect values of a specific List comprehensions are hugely useful. For example, you can collect values of a specific
record field: dictionary fields:
```python ```python
stocknames = [s['name'] for s in stocks] stocknames = [s['name'] for s in stocks]
@@ -91,20 +94,22 @@ it's fine to view it as a cool list shortcut.
## Exercises ## Exercises
Start by running your `report.py` program so that you have the portfolio of stocks loaded in the interactive mode. Start by running your `report.py` program so that you have the
portfolio of stocks loaded in the interactive mode.
```bash ```bash
bash % python3 -i report.py bash % python3 -i report.py
``` ```
Now, at the Python interactive prompt, type statements to perform the operations described below. Now, at the Python interactive prompt, type statements to perform the
These operations perform various kinds of data reductions, transforms, and queries on the portfolio data. operations described below. These operations perform various kinds of
data reductions, transforms, and queries on the portfolio data.
### Exercise 2.19: List comprehensions ### Exercise 2.19: List comprehensions
Try a few simple list comprehensions just to become familiar with the syntax. Try a few simple list comprehensions just to become familiar with the syntax.
```pycon ```python
>>> nums = [1,2,3,4] >>> nums = [1,2,3,4]
>>> squares = [ x * x for x in nums ] >>> squares = [ x * x for x in nums ]
>>> squares >>> squares
@@ -115,31 +120,35 @@ Try a few simple list comprehensions just to become familiar with the syntax.
>>> >>>
``` ```
Notice how the list comprehensions are creating a new list with the data suitably transformed or filtered. Notice how the list comprehensions are creating a new list with the
data suitably transformed or filtered.
### Exercise 2.20: Sequence Reductions ### Exercise 2.20: Sequence Reductions
Compute the total cost of the portfolio using a single Python statement. Compute the total cost of the portfolio using a single Python statement.
```pycon ```python
>>> portfolio = read_portfolio('Data/portfolio.csv')
>>> cost = sum([ s['shares'] * s['price'] for s in portfolio ]) >>> cost = sum([ s['shares'] * s['price'] for s in portfolio ])
>>> cost >>> cost
44671.15 44671.15
>>> >>>
``` ```
After you have done that, show how you can compute the current value of the portfolio using a single statement. After you have done that, show how you can compute the current value
of the portfolio using a single statement.
```pycon ```python
>>> value = sum([ s['shares'] * prices[s['name']] for s in portfolio ]) >>> value = sum([ s['shares'] * prices[s['name']] for s in portfolio ])
>>> value >>> value
28686.1 28686.1
>>> >>>
``` ```
Both of the above operations are an example of a map-reduction. The list comprehension is mapping an operation across the list. Both of the above operations are an example of a map-reduction. The
list comprehension is mapping an operation across the list.
```pycon ```python
>>> [ s['shares'] * s['price'] for s in portfolio ] >>> [ s['shares'] * s['price'] for s in portfolio ]
[3220.0000000000005, 4555.0, 12516.0, 10246.0, 3835.1499999999996, 3254.9999999999995, 7044.0] [3220.0000000000005, 4555.0, 12516.0, 10246.0, 3835.1499999999996, 3254.9999999999995, 7044.0]
>>> >>>
@@ -161,7 +170,7 @@ Try the following examples of various data queries.
First, a list of all portfolio holdings with more than 100 shares. First, a list of all portfolio holdings with more than 100 shares.
```pycon ```python
>>> more100 = [ s for s in portfolio if s['shares'] > 100 ] >>> more100 = [ s for s in portfolio if s['shares'] > 100 ]
>>> more100 >>> more100
[{'price': 83.44, 'name': 'CAT', 'shares': 150}, {'price': 51.23, 'name': 'MSFT', 'shares': 200}] [{'price': 83.44, 'name': 'CAT', 'shares': 150}, {'price': 51.23, 'name': 'MSFT', 'shares': 200}]
@@ -170,7 +179,7 @@ First, a list of all portfolio holdings with more than 100 shares.
All portfolio holdings for MSFT and IBM stocks. All portfolio holdings for MSFT and IBM stocks.
```pycon ```python
>>> msftibm = [ s for s in portfolio if s['name'] in {'MSFT','IBM'} ] >>> msftibm = [ s for s in portfolio if s['name'] in {'MSFT','IBM'} ]
>>> msftibm >>> msftibm
[{'price': 91.1, 'name': 'IBM', 'shares': 50}, {'price': 51.23, 'name': 'MSFT', 'shares': 200}, [{'price': 91.1, 'name': 'IBM', 'shares': 50}, {'price': 51.23, 'name': 'MSFT', 'shares': 200},
@@ -180,7 +189,7 @@ All portfolio holdings for MSFT and IBM stocks.
A list of all portfolio holdings that cost more than $10000. A list of all portfolio holdings that cost more than $10000.
```pycon ```python
>>> cost10k = [ s for s in portfolio if s['shares'] * s['price'] > 10000 ] >>> cost10k = [ s for s in portfolio if s['shares'] * s['price'] > 10000 ]
>>> cost10k >>> cost10k
[{'price': 83.44, 'name': 'CAT', 'shares': 150}, {'price': 51.23, 'name': 'MSFT', 'shares': 200}] [{'price': 83.44, 'name': 'CAT', 'shares': 150}, {'price': 51.23, 'name': 'MSFT', 'shares': 200}]
@@ -191,7 +200,7 @@ A list of all portfolio holdings that cost more than $10000.
Show how you could build a list of tuples `(name, shares)` where `name` and `shares` are taken from `portfolio`. Show how you could build a list of tuples `(name, shares)` where `name` and `shares` are taken from `portfolio`.
```pycon ```python
>>> name_shares =[ (s['name'], s['shares']) for s in portfolio ] >>> name_shares =[ (s['name'], s['shares']) for s in portfolio ]
>>> name_shares >>> name_shares
[('AA', 100), ('IBM', 50), ('CAT', 150), ('MSFT', 200), ('GE', 95), ('MSFT', 50), ('IBM', 100)] [('AA', 100), ('IBM', 50), ('CAT', 150), ('MSFT', 200), ('GE', 95), ('MSFT', 50), ('IBM', 100)]
@@ -201,9 +210,9 @@ 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 the square brackets (`[`,`]`) to curly braces (`{`, `}`), you get something known as a set comprehension.
This gives you unique or distinct values. This gives you unique or distinct values.
For example, this determines the set of stock names that appear in `portfolio`: For example, this determines the set of unique stock names that appear in `portfolio`:
```pycon ```python
>>> names = { s['name'] for s in portfolio } >>> names = { s['name'] for s in portfolio }
>>> names >>> names
{ 'AA', 'GE', 'IBM', 'MSFT', 'CAT'] } { 'AA', 'GE', 'IBM', 'MSFT', 'CAT'] }
@@ -213,7 +222,7 @@ For example, this determines the set of stock names that appear in `portfolio`:
If you specify `key:value` pairs, you can build a dictionary. If you specify `key:value` pairs, you can build a dictionary.
For example, make a dictionary that maps the name of a stock to the total number of shares held. For example, make a dictionary that maps the name of a stock to the total number of shares held.
```pycon ```python
>>> holdings = { name: 0 for name in names } >>> holdings = { name: 0 for name in names }
>>> holdings >>> holdings
{'AA': 0, 'GE': 0, 'IBM': 0, 'MSFT': 0, 'CAT': 0} {'AA': 0, 'GE': 0, 'IBM': 0, 'MSFT': 0, 'CAT': 0}
@@ -222,7 +231,7 @@ For example, make a dictionary that maps the name of a stock to the total number
This latter feature is known as a **dictionary comprehension**. Lets tabulate: This latter feature is known as a **dictionary comprehension**. Lets tabulate:
```pycon ```python
>>> for s in portfolio: >>> for s in portfolio:
holdings[s['name']] += s['shares'] holdings[s['name']] += s['shares']
@@ -231,9 +240,10 @@ This latter feature is known as a **dictionary comprehension**. Lets tabulate
>>> >>>
``` ```
Try this example that filters the `prices` dictionary down to only those names that appear in the portfolio: Try this example that filters the `prices` dictionary down to only
those names that appear in the portfolio:
```pycon ```python
>>> portfolio_prices = { name: prices[name] for name in names } >>> portfolio_prices = { name: prices[name] for name in names }
>>> portfolio_prices >>> portfolio_prices
{'AA': 9.22, 'GE': 13.48, 'IBM': 106.28, 'MSFT': 20.89, 'CAT': 35.46} {'AA': 9.22, 'GE': 13.48, 'IBM': 106.28, 'MSFT': 20.89, 'CAT': 35.46}
@@ -242,12 +252,14 @@ Try this example that filters the `prices` dictionary down to only those names t
### Exercise 2.23: Extracting Data From CSV Files ### Exercise 2.23: Extracting Data From CSV Files
Knowing how to use various combinations of list, set, and dictionary comprehensions can be useful in various forms of data processing. Knowing how to use various combinations of list, set, and dictionary
Heres an example that shows how to extract selected columns from a CSV file. comprehensions can be useful in various forms of data processing.
Heres an example that shows how to extract selected columns from a
CSV file.
First, read a row of header information from a CSV file: First, read a row of header information from a CSV file:
```pycon ```python
>>> import csv >>> import csv
>>> f = open('Data/portfoliodate.csv') >>> f = open('Data/portfoliodate.csv')
>>> rows = csv.reader(f) >>> rows = csv.reader(f)
@@ -259,23 +271,24 @@ First, read a row of header information from a CSV file:
Next, define a variable that lists the columns that you actually care about: Next, define a variable that lists the columns that you actually care about:
```pycon ```python
>>> select = ['name', 'shares', 'price'] >>> select = ['name', 'shares', 'price']
>>> >>>
``` ```
Now, locate the indices of the above columns in the source CSV file: Now, locate the indices of the above columns in the source CSV file:
```pycon ```python
>>> indices = [ headers.index(colname) for colname in select ] >>> indices = [ headers.index(colname) for colname in select ]
>>> indices >>> indices
[0, 3, 4] [0, 3, 4]
>>> >>>
``` ```
Finally, read a row of data and turn it into a dictionary using a dictionary comprehension: Finally, read a row of data and turn it into a dictionary using a
dictionary comprehension:
```pycon ```python
>>> row = next(rows) >>> row = next(rows)
>>> record = { colname: row[index] for colname, index in zip(select, indices) } # dict-comprehension >>> record = { colname: row[index] for colname, index in zip(select, indices) } # dict-comprehension
>>> record >>> record
@@ -286,7 +299,7 @@ Finally, read a row of data and turn it into a dictionary using a dictionary com
If youre feeling comfortable with what just happened, read the rest If youre feeling comfortable with what just happened, read the rest
of the file: of the file:
```pycon ```python
>>> portfolio = [ { colname: row[index] for colname, index in zip(select, indices) } for row in rows ] >>> portfolio = [ { colname: row[index] for colname, index in zip(select, indices) } for row in rows ]
>>> portfolio >>> portfolio
[{'price': '91.10', 'name': 'IBM', 'shares': '50'}, {'price': '83.44', 'name': 'CAT', 'shares': '150'}, [{'price': '91.10', 'name': 'IBM', 'shares': '50'}, {'price': '83.44', 'name': 'CAT', 'shares': '150'},

View File

@@ -1,3 +1,5 @@
[Contents](../Contents) \| [Previous (2.6 List Comprehensions)](06_List_comprehension) \| [Next (3 Program Organization)](../03_Program_organization/00_Overview)
# 2.7 Objects # 2.7 Objects
This section introduces more details about Python's internal object model and This section introduces more details about Python's internal object model and
@@ -31,9 +33,11 @@ A picture of the underlying memory operations. In this example, there
is only one list object `[1,2,3]`, but there are four different is only one list object `[1,2,3]`, but there are four different
references to it. references to it.
![References](references.png)
This means that modifying a value affects *all* references. This means that modifying a value affects *all* references.
```pycon ```python
>>> a.append(999) >>> a.append(999)
>>> a >>> a
[1,2,3,999] [1,2,3,999]
@@ -44,14 +48,15 @@ This means that modifying a value affects *all* references.
>>> >>>
``` ```
Notice how a change in the original list shows up everywhere else (yikes!). Notice how a change in the original list shows up everywhere else
This is because no copies were ever made. Everything is pointing to the same thing. (yikes!). This is because no copies were ever made. Everything is
pointing to the same thing.
### Reassigning values ### Reassigning values
Reassigning a value *never* overwrites the memory used by the previous value. Reassigning a value *never* overwrites the memory used by the previous value.
```pycon ```python
a = [1,2,3] a = [1,2,3]
b = a b = a
a = [4,5,6] a = [4,5,6]
@@ -69,13 +74,14 @@ foot at some point. Typical scenario. You modify some data thinking
that it's your own private copy and it accidentally corrupts some data that it's your own private copy and it accidentally corrupts some data
in some other part of the program. in some other part of the program.
*Comment: This is one of the reasons why the primitive datatypes (int, float, string) are immutable (read-only).* *Comment: This is one of the reasons why the primitive datatypes (int,
float, string) are immutable (read-only).*
### Identity and References ### Identity and References
Use ths `is` operator to check if two values are exactly the same object. Use the `is` operator to check if two values are exactly the same object.
```pycon ```python
>>> a = [1,2,3] >>> a = [1,2,3]
>>> b = a >>> b = a
>>> a is b >>> a is b
@@ -86,7 +92,7 @@ True
`is` compares the object identity (an integer). The identity can be `is` compares the object identity (an integer). The identity can be
obtained using `id()`. obtained using `id()`.
```pycon ```python
>>> id(a) >>> id(a)
3588944 3588944
>>> id(b) >>> id(b)
@@ -94,11 +100,27 @@ obtained using `id()`.
>>> >>>
``` ```
Note: It is almost always better to use `==` for checking objects. The behavior
of `is` is often unexpected:
```python
>>> a = [1,2,3]
>>> b = a
>>> c = [1,2,3]
>>> a is b
True
>>> a is c
False
>>> a == c
True
>>>
```
### Shallow copies ### Shallow copies
Lists and dicts have methods for copying. Lists and dicts have methods for copying.
```pycon ```python
>>> a = [2,3,[100,101],4] >>> a = [2,3,[100,101],4]
>>> b = list(a) # Make a copy >>> b = list(a) # Make a copy
>>> a is b >>> a is b
@@ -118,14 +140,16 @@ True
``` ```
For example, the inner list `[100, 101]` is being shared. For example, the inner list `[100, 101]` is being shared.
This is knows as a shallow copy. This is known as a shallow copy. Here is a picture.
![Shallow copy](shallow.png)
### Deep copies ### Deep copies
Sometimes you need to make a copy of an object and all the objects contained withn it. Sometimes you need to make a copy of an object and all the objects contained withn it.
You can use the `copy` module for this: You can use the `copy` module for this:
```pycon ```python
>>> a = [2,3,[100,101],4] >>> a = [2,3,[100,101],4]
>>> import copy >>> import copy
>>> b = copy.deepcopy(a) >>> b = copy.deepcopy(a)
@@ -142,7 +166,7 @@ False
Variable names do not have a *type*. It's only a name. Variable names do not have a *type*. It's only a name.
However, values *do* have an underlying type. However, values *do* have an underlying type.
```pycon ```python
>>> a = 42 >>> a = 42
>>> b = 'Hello World' >>> b = 'Hello World'
>>> type(a) >>> type(a)
@@ -151,7 +175,7 @@ However, values *do* have an underlying type.
<type 'str'> <type 'str'>
``` ```
`type()` will tell you what it is. The type name is usually a function `type()` will tell you what it is. The type name is usually used as a function
that creates or converts a value to that type. that creates or converts a value to that type.
### Type Checking ### Type Checking
@@ -159,18 +183,21 @@ that creates or converts a value to that type.
How to tell if an object is a specific type. How to tell if an object is a specific type.
```python ```python
if isinstance(a,list): if isinstance(a, list):
print('a is a list') print('a is a list')
``` ```
Checking for one of many types. Checking for one of many possible types.
```python ```python
if isinstance(a, (list,tuple)): if isinstance(a, (list,tuple)):
print('a is a list or tuple') print('a is a list or tuple')
``` ```
*Caution: Don't go overboard with type checking. It can lead to excessive complexity.* *Caution: Don't go overboard with type checking. It can lead to
excessive code complexity. Usually you'd only do it if doing
so would prevent common mistakes made by others using your code.
*
### Everything is an object ### Everything is an object
@@ -182,7 +209,7 @@ is said that all objects are "first-class".
A simple example: A simple example:
```pycon ```python
>>> import math >>> import math
>>> items = [abs, math, ValueError ] >>> items = [abs, math, ValueError ]
>>> items >>> items
@@ -201,8 +228,9 @@ Failed!
>>> >>>
``` ```
Here, `items` is a list containing a function, a module and an exception. Here, `items` is a list containing a function, a module and an
You can use the items in the list in place of the original names: exception. You can directly use the items in the list in place of the
original names:
```python ```python
items[0](-45) # abs items[0](-45) # abs
@@ -210,6 +238,8 @@ items[1].sqrt(2) # math
except items[2]: # ValueError except items[2]: # ValueError
``` ```
With great power come responsibility. Just because you can do that doesn't me you should.
## Exercises ## Exercises
In this set of exercises, we look at some of the power that comes from first-class In this set of exercises, we look at some of the power that comes from first-class
@@ -242,7 +272,7 @@ using some list basic operations.
Make a Python list that contains the names of the conversion functions Make a Python list that contains the names of the conversion functions
you would use to convert each column into the appropriate type: you would use to convert each column into the appropriate type:
```pycon ```python
>>> types = [str, int, float] >>> types = [str, int, float]
>>> >>>
``` ```
@@ -254,7 +284,7 @@ a value `x` into a given type (e.g., `str(x)`, `int(x)`, `float(x)`).
Now, read a row of data from the above file: Now, read a row of data from the above file:
```pycon ```python
>>> import csv >>> import csv
>>> f = open('Data/portfolio.csv') >>> f = open('Data/portfolio.csv')
>>> rows = csv.reader(f) >>> rows = csv.reader(f)
@@ -265,9 +295,10 @@ Now, read a row of data from the above file:
>>> >>>
``` ```
As noted, this row isnt enough to do calculations because the types are wrong. For example: As noted, this row isnt enough to do calculations because the types
are wrong. For example:
```pycon ```python
>>> row[1] * row[2] >>> row[1] * row[2]
Traceback (most recent call last): Traceback (most recent call last):
File "<stdin>", line 1, in <module> File "<stdin>", line 1, in <module>
@@ -275,9 +306,10 @@ TypeError: can't multiply sequence by non-int of type 'str'
>>> >>>
``` ```
However, maybe the data can be paired up with the types you specified in `types`. For example: However, maybe the data can be paired up with the types you specified
in `types`. For example:
```pycon ```python
>>> types[1] >>> types[1]
<type 'int'> <type 'int'>
>>> row[1] >>> row[1]
@@ -287,7 +319,7 @@ However, maybe the data can be paired up with the types you specified in `types`
Try converting one of the values: Try converting one of the values:
```pycon ```python
>>> types[1](row[1]) # Same as int(row[1]) >>> types[1](row[1]) # Same as int(row[1])
100 100
>>> >>>
@@ -295,7 +327,7 @@ Try converting one of the values:
Try converting a different value: Try converting a different value:
```pycon ```python
>>> types[2](row[2]) # Same as float(row[2]) >>> types[2](row[2]) # Same as float(row[2])
32.2 32.2
>>> >>>
@@ -303,7 +335,7 @@ Try converting a different value:
Try the calculation with converted values: Try the calculation with converted values:
```pycon ```python
>>> types[1](row[1])*types[2](row[2]) >>> types[1](row[1])*types[2](row[2])
3220.0000000000005 3220.0000000000005
>>> >>>
@@ -311,7 +343,7 @@ Try the calculation with converted values:
Zip the column types with the fields and look at the result: Zip the column types with the fields and look at the result:
```pycon ```python
>>> r = list(zip(types, row)) >>> r = list(zip(types, row))
>>> r >>> r
[(<type 'str'>, 'AA'), (<type 'int'>, '100'), (<type 'float'>,'32.20')] [(<type 'str'>, 'AA'), (<type 'int'>, '100'), (<type 'float'>,'32.20')]
@@ -321,10 +353,10 @@ Zip the column types with the fields and look at the result:
You will notice that this has paired a type conversion with a You will notice that this has paired a type conversion with a
value. For example, `int` is paired with the value `'100'`. value. For example, `int` is paired with the value `'100'`.
The zipped list is useful if you want to perform conversions on all of the values, one The zipped list is useful if you want to perform conversions on all of
after the other. Try this: the values, one after the other. Try this:
```pycon ```python
>>> converted = [] >>> converted = []
>>> for func, val in zip(types, row): >>> for func, val in zip(types, row):
converted.append(func(val)) converted.append(func(val))
@@ -336,14 +368,15 @@ after the other. Try this:
>>> >>>
``` ```
Make sure you understand whats happening in the above code. Make sure you understand whats happening in the above code. In the
In the loop, the `func` variable is one of the type conversion functions (e.g., loop, the `func` variable is one of the type conversion functions
`str`, `int`, etc.) and the `val` variable is one of the values like (e.g., `str`, `int`, etc.) and the `val` variable is one of the values
`'AA'`, `'100'`. The expression `func(val)` is converting a value (kind of like a type cast). like `'AA'`, `'100'`. The expression `func(val)` is converting a
value (kind of like a type cast).
The above code can be compressed into a single list comprehension. The above code can be compressed into a single list comprehension.
```pycon ```python
>>> converted = [func(val) for func, val in zip(types, row)] >>> converted = [func(val) for func, val in zip(types, row)]
>>> converted >>> converted
['AA', 100, 32.2] ['AA', 100, 32.2]
@@ -352,10 +385,11 @@ The above code can be compressed into a single list comprehension.
### Exercise 2.25: Making dictionaries ### Exercise 2.25: Making dictionaries
Remember how the `dict()` function can easily make a dictionary if you have a sequence of key names and values? Remember how the `dict()` function can easily make a dictionary if you
Lets make a dictionary from the column headers: have a sequence of key names and values? Lets make a dictionary from
the column headers:
```pycon ```python
>>> headers >>> headers
['name', 'shares', 'price'] ['name', 'shares', 'price']
>>> converted >>> converted
@@ -365,9 +399,10 @@ Lets make a dictionary from the column headers:
>>> >>>
``` ```
Of course, if youre up on your list-comprehension fu, you can do the whole conversion in a single shot using a dict-comprehension: Of course, if youre up on your list-comprehension fu, you can do the
whole conversion in a single step using a dict-comprehension:
```pycon ```python
>>> { name: func(val) for name, func, val in zip(headers, types, row) } >>> { name: func(val) for name, func, val in zip(headers, types, row) }
{'price': 32.2, 'name': 'AA', 'shares': 100} {'price': 32.2, 'name': 'AA', 'shares': 100}
>>> >>>
@@ -375,11 +410,13 @@ Of course, if youre up on your list-comprehension fu, you can do the whole co
### Exercise 2.26: The Big Picture ### Exercise 2.26: The Big Picture
Using the techniques in this exercise, you could write statements that easily convert fields from just about any column-oriented datafile into a Python dictionary. Using the techniques in this exercise, you could write statements that
easily convert fields from just about any column-oriented datafile
into a Python dictionary.
Just to illustrate, suppose you read data from a different datafile like this: Just to illustrate, suppose you read data from a different datafile like this:
```pycon ```python
>>> f = open('Data/dowstocks.csv') >>> f = open('Data/dowstocks.csv')
>>> rows = csv.reader(f) >>> rows = csv.reader(f)
>>> headers = next(rows) >>> headers = next(rows)
@@ -393,7 +430,7 @@ Just to illustrate, suppose you read data from a different datafile like this:
Lets convert the fields using a similar trick: Lets convert the fields using a similar trick:
```pycon ```python
>>> types = [str, float, str, str, float, float, float, float, int] >>> types = [str, float, str, str, float, float, float, float, int]
>>> converted = [func(val) for func, val in zip(types, row)] >>> converted = [func(val) for func, val in zip(types, row)]
>>> record = dict(zip(headers, converted)) >>> record = dict(zip(headers, converted))
@@ -408,6 +445,10 @@ Lets convert the fields using a similar trick:
>>> >>>
``` ```
Spend some time to ponder what youve done in this exercise. Well revisit these ideas a little later. Bonus: How would you modify this example to additionally parse the
`date` entry into a tuple such as `(6, 11, 2007)`?
Spend some time to ponder what youve done in this exercise. Well
revisit these ideas a little later.
[Contents](../Contents) \| [Previous (2.6 List Comprehensions)](06_List_comprehension) \| [Next (3 Program Organization)](../03_Program_organization/00_Overview) [Contents](../Contents) \| [Previous (2.6 List Comprehensions)](06_List_comprehension) \| [Next (3 Program Organization)](../03_Program_organization/00_Overview)

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB