Added sections 2-4
This commit is contained in:
275
Notes/03_Program_organization/01_Script.md
Normal file
275
Notes/03_Program_organization/01_Script.md
Normal file
@@ -0,0 +1,275 @@
|
||||
# 3.1 Python Scripting
|
||||
|
||||
In this part we look more closely at the practice of writing Python
|
||||
scripts.
|
||||
|
||||
### What is a Script?
|
||||
|
||||
A *script* is a program that runs a series of statements and stops.
|
||||
|
||||
```python
|
||||
# program.py
|
||||
|
||||
statement1
|
||||
statement2
|
||||
statement3
|
||||
...
|
||||
```
|
||||
|
||||
We have been writing scripts to this point.
|
||||
|
||||
### A Problem
|
||||
|
||||
If you write a useful script, it will grow in features and
|
||||
functionality. You may want to apply it to other related problems.
|
||||
Over time, it might become a critical application. And if you don't
|
||||
take care, it might turn into a huge tangled mess. So, let's get
|
||||
organized.
|
||||
|
||||
### Defining Things
|
||||
|
||||
You must always define things before they get used later on in a program.
|
||||
|
||||
```python
|
||||
def square(x):
|
||||
return x*x
|
||||
|
||||
a = 42
|
||||
b = a + 2 # Requires that `a` is defined
|
||||
|
||||
z = square(b) # Requires `square` and `b` to be defined
|
||||
```
|
||||
|
||||
**The order is important.**
|
||||
You almost always put the definitions of variables an functions near the beginning.
|
||||
|
||||
### Defining Functions
|
||||
|
||||
It is a good idea to put all of the code related to a single *task* all in one place.
|
||||
|
||||
```python
|
||||
def read_prices(filename):
|
||||
prices = {}
|
||||
with open(filename) as f:
|
||||
f_csv = csv.reader(f)
|
||||
for row in f_csv:
|
||||
prices[row[0]] = float(row[1])
|
||||
return prices
|
||||
```
|
||||
|
||||
A function also simplifies repeated operations.
|
||||
|
||||
```python
|
||||
oldprices = read_prices('oldprices.csv')
|
||||
newprices = read_prices('newprices.csv')
|
||||
```
|
||||
|
||||
### What is a Function?
|
||||
|
||||
A function is a named sequence of statements.
|
||||
|
||||
```python
|
||||
def funcname(args):
|
||||
statement
|
||||
statement
|
||||
...
|
||||
return result
|
||||
```
|
||||
|
||||
*Any* Python statement can be used inside.
|
||||
|
||||
```python
|
||||
def foo():
|
||||
import math
|
||||
print(math.sqrt(2))
|
||||
help(math)
|
||||
```
|
||||
|
||||
There are no *special* statements in Python.
|
||||
|
||||
### Function Definition
|
||||
|
||||
Functions can be *defined* in any order.
|
||||
|
||||
```python
|
||||
def foo(x):
|
||||
bar(x)
|
||||
|
||||
def bar(x):
|
||||
statements
|
||||
|
||||
# OR
|
||||
def bar(x)
|
||||
statements
|
||||
|
||||
def foo(x):
|
||||
bar(x)
|
||||
```
|
||||
|
||||
Functions must only be defined before they are actually *used* (or called) during program execution.
|
||||
|
||||
```python
|
||||
foo(3) # foo must be defined already
|
||||
```
|
||||
|
||||
Stylistically, it is probably more common to see functions defined in a *bottom-up* fashion.
|
||||
|
||||
### Bottom-up Style
|
||||
|
||||
Functions are treated as building blocks.
|
||||
The smaller/simpler blocks go first.
|
||||
|
||||
```python
|
||||
# myprogram.py
|
||||
def foo(x):
|
||||
...
|
||||
|
||||
def bar(x):
|
||||
...
|
||||
foo(x) # Defined above
|
||||
...
|
||||
|
||||
def spam(x):
|
||||
...
|
||||
bar(x) # Defined above
|
||||
...
|
||||
|
||||
spam(42) # Code that uses the functions appears at the end
|
||||
```
|
||||
|
||||
Later functions build upon earlier functions.
|
||||
|
||||
### Function Design
|
||||
|
||||
Ideally, functions should be a *black box*.
|
||||
They should only operate on passed inputs and avoid global variables
|
||||
and mysterious side-effects. Main goals: *Modularity* and *Predictability*.
|
||||
|
||||
### Doc Strings
|
||||
|
||||
A good practice is to include documentations in the form of
|
||||
doc-strings. Doc-strings are strings written immediately after the
|
||||
name of the function. They feed `help()`, IDEs and other tools.
|
||||
|
||||
```python
|
||||
def read_prices(filename):
|
||||
'''
|
||||
Read prices from a CSV file of name,price
|
||||
'''
|
||||
prices = {}
|
||||
with open(filename) as f:
|
||||
f_csv = csv.reader(f)
|
||||
for row in f_csv:
|
||||
prices[row[0]] = float(row[1])
|
||||
return prices
|
||||
```
|
||||
|
||||
### Type Annotations
|
||||
|
||||
You can also add some optional type annotations to your function definitions.
|
||||
|
||||
```python
|
||||
def read_prices(filename: str) -> dict:
|
||||
'''
|
||||
Read prices from a CSV file of name,price
|
||||
'''
|
||||
prices = {}
|
||||
with open(filename) as f:
|
||||
f_csv = csv.reader(f)
|
||||
for row in f_csv:
|
||||
prices[row[0]] = float(row[1])
|
||||
return prices
|
||||
```
|
||||
|
||||
These do nothing. It is purely informational.
|
||||
They may be used by IDEs, code checkers, etc.
|
||||
|
||||
## Exercises
|
||||
|
||||
In section 2, you wrote a program called `report.py` that printed out a report showing the performance of a stock portfolio.
|
||||
This program consisted of some functions. For example:
|
||||
|
||||
```python
|
||||
# report.py
|
||||
import csv
|
||||
|
||||
def read_portfolio(filename):
|
||||
'''
|
||||
Read a stock portfolio file into a list of dictionaries with keys
|
||||
name, shares, and price.
|
||||
'''
|
||||
portfolio = []
|
||||
with open(filename) as f:
|
||||
rows = csv.reader(f)
|
||||
headers = next(rows)
|
||||
|
||||
for row in rows:
|
||||
record = dict(zip(headers, row))
|
||||
stock = {
|
||||
'name' : record['name'],
|
||||
'shares' : int(record['shares']),
|
||||
'price' : float(record['price'])
|
||||
}
|
||||
portfolio.append(stock)
|
||||
return portfolio
|
||||
...
|
||||
```
|
||||
|
||||
However, there were also portions of the program that just performed a series of scripted calculations.
|
||||
This code appeared near the end of the program. For example:
|
||||
|
||||
```python
|
||||
...
|
||||
|
||||
# Output the report
|
||||
|
||||
headers = ('Name', 'Shares', 'Price', 'Change')
|
||||
print('%10s %10s %10s %10s' % headers)
|
||||
print(('-' * 10 + ' ') * len(headers))
|
||||
for row in report:
|
||||
print('%10s %10d %10.2f %10.2f' % row)
|
||||
...
|
||||
```
|
||||
|
||||
In this exercise, we’re going take this program and organize it a little more strongly around the use of functions.
|
||||
|
||||
### (a) Structuring a program as a collection of functions
|
||||
|
||||
Modify your `report.py` program so that all major operations,
|
||||
including calculations and output, are carried out by a collection of
|
||||
functions. Specifically:
|
||||
|
||||
* Create a function `print_report(report)` that prints out the report.
|
||||
* Change the last part of the program so that it is nothing more than a series of function calls and no other computation.
|
||||
|
||||
### (b) Creating a function for program execution
|
||||
|
||||
Take the last part of your program and package it into a single function `portfolio_report(portfolio_filename, prices_filename)`.
|
||||
Have the function work so that the following function call creates the report as before:
|
||||
|
||||
```python
|
||||
portfolio_report('Data/portfolio.csv', 'Data/prices.csv')
|
||||
```
|
||||
|
||||
In this final version, your program will be nothing more than a series
|
||||
of function definitions followed by a single function call to
|
||||
`portfolio_report()` at the very end (which executes all of the steps
|
||||
involved in the program).
|
||||
|
||||
By turning your program into a single function, it becomes easy to run it on different inputs.
|
||||
For example, try these statements interactively after running your program:
|
||||
|
||||
```python
|
||||
>>> portfolio_report('Data/portfolio2.csv', 'Data/prices.csv')
|
||||
... look at the output ...
|
||||
>>> files = ['Data/portfolio.csv', 'Data/portfolio2.csv']
|
||||
>>> for name in files:
|
||||
print(f'{name:-^43s}')
|
||||
portfolio_report(name, 'prices.csv')
|
||||
print()
|
||||
|
||||
... look at the output ...
|
||||
>>>
|
||||
```
|
||||
|
||||
[Next](02_More_functions)
|
||||
Reference in New Issue
Block a user