6.4 KiB
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.
# 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.
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.
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.
oldprices = read_prices('oldprices.csv')
newprices = read_prices('newprices.csv')
What is a Function?
A function is a named sequence of statements.
def funcname(args):
statement
statement
...
return result
Any Python statement can be used inside.
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.
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.
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.
# 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.
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.
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:
# 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:
...
# 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:
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:
>>> 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 ...
>>>