Files
practical-python/Notes/03_Program_organization/01_Script.md
2020-05-25 12:40:11 -05:00

276 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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, were 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)