chore: clean repository history

This commit is contained in:
xixu-me committed 2026-06-05 21:13:11 +08:00
commit e1fb2929c0
661 files changed
+167488

No files matched your search

@@ -0,0 +1,18 @@
[Contents](../Contents.md) \| [Next (2 Working With Data)](../02_Working_with_data/00_Overview.md)
## 1. Introduction to Python
The goal of this first section is to introduce some Python basics from
the ground up. Starting with nothing, you'll learn how to edit, run,
and debug small programs. Ultimately, you'll write a short script that
reads a CSV data file and performs a simple calculation.
* [1.1 Introducing Python](01_Python.md)
* [1.2 A First Program](02_Hello_world.md)
* [1.3 Numbers](03_Numbers.md)
* [1.4 Strings](04_Strings.md)
* [1.5 Lists](05_Lists.md)
* [1.6 Files](06_Files.md)
* [1.7 Functions](07_Functions.md)
[Contents](../Contents.md) \| [Next (2 Working With Data)](../02_Working_with_data/00_Overview.md)
@@ -0,0 +1,215 @@
[Contents](../Contents.md) \| [Next (1.2 A First Program)](02_Hello_world.md)
# 1.1 Python
### What is Python?
Python is an interpreted high level programming language. It is often classified as a
["scripting language"](https://en.wikipedia.org/wiki/Scripting_language) and
is considered similar to languages such as Perl, Tcl, or Ruby. The syntax
of Python is loosely inspired by elements of C programming.
Python was created by Guido van Rossum around 1990 who named it in honor of Monty Python.
### Where to get Python?
[Python.org](https://www.python.org/) is where you obtain Python. For the purposes of this course, you
only need a basic installation. I recommend installing Python 3.6 or newer. Python 3.6 is used in the notes
and solutions.
### Why was Python created?
In the words of Python's creator:
> My original motivation for creating Python was the perceived need
> for a higher level language in the Amoeba [Operating Systems]
> project. I realized that the development of system administration
> utilities in C was taking too long. Moreover, doing these things in
> the Bourne shell wouldn't work for a variety of reasons. ... So,
> there was a need for a language that would bridge the gap between C
> and the shell.
>
> - Guido van Rossum
### Where is Python on my Machine?
Although there are many environments in which you might run Python,
Python is typically installed on your machine as a program that runs
from the terminal or command shell. From the terminal, you should be
able to type `python` like this:
```
bash $ python
Python 3.8.1 (default, Feb 20 2020, 09:29:22)
[Clang 10.0.0 (clang-1000.10.44.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> print("hello world")
hello world
>>>
```
If you are new to using the shell or a terminal, you should probably
stop, finish a short tutorial on that first, and then return here.
Although there are many non-shell environments where you can code
Python, you will be a stronger Python programmer if you are able to
run, debug, and interact with Python at the terminal. This is
Python's native environment. If you are able to use Python here, you
will be able to use it everywhere else.
## Exercises
### Exercise 1.1: Using Python as a Calculator
On your machine, start Python and use it as a calculator to solve the
following problem.
Lucky Larry bought 75 shares of Google stock at a price of $235.14 per
share. Today, shares of Google are priced at $711.25. Using Python’s
interactive mode as a calculator, figure out how much profit Larry would
make if he sold all of his shares.
```python
>>> (711.25 - 235.14) * 75
35708.25
>>>
```
Pro-tip: Use the underscore (\_) variable to use the result of the last
calculation. For example, how much profit does Larry make after his evil
broker takes their 20% cut?
```python
>>> _ * 0.80
28566.600000000002
>>>
```
### Exercise 1.2: Getting help
Use the `help()` command to get help on the `abs()` function. Then use
`help()` to get help on the `round()` function. Type `help()` just by
itself with no value to enter the interactive help viewer.
One caution with `help()` is that it doesn’t work for basic Python
statements such as `for`, `if`, `while`, and so forth (i.e., if you type
`help(for)` you’ll get a syntax error). You can try putting the help
topic in quotes such as `help("for")` instead. If that doesn’t work,
you’ll have to turn to an internet search.
Followup: Go to <http://docs.python.org> and find the documentation for
the `abs()` function (hint: it’s found under the library reference
related to built-in functions).
### Exercise 1.3: Cutting and Pasting
This course is structured as a series of traditional web pages where
you are encouraged to try interactive Python code samples **by typing
them out by hand.** If you are learning Python for the first time,
this "slow approach" is encouraged. You will get a better feel for
the language by slowing down, typing things in, and thinking about
what you are doing.
If you must "cut and paste" code samples, select code
starting after the `>>>` prompt and going up to, but not any further
than the first blank line or the next `>>>` prompt (whichever appears
first). Select "copy" from the browser, go to the Python window, and
select "paste" to copy it into the Python shell. To get the code to
run, you may have to hit "Return" once after you’ve pasted it in.
Use cut-and-paste to execute the Python statements in this session:
```python
>>> 12 + 20
32
>>> (3 + 4
+ 5 + 6)
18
>>> for i in range(5):
print(i)
0
1
2
3
4
>>>
```
Warning: It is never possible to paste more than one Python command
(statements that appear after `>>>`) to the basic Python shell at a
time. You have to paste each command one at a time.
Now that you've done this, just remember that you will get more out of
the class by typing in code slowly and thinking about it--not cut and pasting.
### Exercise 1.4: Where is My Bus?
Note: This was a whimsical example that was a real crowd-pleaser when
I taught this course in my office. You could query the bus and then
literally watch it pass by the window out front. Sadly, APIs rarely live
forever and it seems that this one has now ridden off into the sunset. --Dave
Update: GitHub user @asett has suggested the following modified code might work,
but you'll have to provide your own API key (available [here](https://www.transitchicago.com/developers/bustracker/)).
```python
import urllib.request
u = urllib.request.urlopen('http://www.ctabustracker.com/bustime/api/v2/getpredictions?key=REDACTED_PLACEHOLDER&rt=22&stpid=14791')
from xml.etree.ElementTree import parse
doc = parse(u)
print("Arrival time in minutes:")
for pt in doc.findall('.//prdctdn'):
print(pt.text)
```
(Original exercise example follows below)
Try something more advanced and type these statements to find out how
long people waiting on the corner of Clark street and Balmoral in
Chicago will have to wait for the next northbound CTA \#22 bus:
```python
>>> import urllib.request
>>> u = urllib.request.urlopen('http://ctabustracker.com/bustime/map/getStopPredictions.jsp?stop=14791&route=22')
>>> from xml.etree.ElementTree import parse
>>> doc = parse(u)
>>> for pt in doc.findall('.//pt'):
print(pt.text)
6 MIN
18 MIN
28 MIN
>>>
```
Yes, you just downloaded a web page, parsed an XML document, and
extracted some useful information in about 6 lines of code. The data
you accessed is actually feeding the website
<http://ctabustracker.com/bustime/home.jsp>. Try it again and watch
the predictions change.
Note: This service only reports arrival times within the next 30 minutes.
If you're in a different timezone and it happens to be 3am in Chicago, you
might not get any output. You use the tracker link above to double check.
If the first import statement `import urllib.request` fails, you’re
probably using Python 2. For this course, you need to make sure you’re
using Python 3.6 or newer. Go to <https://www.python.org> to download
it if you need it.
If your work environment requires the use of an HTTP proxy server, you may need
to set the `HTTP_PROXY` environment variable to make this part of the
exercise work. For example:
```python
>>> import os
>>> os.environ['HTTP_PROXY'] = 'http://yourproxy.server.com'
>>>
```
If you can't make this work, don't worry about it. The rest of this course
has nothing to do with parsing XML.
[Contents](../Contents.md) \| [Next (1.2 A First Program)](02_Hello_world.md)
@@ -0,0 +1,478 @@
[Contents](../Contents.md) \| [Previous (1.1 Python)](01_Python.md) \| [Next (1.3 Numbers)](03_Numbers.md)
# 1.2 A First Program
This section discusses the creation of your first program, running the
interpreter, and some basic debugging.
### Running Python
Python programs always run inside an interpreter.
The interpreter is a "console-based" application that normally runs
from a command shell.
```bash
python3
Python 3.6.1 (v3.6.1:69c0db5050, Mar 21 2017, 01:21:04)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
```
Expert programmers usually have no problem using the interpreter in
this way, but it's not so user-friendly for beginners. You may be using
an environment that provides a different interface to Python. That's fine,
but learning how to run Python terminal is still a useful skill to know.
### Interactive Mode
When you start Python, you get an *interactive* mode where you can experiment.
If you start typing statements, they will run immediately. There is no
edit/compile/run/debug cycle.
```python
>>> print('hello world')
hello world
>>> 37*42
1554
>>> for i in range(5):
... print(i)
...
0
1
2
3
4
>>>
```
This so-called *read-eval-print-loop* (or REPL) is very useful for debugging and exploration.
**STOP**: If you can't figure out how to interact with Python, stop what you're doing
and figure out how to do it. If you're using an IDE, it might be hidden behind a
menu option or other window. Many parts of this course assume that you can
interact with the interpreter.
Let's take a closer look at the elements of the REPL:
- `>>>` is the interpreter prompt for starting a new statement.
- `...` is the interpreter prompt for continuing a statement. Enter a blank line to finish typing and run what you've entered.
The `...` prompt may or may not be shown depending on your environment. For this course,
it is shown as blanks to make it easier to cut/paste code samples.
The underscore `_` holds the last result.
```python
>>> 37 * 42
1554
>>> _ * 2
3108
>>> _ + 50
3158
>>>
```
*This is only true in the interactive mode.* You never use `_` in a program.
### Creating programs
Programs are put in `.py` files.
```python
# hello.py
print('hello world')
```
You can create these files with your favorite text editor.
### Running Programs
To execute a program, run it in the terminal with the `python` command.
For example, in command-line Unix:
```bash
bash % python hello.py
hello world
bash %
```
Or from the Windows shell:
```
C:\SomeFolder>hello.py
hello world
C:\SomeFolder>c:\python36\python hello.py
hello world
```
Note: On Windows, you may need to specify a full path to the Python interpreter such as `c:\python36\python`.
However, if Python is installed in its usual way, you might be able to just type the name of the program
such as `hello.py`.
### A Sample Program
Let's solve the following problem:
> One morning, you go out and place a dollar bill on the sidewalk by the Sears tower in Chicago.
> Each day thereafter, you go out double the number of bills.
> How long does it take for the stack of bills to exceed the height of the tower?
Here's a solution:
```python
# sears.py
bill_thickness = 0.11 * 0.001 # Meters (0.11 mm)
sears_height = 442 # Height (meters)
num_bills = 1
day = 1
while num_bills * bill_thickness < sears_height:
print(day, num_bills, num_bills * bill_thickness)
day = day + 1
num_bills = num_bills * 2
print('Number of days', day)
print('Number of bills', num_bills)
print('Final height', num_bills * bill_thickness)
```
When you run it, you get the following output:
```bash
bash % python3 sears.py
1 1 0.00011
2 2 0.00022
3 4 0.00044
4 8 0.00088
5 16 0.00176
6 32 0.00352
...
21 1048576 115.34336
22 2097152 230.68672
Number of days 23
Number of bills 4194304
Final height 461.37344
```
Using this program as a guide, you can learn a number of important core concepts about Python.
### Statements
A python program is a sequence of statements:
```python
a = 3 + 4
b = a * 2
print(b)
```
Each statement is terminated by a newline. Statements are executed one after the other until control reaches the end of the file.
### Comments
Comments are text that will not be executed.
```python
a = 3 + 4
# This is a comment
b = a * 2
print(b)
```
Comments are denoted by `#` and extend to the end of the line.
### Variables
A variable is a name for a value. You can use letters (lower and
upper-case) from a to z. As well as the character underscore `_`.
Numbers can also be part of the name of a variable, except as the
first character.
```python
height = 442 # valid
_height = 442 # valid
height2 = 442 # valid
2height = 442 # invalid
```
### Types
Variables do not need to be declared with the type of the value. The type
is associated with the value on the right hand side, not name of the variable.
```python
height = 442 # An integer
height = 442.0 # Floating point
height = 'Really tall' # A string
```
Python is dynamically typed. The perceived "type" of a variable might change
as a program executes depending on the current value assigned to it.
### Case Sensitivity
Python is case sensitive. Upper and lower-case letters are considered different letters.
These are all different variables:
```python
name = 'Jake'
Name = 'Elwood'
NAME = 'Guido'
```
Language statements are always lower-case.
```python
while x < 0: # OK
WHILE x < 0: # ERROR
```
### Looping
The `while` statement executes a loop.
```python
while num_bills * bill_thickness < sears_height:
print(day, num_bills, num_bills * bill_thickness)
day = day + 1
num_bills = num_bills * 2
print('Number of days', day)
```
The statements indented below the `while` will execute as long as the expression after the `while` is `true`.
### Indentation
Indentation is used to denote groups of statements that go together.
Consider the previous example:
```python
while num_bills * bill_thickness < sears_height:
print(day, num_bills, num_bills * bill_thickness)
day = day + 1
num_bills = num_bills * 2
print('Number of days', day)
```
Indentation groups the following statements together as the operations that repeat:
```python
print(day, num_bills, num_bills * bill_thickness)
day = day + 1
num_bills = num_bills * 2
```
Because the `print()` statement at the end is not indented, it
does not belong to the loop. The empty line is just for
readability. It does not affect the execution.
### Indentation best practices
* Use spaces instead of tabs.
* Use 4 spaces per level.
* Use a Python-aware editor.
Python's only requirement is that indentation within the same block
be consistent. For example, this is an error:
```python
while num_bills * bill_thickness < sears_height:
print(day, num_bills, num_bills * bill_thickness)
day = day + 1 # ERROR
num_bills = num_bills * 2
```
### Conditionals
The `if` statement is used to execute a conditional:
```python
if a > b:
print('Computer says no')
else:
print('Computer says yes')
```
You can check for multiple conditions by adding extra checks using `elif`.
```python
if a > b:
print('Computer says no')
elif a == b:
print('Computer says yes')
else:
print('Computer says maybe')
```
### Printing
The `print` function produces a single line of text with the values passed.
```python
print('Hello world!') # Prints the text 'Hello world!'
```
You can use variables. The text printed will be the value of the variable, not the name.
```python
x = 100
print(x) # Prints the text '100'
```
If you pass more than one value to `print` they are separated by spaces.
```python
name = 'Jake'
print('My name is', name) # Print the text 'My name is Jake'
```
`print()` always puts a newline at the end.
```python
print('Hello')
print('My name is', 'Jake')
```
This prints:
```code
Hello
My name is Jake
```
The extra newline can be suppressed:
```python
print('Hello', end=' ')
print('My name is', 'Jake')
```
This code will now print:
```code
Hello My name is Jake
```
### User input
To read a line of typed user input, use the `input()` function:
```python
name = input('Enter your name:')
print('Your name is', name)
```
`input` prints a prompt to the user and returns their response.
This is useful for small programs, learning exercises or simple debugging.
It is not widely used for real programs.
### pass statement
Sometimes you need to specify an empty code block. The keyword `pass` is used for it.
```python
if a > b:
pass
else:
print('Computer says false')
```
This is also called a "no-op" statement. It does nothing. It serves as a placeholder for statements, possibly to be added later.
## Exercises
This is the first set of exercises where you need to create Python
files and run them. From this point forward, it is assumed that you
are editing files in the `practical-python/Work/` directory. To help
you locate the proper place, a number of empty starter files have
been created with the appropriate filenames. Look for the file
`Work/bounce.py` that's used in the first exercise.
### Exercise 1.5: The Bouncing Ball
A rubber ball is dropped from a height of 100 meters and each time it
hits the ground, it bounces back up to 3/5 the height it fell. Write
a program `bounce.py` that prints a table showing the height of the
first 10 bounces.
Your program should make a table that looks something like this:
```code
1 60.0
2 36.0
3 21.599999999999998
4 12.959999999999999
5 7.775999999999999
6 4.6655999999999995
7 2.7993599999999996
8 1.6796159999999998
9 1.0077695999999998
10 0.6046617599999998
```
*Note: You can clean up the output a bit if you use the round() function. Try using it to round the output to 4 digits.*
```code
1 60.0
2 36.0
3 21.6
4 12.96
5 7.776
6 4.6656
7 2.7994
8 1.6796
9 1.0078
10 0.6047
```
### Exercise 1.6: Debugging
The following code fragment contains code from the Sears tower problem. It also has a bug in it.
```python
# sears.py
bill_thickness = 0.11 * 0.001 # Meters (0.11 mm)
sears_height = 442 # Height (meters)
num_bills = 1
day = 1
while num_bills * bill_thickness < sears_height:
print(day, num_bills, num_bills * bill_thickness)
day = days + 1
num_bills = num_bills * 2
print('Number of days', day)
print('Number of bills', num_bills)
print('Final height', num_bills * bill_thickness)
```
Copy and paste the code that appears above in a new program called `sears.py`.
When you run the code you will get an error message that causes the
program to crash like this:
```code
Traceback (most recent call last):
File "sears.py", line 10, in <module>
day = days + 1
NameError: name 'days' is not defined
```
Reading error messages is an important part of Python code. If your program
crashes, the very last line of the traceback message is the actual reason why the
the program crashed. Above that, you should see a fragment of source code and then
an identifying filename and line number.
* Which line is the error?
* What is the error?
* Fix the error
* Run the program successfully
[Contents](../Contents.md) \| [Previous (1.1 Python)](01_Python.md) \| [Next (1.3 Numbers)](03_Numbers.md)
@@ -0,0 +1,269 @@
[Contents](../Contents.md) \| [Previous (1.2 A First Program)](02_Hello_world.md) \| [Next (1.4 Strings)](04_Strings.md)
# 1.3 Numbers
This section discusses mathematical calculations.
### Types of Numbers
Python has 4 types of numbers:
* Booleans
* Integers
* Floating point
* Complex (imaginary numbers)
### Booleans (bool)
Booleans have two values: `True`, `False`.
```python
a = True
b = False
```
Numerically, they're evaluated as integers with value `1`, `0`.
```python
c = 4 + True # 5
d = False
if d == 0:
print('d is False')
```
*But, don't write code like that. It would be odd.*
### Integers (int)
Signed values of arbitrary size and base:
```python
a = 37
b = -299392993727716627377128481812241231
c = 0x7fa8 # Hexadecimal
d = 0o253 # Octal
e = 0b10001111 # Binary
```
Common operations:
```
x + y Add
x - y Subtract
x * y Multiply
x / y Divide (produces a float)
x // y Floor Divide (produces an integer)
x % y Modulo (remainder)
x ** y Power
x << n Bit shift left
x >> n Bit shift right
x & y Bit-wise AND
x | y Bit-wise OR
x ^ y Bit-wise XOR
~x Bit-wise NOT
abs(x) Absolute value
```
### Floating point (float)
Use a decimal or exponential notation to specify a floating point value:
```python
a = 37.45
b = 4e5 # 4 x 10**5 or 400,000
c = -1.345e-10
```
Floats are represented as double precision using the native CPU representation [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754).
This is the same as the `double` type in the programming language C.
> 17 digits of precision
> Exponent from -308 to 308
Be aware that floating point numbers are inexact when representing decimals.
```python
>>> a = 2.1 + 4.2
>>> a == 6.3
False
>>> a
6.300000000000001
>>>
```
This is **not a Python issue**, but the underlying floating point hardware on the CPU.
Common Operations:
```
x + y Add
x - y Subtract
x * y Multiply
x / y Divide
x // y Floor Divide
x % y Modulo
x ** y Power
abs(x) Absolute Value
```
These are the same operators as Integers, except for the bit-wise operators.
Additional math functions are found in the `math` module.
```python
import math
a = math.sqrt(x)
b = math.sin(x)
c = math.cos(x)
d = math.tan(x)
e = math.log(x)
```
### Comparisons
The following comparison / relational operators work with numbers:
```
x < y Less than
x <= y Less than or equal
x > y Greater than
x >= y Greater than or equal
x == y Equal to
x != y Not equal to
```
You can form more complex boolean expressions using
`and`, `or`, `not`
Here are a few examples:
```python
if b >= a and b <= c:
print('b is between a and c')
if not (b < a or b > c):
print('b is still between a and c')
```
### Converting Numbers
The type name can be used to convert values:
```python
a = int(x) # Convert x to integer
b = float(x) # Convert x to float
```
Try it out.
```python
>>> a = 3.14159
>>> int(a)
3
>>> b = '3.14159' # It also works with strings containing numbers
>>> float(b)
3.14159
>>>
```
## Exercises
Reminder: These exercises assume you are working in the `practical-python/Work` directory. Look
for the file `mortgage.py`.
### Exercise 1.7: Dave's mortgage
Dave has decided to take out a 30-year fixed rate mortgage of $500,000
with Guido’s Mortgage, Stock Investment, and Bitcoin trading
corporation. The interest rate is 5% and the monthly payment is
$2684.11.
Here is a program that calculates the total amount that Dave will have
to pay over the life of the mortgage:
```python
# mortgage.py
principal = 500000.0
rate = 0.05
payment = 2684.11
total_paid = 0.0
while principal > 0:
principal = principal * (1+rate/12) - payment
total_paid = total_paid + payment
print('Total paid', total_paid)
```
Enter this program and run it. You should get an answer of `966,279.6`.
### Exercise 1.8: Extra payments
Suppose Dave pays an extra $1000/month for the first 12 months of the mortgage?
Modify the program to incorporate this extra payment and have it print the total amount paid along with the number of months required.
When you run the new program, it should report a total payment of `929,965.62` over 342 months.
### Exercise 1.9: Making an Extra Payment Calculator
Modify the program so that extra payment information can be more generally handled.
Make it so that the user can set these variables:
```python
extra_payment_start_month = 61
extra_payment_end_month = 108
extra_payment = 1000
```
Make the program look at these variables and calculate the total paid appropriately.
How much will Dave pay if he pays an extra $1000/month for 4 years starting after the first
five years have already been paid?
### Exercise 1.10: Making a table
Modify the program to print out a table showing the month, total paid so far, and the remaining principal.
The output should look something like this:
```bash
1 2684.11 499399.22
2 5368.22 498795.94
3 8052.33 498190.15
4 10736.44 497581.83
5 13420.55 496970.98
...
308 874705.88 3478.83
309 877389.99 809.21
310 880074.1 -1871.53
Total paid 880074.1
Months 310
```
### Exercise 1.11: Bonus
While you’re at it, fix the program to correct for the overpayment that occurs in the last month.
### Exercise 1.12: A Mystery
`int()` and `float()` can be used to convert numbers. For example,
```python
>>> int("123")
123
>>> float("1.23")
1.23
>>>
```
With that in mind, can you explain this behavior?
```python
>>> bool("False")
True
>>>
```
[Contents](../Contents.md) \| [Previous (1.2 A First Program)](02_Hello_world.md) \| [Next (1.4 Strings)](04_Strings.md)
@@ -0,0 +1,488 @@
[Contents](../Contents.md) \| [Previous (1.3 Numbers)](03_Numbers.md) \| [Next (1.5 Lists)](05_Lists.md)
# 1.4 Strings
This section introduces ways to work with text.
### Representing Literal Text
String literals are written in programs with quotes.
```python
# Single quote
a = 'Yeah but no but yeah but...'
# Double quote
b = "computer says no"
# Triple quotes
c = '''
Look into my eyes, look into my eyes, the eyes, the eyes, the eyes,
not around the eyes,
don't look around the eyes,
look into my eyes, you're under.
'''
```
Normally strings may only span a single line. Triple quotes capture all text enclosed across multiple lines
including all formatting.
There is no difference between using single (') versus double (")
quotes. *However, the same type of quote used to start a string must be used to
terminate it*.
### String escape codes
Escape codes are used to represent control characters and characters that can't be easily typed
directly at the keyboard. Here are some common escape codes:
```
'\n' Line feed
'\r' Carriage return
'\t' Tab
'\'' Literal single quote
'\"' Literal double quote
'\\' Literal backslash
```
### String Representation
Each character in a string is stored internally as a so-called Unicode "code-point" which is
an integer. You can specify an exact code-point value using the following escape sequences:
```python
a = '\xf1' # a = 'ñ'
b = '\u2200' # b = '∀'
c = '\U0001D122' # c = '𝄢'
d = '\N{FOR ALL}' # d = '∀'
```
The [Unicode Character Database](https://unicode.org/charts) is a reference for all
available character codes.
### String Indexing
Strings work like an array for accessing individual characters. You use an integer index, starting at 0.
Negative indices specify a position relative to the end of the string.
```python
a = 'Hello world'
b = a[0] # 'H'
c = a[4] # 'o'
d = a[-1] # 'd' (end of string)
```
You can also slice or select substrings specifying a range of indices with `:`.
```python
d = a[:5] # 'Hello'
e = a[6:] # 'world'
f = a[3:8] # 'lo wo'
g = a[-5:] # 'world'
```
The character at the ending index is not included. Missing indices assume the beginning or ending of the string.
### String operations
Concatenation, length, membership and replication.
```python
# Concatenation (+)
a = 'Hello' + 'World' # 'HelloWorld'
b = 'Say ' + a # 'Say HelloWorld'
# Length (len)
s = 'Hello'
len(s) # 5
# Membership test (`in`, `not in`)
t = 'e' in s # True
f = 'x' in s # False
g = 'hi' not in s # True
# Replication (s * n)
rep = s * 5 # 'HelloHelloHelloHelloHello'
```
### String methods
Strings have methods that perform various operations with the string data.
Example: stripping any leading / trailing white space.
```python
s = ' Hello '
t = s.strip() # 'Hello'
```
Example: Case conversion.
```python
s = 'Hello'
l = s.lower() # 'hello'
u = s.upper() # 'HELLO'
```
Example: Replacing text.
```python
s = 'Hello world'
t = s.replace('Hello' , 'Hallo') # 'Hallo world'
```
**More string methods:**
Strings have a wide variety of other methods for testing and manipulating the text data.
This is a small sample of methods:
```python
s.endswith(suffix) # Check if string ends with suffix
s.find(t) # First occurrence of t in s
s.index(t) # First occurrence of t in s
s.isalpha() # Check if characters are alphabetic
s.isdigit() # Check if characters are numeric
s.islower() # Check if characters are lower-case
s.isupper() # Check if characters are upper-case
s.join(slist) # Join a list of strings using s as delimiter
s.lower() # Convert to lower case
s.replace(old,new) # Replace text
s.rfind(t) # Search for t from end of string
s.rindex(t) # Search for t from end of string
s.split([delim]) # Split string into list of substrings
s.startswith(prefix) # Check if string starts with prefix
s.strip() # Strip leading/trailing space
s.upper() # Convert to upper case
```
### String Mutability
Strings are "immutable" or read-only.
Once created, the value can't be changed.
```python
>>> s = 'Hello World'
>>> s[1] = 'a'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
>>>
```
**All operations and methods that manipulate string data, always create new strings.**
### String Conversions
Use `str()` to convert any value to a string. The result is a string holding the
same text that would have been produced by the `print()` statement.
```python
>>> x = 42
>>> str(x)
'42'
>>>
```
### Byte Strings
A string of 8-bit bytes, commonly encountered with low-level I/O, is written as follows:
```python
data = b'Hello World\r\n'
```
By putting a little b before the first quotation, you specify that it is a byte string as opposed to a text string.
Most of the usual string operations work.
```python
len(data) # 13
data[0:5] # b'Hello'
data.replace(b'Hello', b'Cruel') # b'Cruel World\r\n'
```
Indexing is a bit different because it returns byte values as integers.
```python
data[0] # 72 (ASCII code for 'H')
```
Conversion to/from text strings.
```python
text = data.decode('utf-8') # bytes -> text
data = text.encode('utf-8') # text -> bytes
```
The `'utf-8'` argument specifies a character encoding. Other common
values include `'ascii'` and `'latin1'`.
### Raw Strings
Raw strings are string literals with an uninterpreted backslash. They
are specified by prefixing the initial quote with a lowercase "r".
```python
>>> rs = r'c:\newdata\test' # Raw (uninterpreted backslash)
>>> rs
'c:\\newdata\\test'
```
The string is the literal text enclosed inside, exactly as typed.
This is useful in situations where the backslash has special
significance. Example: filename, regular expressions, etc.
### f-Strings
A string with formatted expression substitution.
```python
>>> name = 'IBM'
>>> shares = 100
>>> price = 91.1
>>> a = f'{name:>10s} {shares:10d} {price:10.2f}'
>>> a
' IBM 100 91.10'
>>> b = f'Cost = ${shares*price:0.2f}'
>>> b
'Cost = $9110.00'
>>>
```
**Note: This requires Python 3.6 or newer.** The meaning of the format codes
is covered later.
## Exercises
In these exercises, you'll experiment with operations on Python's
string type. You should do this at the Python interactive prompt
where you can easily see the results. Important note:
> In exercises where you are supposed to interact with the interpreter,
> `>>>` is the interpreter prompt that you get when Python wants
> you to type a new statement. Some statements in the exercise span
> multiple lines--to get these statements to run, you may have to hit
> 'return' a few times. Just a reminder that you *DO NOT* type
> the `>>>` when working these examples.
Start by defining a string containing a series of stock ticker symbols like this:
```python
>>> symbols = 'AAPL,IBM,MSFT,YHOO,SCO'
>>>
```
### Exercise 1.13: Extracting individual characters and substrings
Strings are arrays of characters. Try extracting a few characters:
```python
>>> symbols[0]
?
>>> symbols[1]
?
>>> symbols[2]
?
>>> symbols[-1] # Last character
?
>>> symbols[-2] # Negative indices are from end of string
?
>>>
```
In Python, strings are read-only.
Verify this by trying to change the first character of `symbols` to a lower-case 'a'.
```python
>>> symbols[0] = 'a'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
>>>
```
### Exercise 1.14: String concatenation
Although string data is read-only, you can always reassign a variable
to a newly created string.
Try the following statement which concatenates a new symbol "GOOG" to
the end of `symbols`:
```python
>>> symbols = symbols + 'GOOG'
>>> symbols
'AAPL,IBM,MSFT,YHOO,SCOGOOG'
>>>
```
Oops! That's not what you wanted. Fix it so that the `symbols` variable holds the value `'AAPL,IBM,MSFT,YHOO,SCO,GOOG'`.
```python
>>> symbols = ?
>>> symbols
'AAPL,IBM,MSFT,YHOO,SCO,GOOG'
>>>
```
Add `'HPQ'` to the front the string:
```python
>>> symbols = ?
>>> symbols
'HPQ,AAPL,IBM,MSFT,YHOO,SCO,GOOG'
>>>
```
In these examples, it might look like the original string is being
modified, in an apparent violation of strings being read only. Not
so. Operations on strings create an entirely new string each
time. When the variable name `symbols` is reassigned, it points to the
newly created string. Afterwards, the old string is destroyed since
it's not being used anymore.
### Exercise 1.15: Membership testing (substring testing)
Experiment with the `in` operator to check for substrings. At the
interactive prompt, try these operations:
```python
>>> 'IBM' in symbols
?
>>> 'AA' in symbols
True
>>> 'CAT' in symbols
?
>>>
```
*Why did the check for `'AA'` return `True`?*
### Exercise 1.16: String Methods
At the Python interactive prompt, try experimenting with some of the string methods.
```python
>>> symbols.lower()
?
>>> symbols
?
>>>
```
Remember, strings are always read-only. If you want to save the result of an operation, you need to place it in a variable:
```python
>>> lowersyms = symbols.lower()
>>>
```
Try some more operations:
```python
>>> symbols.find('MSFT')
?
>>> symbols[13:17]
?
>>> symbols = symbols.replace('SCO','DOA')
>>> symbols
?
>>> name = ' IBM \n'
>>> name = name.strip() # Remove surrounding whitespace
>>> name
?
>>>
```
### Exercise 1.17: f-strings
Sometimes you want to create a string and embed the values of
variables into it.
To do that, use an f-string. For example:
```python
>>> name = 'IBM'
>>> shares = 100
>>> price = 91.1
>>> f'{shares} shares of {name} at ${price:0.2f}'
'100 shares of IBM at $91.10'
>>>
```
Modify the `mortgage.py` program from [Exercise 1.10](03_Numbers.md) to create its output using f-strings.
Try to make it so that output is nicely aligned.
### Exercise 1.18: Regular Expressions
One limitation of the basic string operations is that they don't
support any kind of advanced pattern matching. For that, you
need to turn to Python's `re` module and regular expressions.
Regular expression handling is a big topic, but here is a short
example:
```python
>>> text = 'Today is 3/27/2018. Tomorrow is 3/28/2018.'
>>> # Find all occurrences of a date
>>> import re
>>> re.findall(r'\d+/\d+/\d+', text)
['3/27/2018', '3/28/2018']
>>> # Replace all occurrences of a date with replacement text
>>> re.sub(r'(\d+)/(\d+)/(\d+)', r'\3-\1-\2', text)
'Today is 2018-3-27. Tomorrow is 2018-3-28.'
>>>
```
For more information about the `re` module, see the official documentation at
[https://docs.python.org/library/re.html](https://docs.python.org/3/library/re.html).
### Commentary
As you start to experiment with the interpreter, you often want to
know more about the operations supported by different objects. For
example, how do you find out what operations are available on a
string?
Depending on your Python environment, you might be able to see a list
of available methods via tab-completion. For example, try typing
this:
```python
>>> s = 'hello world'
>>> s.<tab key>
>>>
```
If hitting tab doesn't do anything, you can fall back to the
builtin-in `dir()` function. For example:
```python
>>> s = 'hello'
>>> dir(s)
['__add__', '__class__', '__contains__', ..., 'find', 'format',
'index', 'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace',
'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'partition',
'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit',
'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase',
'title', 'translate', 'upper', 'zfill']
>>>
```
`dir()` produces a list of all operations that can appear after the `(.)`.
Use the `help()` command to get more information about a specific operation:
```python
>>> help(s.upper)
Help on built-in function upper:
upper(...)
S.upper() -> string
Return a copy of the string S converted to uppercase.
>>>
```
[Contents](../Contents.md) \| [Previous (1.3 Numbers)](03_Numbers.md) \| [Next (1.5 Lists)](05_Lists.md)
@@ -0,0 +1,414 @@
[Contents](../Contents.md) \| [Previous (1.4 Strings)](04_Strings.md) \| [Next (1.6 Files)](06_Files.md)
# 1.5 Lists
This section introduces lists, Python's primary type for holding an ordered collection of values.
### Creating a List
Use square brackets to define a list literal:
```python
names = [ 'Elwood', 'Jake', 'Curtis' ]
nums = [ 39, 38, 42, 65, 111]
```
Sometimes lists are created by other methods. For example, a string can be split into a
list using the `split()` method:
```python
>>> line = 'GOOG,100,490.10'
>>> row = line.split(',')
>>> row
['GOOG', '100', '490.10']
>>>
```
### List operations
Lists can hold items of any type. Add a new item using `append()`:
```python
names.append('Murphy') # Adds at end
names.insert(2, 'Aretha') # Inserts in middle
```
Use `+` to concatenate lists:
```python
s = [1, 2, 3]
t = ['a', 'b']
s + t # [1, 2, 3, 'a', 'b']
```
Lists are indexed by integers. Starting at 0.
```python
names = [ 'Elwood', 'Jake', 'Curtis' ]
names[0] # 'Elwood'
names[1] # 'Jake'
names[2] # 'Curtis'
```
Negative indices count from the end.
```python
names[-1] # 'Curtis'
```
You can change any item in a list.
```python
names[1] = 'Joliet Jake'
names # [ 'Elwood', 'Joliet Jake', 'Curtis' ]
```
Length of the list.
```python
names = ['Elwood','Jake','Curtis']
len(names) # 3
```
Membership test (`in`, `not in`).
```python
'Elwood' in names # True
'Britney' not in names # True
```
Replication (`s * n`).
```python
s = [1, 2, 3]
s * 3 # [1, 2, 3, 1, 2, 3, 1, 2, 3]
```
### List Iteration and Search
Use `for` to iterate over the list contents.
```python
for name in names:
# use name
# e.g. print(name)
...
```
This is similar to a `foreach` statement from other programming languages.
To find the position of something quickly, use `index()`.
```python
names = ['Elwood','Jake','Curtis']
names.index('Curtis') # 2
```
If the element is present more than once, `index()` will return the index of the first occurrence.
If the element is not found, it will raise a `ValueError` exception.
### List Removal
You can remove items either by element value or by index:
```python
# Using the value
names.remove('Curtis')
# Using the index
del names[1]
```
Removing an item does not create a hole. Other items will move down
to fill the space vacated. If there are more than one occurrence of
the element, `remove()` will remove only the first occurrence.
### List Sorting
Lists can be sorted "in-place".
```python
s = [10, 1, 7, 3]
s.sort() # [1, 3, 7, 10]
# Reverse order
s = [10, 1, 7, 3]
s.sort(reverse=True) # [10, 7, 3, 1]
# It works with any ordered data
s = ['foo', 'bar', 'spam']
s.sort() # ['bar', 'foo', 'spam']
```
Use `sorted()` if you'd like to make a new list instead:
```python
t = sorted(s) # s unchanged, t holds sorted values
```
### Lists and Math
*Caution: Lists were not designed for math operations.*
```python
>>> nums = [1, 2, 3, 4, 5]
>>> nums * 2
[1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
>>> nums + [10, 11, 12, 13, 14]
[1, 2, 3, 4, 5, 10, 11, 12, 13, 14]
```
Specifically, lists don't represent vectors/matrices as in MATLAB, Octave, R, etc.
However, there are some packages to help you with that (e.g. [numpy](https://numpy.org)).
## Exercises
In this exercise, we experiment with Python's list datatype. In the last section,
you worked with strings containing stock symbols.
```python
>>> symbols = 'HPQ,AAPL,IBM,MSFT,YHOO,DOA,GOOG'
```
Split it into a list of names using the `split()` operation of strings:
```python
>>> symlist = symbols.split(',')
```
### Exercise 1.19: Extracting and reassigning list elements
Try a few lookups:
```python
>>> symlist[0]
'HPQ'
>>> symlist[1]
'AAPL'
>>> symlist[-1]
'GOOG'
>>> symlist[-2]
'DOA'
>>>
```
Try reassigning one value:
```python
>>> symlist[2] = 'AIG'
>>> symlist
['HPQ', 'AAPL', 'AIG', 'MSFT', 'YHOO', 'DOA', 'GOOG']
>>>
```
Take a few slices:
```python
>>> symlist[0:3]
['HPQ', 'AAPL', 'AIG']
>>> symlist[-2:]
['DOA', 'GOOG']
>>>
```
Create an empty list and append an item to it.
```python
>>> mysyms = []
>>> mysyms.append('GOOG')
>>> mysyms
['GOOG']
```
You can reassign a portion of a list to another list. For example:
```python
>>> symlist[-2:] = mysyms
>>> symlist
['HPQ', 'AAPL', 'AIG', 'MSFT', 'YHOO', 'GOOG']
>>>
```
When you do this, the list on the left-hand-side (`symlist`) will be resized as appropriate to make the right-hand-side (`mysyms`) fit.
For instance, in the above example, the last two items of `symlist` got replaced by the single item in the list `mysyms`.
### Exercise 1.20: Looping over list items
The `for` loop works by looping over data in a sequence such as a list.
Check this out by typing the following loop and watching what happens:
```python
>>> for s in symlist:
print('s =', s)
# Look at the output
```
### Exercise 1.21: Membership tests
Use the `in` or `not in` operator to check if `'AIG'`,`'AA'`, and `'CAT'` are in the list of symbols.
```python
>>> # Is 'AIG' IN the `symlist`?
True
>>> # Is 'AA' IN the `symlist`?
False
>>> # Is 'CAT' NOT IN the `symlist`?
True
>>>
```
### Exercise 1.22: Appending, inserting, and deleting items
Use the `append()` method to add the symbol `'RHT'` to end of `symlist`.
```python
>>> # append 'RHT'
>>> symlist
['HPQ', 'AAPL', 'AIG', 'MSFT', 'YHOO', 'GOOG', 'RHT']
>>>
```
Use the `insert()` method to insert the symbol `'AA'` as the second item in the list.
```python
>>> # Insert 'AA' as the second item in the list
>>> symlist
['HPQ', 'AA', 'AAPL', 'AIG', 'MSFT', 'YHOO', 'GOOG', 'RHT']
>>>
```
Use the `remove()` method to remove `'MSFT'` from the list.
```python
>>> # Remove 'MSFT'
>>> symlist
['HPQ', 'AA', 'AAPL', 'AIG', 'YHOO', 'GOOG', 'RHT']
>>>
```
Append a duplicate entry for `'YHOO'` at the end of the list.
*Note: it is perfectly fine for a list to have duplicate values.*
```python
>>> # Append 'YHOO'
>>> symlist
['HPQ', 'AA', 'AAPL', 'AIG', 'YHOO', 'GOOG', 'RHT', 'YHOO']
>>>
```
Use the `index()` method to find the first position of `'YHOO'` in the list.
```python
>>> # Find the first index of 'YHOO'
4
>>> symlist[4]
'YHOO'
>>>
```
Count how many times `'YHOO'` is in the list:
```python
>>> symlist.count('YHOO')
2
>>>
```
Remove the first occurrence of `'YHOO'`.
```python
>>> # Remove first occurrence 'YHOO'
>>> symlist
['HPQ', 'AA', 'AAPL', 'AIG', 'GOOG', 'RHT', 'YHOO']
>>>
```
Just so you know, there is no method to find or remove all occurrences of an item.
However, we'll see an elegant way to do this in section 2.
### Exercise 1.23: Sorting
Want to sort a list? Use the `sort()` method. Try it out:
```python
>>> symlist.sort()
>>> symlist
['AA', 'AAPL', 'AIG', 'GOOG', 'HPQ', 'RHT', 'YHOO']
>>>
```
Want to sort in reverse? Try this:
```python
>>> symlist.sort(reverse=True)
>>> symlist
['YHOO', 'RHT', 'HPQ', 'GOOG', 'AIG', 'AAPL', 'AA']
>>>
```
Note: Sorting a list modifies its contents 'in-place'. That is, the elements of the list are shuffled around, but no new list is created as a result.
### Exercise 1.24: Putting it all back together
Want to take a list of strings and join them together into one string?
Use the `join()` method of strings like this (note: this looks funny at first).
```python
>>> a = ','.join(symlist)
>>> a
'YHOO,RHT,HPQ,GOOG,AIG,AAPL,AA'
>>> b = ':'.join(symlist)
>>> b
'YHOO:RHT:HPQ:GOOG:AIG:AAPL:AA'
>>> c = ''.join(symlist)
>>> c
'YHOORHTHPQGOOGAIGAAPLAA'
>>>
```
### Exercise 1.25: Lists of anything
Lists can contain any kind of object, including other lists (e.g., nested lists).
Try this out:
```python
>>> nums = [101, 102, 103]
>>> items = ['spam', symlist, nums]
>>> items
['spam', ['YHOO', 'RHT', 'HPQ', 'GOOG', 'AIG', 'AAPL', 'AA'], [101, 102, 103]]
```
Pay close attention to the above output. `items` is a list with three elements.
The first element is a string, but the other two elements are lists.
You can access items in the nested lists by using multiple indexing operations.
```python
>>> items[0]
'spam'
>>> items[0][0]
's'
>>> items[1]
['YHOO', 'RHT', 'HPQ', 'GOOG', 'AIG', 'AAPL', 'AA']
>>> items[1][1]
'RHT'
>>> items[1][1][2]
'T'
>>> items[2]
[101, 102, 103]
>>> items[2][1]
102
>>>
```
Even though it is technically possible to make very complicated list
structures, as a general rule, you want to keep things simple.
Usually lists hold items that are all the same kind of value. For
example, a list that consists entirely of numbers or a list of text
strings. Mixing different kinds of data together in the same list is
often a good way to make your head explode so it's best avoided.
[Contents](../Contents.md) \| [Previous (1.4 Strings)](04_Strings.md) \| [Next (1.6 Files)](06_Files.md)
@@ -0,0 +1,248 @@
[Contents](../Contents.md) \| [Previous (1.5 Lists)](05_Lists.md) \| [Next (1.7 Functions)](07_Functions.md)
# 1.6 File Management
Most programs need to read input from somewhere. This section discusses file access.
### File Input and Output
Open a file.
```python
f = open('foo.txt', 'rt') # Open for reading (text)
g = open('bar.txt', 'wt') # Open for writing (text)
```
Read all of the data.
```python
data = f.read()
# Read only up to 'maxbytes' bytes
data = f.read([maxbytes])
```
Write some text.
```python
g.write('some text')
```
Close when you are done.
```python
f.close()
g.close()
```
Files should be properly closed and it's an easy step to forget.
Thus, the preferred approach is to use the `with` statement like this.
```python
with open(filename, 'rt') as file:
# Use the file `file`
...
# No need to close explicitly
...statements
```
This automatically closes the file when control leaves the indented code block.
### Common Idioms for Reading File Data
Read an entire file all at once as a string.
```python
with open('foo.txt', 'rt') as file:
data = file.read()
# `data` is a string with all the text in `foo.txt`
```
Read a file line-by-line by iterating.
```python
with open(filename, 'rt') as file:
for line in file:
# Process the line
```
### Common Idioms for Writing to a File
Write string data.
```python
with open('outfile', 'wt') as out:
out.write('Hello World\n')
...
```
Redirect the print function.
```python
with open('outfile', 'wt') as out:
print('Hello World', file=out)
...
```
## Exercises
These exercises depend on a file `Data/portfolio.csv`. The file
contains a list of lines with information on a portfolio of stocks.
It is assumed that you are working in the `practical-python/Work/`
directory. If you're not sure, you can find out where Python thinks
it's running by doing this:
```python
>>> import os
>>> os.getcwd()
'/Users/beazley/Desktop/practical-python/Work' # Output vary
>>>
```
### Exercise 1.26: File Preliminaries
First, try reading the entire file all at once as a big string:
```python
>>> with open('Data/portfolio.csv', 'rt') as f:
data = f.read()
>>> data
'name,shares,price\n"AA",100,32.20\n"IBM",50,91.10\n"CAT",150,83.44\n"MSFT",200,51.23\n"GE",95,40.37\n"MSFT",50,65.10\n"IBM",100,70.44\n'
>>> print(data)
name,shares,price
"AA",100,32.20
"IBM",50,91.10
"CAT",150,83.44
"MSFT",200,51.23
"GE",95,40.37
"MSFT",50,65.10
"IBM",100,70.44
>>>
```
In the above example, it should be noted that Python has two modes of
output. In the first mode where you type `data` at the prompt, Python
shows you the raw string representation including quotes and escape
codes. When you type `print(data)`, you get the actual formatted
output of the string.
Although reading a file all at once is simple, it is often not the
most appropriate way to do it—especially if the file happens to be
huge or if contains lines of text that you want to handle one at a
time.
To read a file line-by-line, use a for-loop like this:
```python
>>> with open('Data/portfolio.csv', 'rt') as f:
for line in f:
print(line, end='')
name,shares,price
"AA",100,32.20
"IBM",50,91.10
...
>>>
```
When you use this code as shown, lines are read until the end of the
file is reached at which point the loop stops.
On certain occasions, you might want to manually read or skip a
*single* line of text (e.g., perhaps you want to skip the first line
of column headers).
```python
>>> f = open('Data/portfolio.csv', 'rt')
>>> headers = next(f)
>>> headers
'name,shares,price\n'
>>> for line in f:
print(line, end='')
"AA",100,32.20
"IBM",50,91.10
...
>>> f.close()
>>>
```
`next()` returns the next line of text in the file. If you were to call it repeatedly, you would get successive lines.
However, just so you know, the `for` loop already uses `next()` to obtain its data.
Thus, you normally wouldn’t call it directly unless you’re trying to explicitly skip or read a single line as shown.
Once you’re reading lines of a file, you can start to perform more processing such as splitting.
For example, try this:
```python
>>> f = open('Data/portfolio.csv', 'rt')
>>> headers = next(f).split(',')
>>> headers
['name', 'shares', 'price\n']
>>> for line in f:
row = line.split(',')
print(row)
['"AA"', '100', '32.20\n']
['"IBM"', '50', '91.10\n']
...
>>> f.close()
```
*Note: In these examples, `f.close()` is being called explicitly because the `with` statement isn’t being used.*
### Exercise 1.27: Reading a data file
Now that you know how to read a file, let’s write a program to perform a simple calculation.
The columns in `portfolio.csv` correspond to the stock name, number of
shares, and purchase price of a single stock holding. Write a program called
`pcost.py` that opens this file, reads all lines, and calculates how
much it cost to purchase all of the shares in the portfolio.
*Hint: to convert a string to an integer, use `int(s)`. To convert a string to a floating point, use `float(s)`.*
Your program should print output such as the following:
```bash
Total cost 44671.15
```
### Exercise 1.28: Other kinds of "files"
What if you wanted to read a non-text file such as a gzip-compressed
datafile? The builtin `open()` function won’t help you here, but
Python has a library module `gzip` that can read gzip compressed
files.
Try it:
```python
>>> import gzip
>>> with gzip.open('Data/portfolio.csv.gz', 'rt') as f:
for line in f:
print(line, end='')
... look at the output ...
>>>
```
Note: Including the file mode of `'rt'` is critical here. If you forget that,
you'll get byte strings instead of normal text strings.
### Commentary: Shouldn't we being using Pandas for this?
Data scientists are quick to point out that libraries like
[Pandas](https://pandas.pydata.org) already have a function for
reading CSV files. This is true--and it works pretty well.
However, this is not a course on learning Pandas. Reading files
is a more general problem than the specifics of CSV files.
The main reason we're working with a CSV file is that it's a
familiar format to most coders and it's relatively easy to work with
directly--illustrating many Python features in the process.
So, by all means use Pandas when you go back to work. For the
rest of this course however, we're going to stick with standard
Python functionality.
[Contents](../Contents.md) \| [Previous (1.5 Lists)](05_Lists.md) \| [Next (1.7 Functions)](07_Functions.md)
@@ -0,0 +1,281 @@
[Contents](../Contents.md) \| [Previous (1.6 Files)](06_Files.md) \| [Next (2.0 Working with Data)](../02_Working_with_data/00_Overview.md)
# 1.7 Functions
As your programs start to get larger, you'll want to get organized. This section
briefly introduces functions and library modules. Error handling with exceptions is also introduced.
### Custom Functions
Use functions for code you want to reuse. Here is a function definition:
```python
def sumcount(n):
'''
Returns the sum of the first n integers
'''
total = 0
while n > 0:
total += n
n -= 1
return total
```
To call a function.
```python
a = sumcount(100)
```
A function is a series of statements that perform some task and return a result.
The `return` keyword is needed to explicitly specify the return value of the function.
### Library Functions
Python comes with a large standard library.
Library modules are accessed using `import`.
For example:
```python
import math
x = math.sqrt(10)
import urllib.request
u = urllib.request.urlopen('http://www.python.org/')
data = u.read()
```
We will cover libraries and modules in more detail later.
### Errors and exceptions
Functions report errors as exceptions. An exception causes a function to abort and may
cause your entire program to stop if unhandled.
Try this in your python REPL.
```python
>>> int('N/A')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'N/A'
>>>
```
For debugging purposes, the message describes what happened, where the error occurred,
and a traceback showing the other function calls that led to the failure.
### Catching and Handling Exceptions
Exceptions can be caught and handled.
To catch, use the `try - except` statement.
```python
for line in file:
fields = line.split(',')
try:
shares = int(fields[1])
except ValueError:
print("Couldn't parse", line)
...
```
The name `ValueError` must match the kind of error you are trying to catch.
It is often difficult to know exactly what kinds of errors might occur
in advance depending on the operation being performed. For better or
for worse, exception handling often gets added *after* a program has
unexpectedly crashed (i.e., "oh, we forgot to catch that error. We
should handle that!").
### Raising Exceptions
To raise an exception, use the `raise` statement.
```python
raise RuntimeError('What a kerfuffle')
```
This will cause the program to abort with an exception traceback. Unless caught by a `try-except` block.
```bash
% python3 foo.py
Traceback (most recent call last):
File "foo.py", line 21, in <module>
raise RuntimeError("What a kerfuffle")
RuntimeError: What a kerfuffle
```
## Exercises
### Exercise 1.29: Defining a function
Try defining a simple function:
```python
>>> def greeting(name):
'Issues a greeting'
print('Hello', name)
>>> greeting('Guido')
Hello Guido
>>> greeting('Paula')
Hello Paula
>>>
```
If the first statement of a function is a string, it serves as documentation.
Try typing a command such as `help(greeting)` to see it displayed.
### Exercise 1.30: Turning a script into a function
Take the code you wrote for the `pcost.py` program in [Exercise 1.27](06_Files.md)
and turn it into a function `portfolio_cost(filename)`. This
function takes a filename as input, reads the portfolio data in that
file, and returns the total cost of the portfolio as a float.
To use your function, change your program so that it looks something
like this:
```python
def portfolio_cost(filename):
...
# Your code here
...
cost = portfolio_cost('Data/portfolio.csv')
print('Total cost:', cost)
```
When you run your program, you should see the same output as before.
After you’ve run your program, you can also call your function
interactively by typing this:
```bash
bash $ python3 -i pcost.py
```
This will allow you to call your function from the interactive mode.
```python
>>> portfolio_cost('Data/portfolio.csv')
44671.15
>>>
```
Being able to experiment with your code interactively is useful for
testing and debugging.
### Exercise 1.31: Error handling
What happens if you try your function on a file with some missing fields?
```python
>>> portfolio_cost('Data/missing.csv')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "pcost.py", line 11, in portfolio_cost
nshares = int(fields[1])
ValueError: invalid literal for int() with base 10: ''
>>>
```
At this point, you’re faced with a decision. To make the program work
you can either sanitize the original input file by eliminating bad
lines or you can modify your code to handle the bad lines in some
manner.
Modify the `pcost.py` program to catch the exception, print a warning
message, and continue processing the rest of the file.
### Exercise 1.32: Using a library function
Python comes with a large standard library of useful functions. One
library that might be useful here is the `csv` module. You should use
it whenever you have to work with CSV data files. Here is an example
of how it works:
```python
>>> import csv
>>> f = open('Data/portfolio.csv')
>>> rows = csv.reader(f)
>>> headers = next(rows)
>>> headers
['name', 'shares', 'price']
>>> for row in rows:
print(row)
['AA', '100', '32.20']
['IBM', '50', '91.10']
['CAT', '150', '83.44']
['MSFT', '200', '51.23']
['GE', '95', '40.37']
['MSFT', '50', '65.10']
['IBM', '100', '70.44']
>>> f.close()
>>>
```
One nice thing about the `csv` module is that it deals with a variety
of low-level details such as quoting and proper comma splitting. In
the above output, you’ll notice that it has stripped the double-quotes
away from the names in the first column.
Modify your `pcost.py` program so that it uses the `csv` module for
parsing and try running earlier examples.
### Exercise 1.33: Reading from the command line
In the `pcost.py` program, the name of the input file has been hardwired into the code:
```python
# pcost.py
def portfolio_cost(filename):
...
# Your code here
...
cost = portfolio_cost('Data/portfolio.csv')
print('Total cost:', cost)
```
That’s fine for learning and testing, but in a real program you
probably wouldn’t do that.
Instead, you might pass the name of the file in as an argument to a
script. Try changing the bottom part of the program as follows:
```python
# pcost.py
import sys
def portfolio_cost(filename):
...
# Your code here
...
if len(sys.argv) == 2:
filename = sys.argv[1]
else:
filename = 'Data/portfolio.csv'
cost = portfolio_cost(filename)
print('Total cost:', cost)
```
`sys.argv` is a list that contains passed arguments on the command line (if any).
To run your program, you’ll need to run Python from the
terminal.
For example, from bash on Unix:
```bash
bash % python3 pcost.py Data/portfolio.csv
Total cost: 44671.15
bash %
```
[Contents](../Contents.md) \| [Previous (1.6 Files)](06_Files.md) \| [Next (2.0 Working with Data)](../02_Working_with_data/00_Overview.md)