Yet Another Data Science Python Tutorial¶
This is a basic tutorial to teach a beginner the Python needed to perform Data Science. It assumes some knowledge of getting around a computer and the Internet, and a basic background in mathematics ideally with some statistics. By the end you should have learned how to set up a Python environment, prepare and run a significant Python program, and be ready to learn and use basic Data Science libraries such as NumPy, Pandas, SciKit Learn, and Matplotlib to do real Data Science.
Data Science¶
Data science uses mathematics and statistics to enable organizations to extract insights and guide operational decisions from raw data. It combines mathematics, computing and domain knowledge to solve real-world problems and uncover hidden patterns. Companies are generating vast amounts of data that data scientists can clean, statistically analyze, transform, and visualize to discover patterns and predict events in the future. These can be presented as actionable insights to business decision makers.
Why Python?¶
Python is the dominant programming language for data science because its simplicity makes it easy to learn and use and it is currently the most popular programming language in the world. It was created by Guido van Rossum in 1991, named for the BBC comedy series Monty Python’s Flying Circus. It's usefulness for data science is because of the large ecosystem of open-source third-party packages for Data Science and the large community of developers who create libraries and tools to make Python easier to use.
Data¶
Humans are the third most intelligent species on the planet (after mice and dolphins, as anyone who has seen or read Hitchhikers Guide to Galaxy knows). Humans are distinguished by their ability to create symbols, or abstract signs that mean some concept or object in the world. The oldest human created object symbols include 67,800 year old handprints and 45,500 year old painted animals found in caves in Indonesia. Animal symbols have been found that include 20,000 year old markings that tracked their mating behavior as an aid to hunters. The oldest evidence of counting has been found in 44,000 year old bones with notches on a bone that may have marked a lunar cycle.
Tally marks were a form of concrete counting where symbols formed a one-to-one correspondence with physical objects or concepts. Eventually shepherds used stones to count their flock and tokens were used in the Middle East to count agricultural goods and livestock. Over time, the tokens evolved to represent abstract quantities of what was actually counted. These tokens became marks inscribed on tablets that led to the development of cuneiform as a method of writing letters, words and text.
Data consists of symbols for descriptive facts, figures, observations, symbols, and other useful information. These can be electronically stored as numbers, letters, and text for automatic processing in the computer. The purpose of data science is to inspect, clean, transform, and interpret raw data to support data-driven decisions.
Basic Python Data Types¶
Python has four basic built-in types of data:
- Strings: sequences of text characters
- Integers: negative and positive whole numbers, without fractions
- Floats: continuous numbers with fractions
- Booleans: validity that conditions hold
Note in the following that Python code, lines beginning with '#' are comments for descriptive purposes only and are ignored.
Strings¶
Strings are dynamic sequences of text characters inside matching single or double quotes.
These are examples of creating strings.
# strings can be defined within single quotes
'text string'
'text string'
# strings can also be defined within double quotes
"text string"
'text string'
# double quotes can be included in a single quoted string
'"double quotes" in a text string'
'"double quotes" in a text string'
# single quotes can be included in a double quoted string
"'single quotes' in a text string"
"'single quotes' in a text string"
# double quotes can be included in a double quoted string by preceding them with back quote character '\'
"\"double quotes\" in a double-quoted text string"
'"double quotes" in a double-quoted text string'
# similarly for single quotes
'\'single quotes\' in a single-quoted text string'
"'single quotes' in a single-quoted text string"
# strings can be spread over two lines with the back quote character
"string split over \
two lines"
'string split over two lines'
String characters can be accessed by position counting from position 0 as the first character. Characters can be accessed from the end of the string with negative positions. The string "STRING" can be visualized as:
| 0 | 1 | 2 | 3 | 4 | 5 | |
| S | T | R | I | N | G | |
| -6 | -5 | -4 | -3 | -2 | -1 |
# "STRING"[0] is the first character
"STRING"[0]
'S'
# "STRING"[0] is the fourth character
"STRING"[3]
'I'
# "STRING"[-1] is the last character
"STRING"[-1]
'G'
# "STRING"[-3] is the third to last character
"STRING"[-3]
'I'
String slices can be accessed by referencing two positions separated by a colon :.
The characters are selected up to, but not including, the second position.
# "STRING"[0:2] are the first two characters
"STRING"[0:2]
'ST'
# "STRING"[1:3] are the second character up to, but not including, the fourth character (i.e., characters 1 and 2)
"STRING"[1:3]
'TR'
Slices can include negative positions. Again, the characters are selected up to, but not including, the second position.
# "string"[-3:-1] are the third to last up to, but not including, the last character
"STRING"[-3:-1]
'IN'
# "string"[-6:-2] are the first four characters
"STRING"[-6:-2]
'STRI'
Either the first or second position may be omitted.
- If the first position is omitted, the slice starts at the beginning of the string, up to but not including the second position.
- If the second position is omitted, the slice ends at with the last character of the string.
# with the first position omitted, "string"[:2] are the first two characters
"STRING"[:2]
'ST'
# with the second position omitted, "string"[3:] are the last three characters
"STRING"[3:]
'ING'
# slices with a negative first position omitting the second position, "string"[-4:] are the last four characters
"STRING"[-4:]
'RING'
# slices omitting the first position with a negative second position, "string"[:-3] are the first three characters.
"STRING"[:-3]
'STR'
There are a variety of operations that can be performed on strings.
# two strings separated by a space are combined
"text " "string"
'text string'
# two strings can also be combined with `+`
"text " + "string"
'text string'
# strings can be multiplied with `*`
"yes! " * 2 + "yes!"
'yes! yes! yes!'
# strings can have the first letter capitalized
"needs To Be In Capitals".capitalize()
'Needs to be in capitals'
# strings can be centered in a field
"centered".center(20)
' centered '
# strings can be centered with a fill character
"centered".center(20, '-')
'------centered------'
# count the instances of a substring
"row, row, row your boat".count("row")
3
# count the instances of a substring starting at a character
"row, row, row your boat".count("row", 4)
2
# count the instances of a substring with starting and ending positions
"row, row, row your boat".count("row", 9, 14)
1
# find index of first string character
"motor".index('o')
1
# find index of first string character starting at a position
"motor".index('o', 2)
3
# find the position of a substring
"row, row, row your boat".find("row")
0
# find the position of a substring starting at a character
"row, row, row your boat".find("row", 4)
5
# find the position of a substring with starting and ending positions
"row, row, row your boat".find("row", 9, 14)
10
# join string characters separated by a fill character
"-".join('separated')
's-e-p-a-r-a-t-e-d'
# strings can be left justified in a field
"on the left".ljust(20)
'on the left '
# strings can be left justified with a fill character
"on the left".ljust(20, '-')
'on the left---------'
# strings can be converted to lowercase
"Needs To Be In Lowercase".lower()
'needs to be in lowercase'
# strings can have leading spaces removed
" indented".lstrip()
'indented'
# strings can have any of a set of leading characters removed
"http://www.google.com".lstrip('htp:/')
'www.google.com'
# strings can have a fixed leading string removed
"four score and seven years ago".removeprefix('four ')
'score and seven years ago'
# strings can have a fixed ending string removed
"four score and seven years ago".removesuffix(' ago')
'four score and seven years'
# string substrings can be replaced
"row, row, row your boat".replace('row', 'paddle')
'paddle, paddle, paddle your boat'
# a fixed number of string substrings can be replaced
"row, row, row your boat".replace('row', 'paddle', 2)
'paddle, paddle, row your boat'
# find the last position of a substring
"row, row, row your boat".rfind("row")
10
# find the last position of a substring starting at a character
"row, row, row your boat".find("row", 0)
0
# find the last position of a substring with starting and ending positions
"row, row, row your boat".find("row", 3, 10)
5
# strings can be right justified in a field
"on the right".rjust(20)
' on the right'
# strings can be right justified with a fill character
"on the right".rjust(20, '-')
'--------on the right'
# strings can have trailing spaces removed
"right padded ".rstrip()
'right padded'
# strings can have any of a set of leading characters removed
"python3.exe".rstrip('.ex')
'python3'
# strings can have leading and trailing spaces removed
" in the middle ".strip()
'in the middle'
# strings can have any of a set of leading and trailing characters removed
"www.google.com".strip('.wcom')
'google'
# strings can be converted to title case (each word capitalized)
"hello world".title()
'Hello World'
# strings can be converted to uppercase
"Needs To Be In Uppercase".upper()
'NEEDS TO BE IN UPPERCASE'
# use '+' for integer addition
17 + 25
42
# use '-' for integer subtraction
66 - 24
42
# use '*' for integer multiplication
2 * 21
42
# '//' performs integer division (integer result, remove any fraction)
128 // 3
42
# use '%' for integer modulo (remainder after division)
128 % 43
42
# integer absolute value
abs(-42)
42
# convert to integer
int("42")
42
# integer exponentiation
42 ** 2
1764
# convert an integer to a string
str(42)
'42'
# convert a string to an integer
int("42")
42
Floats¶
Floats are negative and positive real numbers with fractions. The result of any floating operation is always an float.
# use '+' for floating addition
17.0 + 25
42.0
# use '-' for floating subtraction
66.0 - 24
42.0
# use '*' for floating multiplication
2 * 21.0
42.0
# use '/' for floating division (returns a float even for integers)
126 / 3
42.0
# use '%' for modulo (remainder after division)
128.0 % 43
42.0
# float absolute value
abs(-42.0)
42.0
# convert to float
float("42.0")
42.0
# exponentiation (here, square root)
1764 ** 0.5
42.0
# convert a float to a string
str(42.0)
'42.0'
# convert a string to a float
float("42.0")
42.0
# round a float
round(4.7)
5
Booleans¶
Booleans are truth values, and always have one of two values True and False. There are a number of comparison operations that return booleans. They work for integer, float, as well as string types. For integers and floats, they work as expected. For strings, characters are compared from the left to find the result.
| Comparison | Meaning |
|---|---|
| < | less than |
| <= | less than or equal |
| > | greater than |
| >= | greater than or equal |
| == | equal |
| != | not equal |
# string less than
"abc" < "abc"
False
# string less than or equal
"abc" <= "abc"
True
# string greater than
"abc" > "abc"
False
# string greater than or equal
"abc" >= "abc"
True
# string equal
"abc" == "abc"
True
# string not equal
"abc" != "abc"
False
There are a number of string functions that return booleans.
# test a string ends with a substring
'Python'.endswith('thon')
True
# test a string ends with a substring, starting at a position
'Python'.endswith('thon', 3)
False
# test if all characters in the string are alphanumeric
'abc123'.isalnum()
True
# test if all characters in the string are decimal characters (0 to 9)
'0123456789'.isdecimal()
True
# test if all characters in the string are alphabetic
'abcdef'.isalpha()
True
# test if all characters in the string are spaces
' '.isspace()
True
# test if all characters in the string are uppercase
'BANANA'.isupper()
True
# test a string starts with a substring
'Python'.startswith('Pyth')
True
# test a string starts with a substring, starting at a position
'Python'.startswith('Pyth', 2)
False
# test if a substring is in a string
'ytho' in 'Python'
True
# test if a substring is not in a string
'ithon' not in 'Python'
True
Operator Precedence¶
The table below shows the precedence and associativity of arithmetic operators
| Operator | Description | Associativity |
|---|---|---|
| ** | exponentiation | right to left |
| %, *, /, // | modulo, multiplication, division and integer division | left to right |
| +, - | addition and subtraction | left to right |
| <, <=, >, >=, ==, != | comparisons | left to right |
Python Data Structures¶
Python data structs collect group data value. These include:
- Lists: an ordered sequence of any values
- Sets: a group of values with no duplicates
- Dictionaries: a group where values are associated with a key
Lists¶
Lists are dynamic sequences of values that can be accessed by position. Lists are formed with enclosing values of any type between square brackets []. List values are accessed by position counting from 0:
- list[0] is the first element
- list[1:3] are the first up to the third element (i.e., elements 1 and 2)
- list[-1] is the last element
and so on. The list ["text", 1, True, 4.2] can be visualized as:
| 0 | 1 | 2 | 3 | |
| "text" | 1 | True | 4.2 | |
| -4 | -3 | -2 | -1 |
There are a variety of operations that can be performed on lists.
# a sample list with mixed types
['text', 1, True, 4.2]
['text', 1, True, 4.2]
# length of a list
len(['a', 'b', 'c', 'b'])
4
# select a list value (counting from 0)
['a', 'b', 'c', 'd'][1]
'b'
# select a list slice of from 1 up to (not including) 3
['a', 'b', 'c', 'd'][1:3]
['b', 'c']
# select a list slice of from 1 to the end
['a', 'b', 'c', 'd'][1:]
['b', 'c', 'd']
# select a list slice up to (not including) 2
['a', 'b', 'c', 'd'][:2]
['a', 'b']
# select every other list value
print(['a', 'b', 'c', 'd'][0:3:2])
['a', 'c']
# select last list value
['a', 'b', 'c', 'd'][-1]
'd'
# find index of first list value
['a', 'b', 'c', 'd', 'b'].index('b')
1
# count instances of a list value
['a', 'b', 'c', 'd', 'b'].count('b')
2
# test if a value is in list
'b' in ['a', 'b', 'c', 'd']
True
# test if a value is not in list
'e' not in ['a', 'b', 'c', 'd']
True
# repeat a list
['a', 'b', 'c', 'd']*2
['a', 'b', 'c', 'd', 'a', 'b', 'c', 'd']
# combine two lists
['a', 'b', 'c', 'd'] + ['e', 'f', 'g', 'h']
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
# list minimum value
min([20.1, 1, 4.2, 15])
1
# list mazimum value
max([20.1, 1, 4.2, 15])
20.1
# sum of list values
sum([20.1, 1, 4.2, 15])
40.3
# sorted list
sorted([20.1, 1, 4.2, 15])
[1, 4.2, 15, 20.1]
# split a string at spaces into a list
'1 2 3'.split()
['1', '2', '3']
# split a string at a character into a list
'1,2,3'.split(',')
['1', '2', '3']
# join list strings separated by a fill character
"-".join(['one', 'two', 'three'])
'one-two-three'
Sets¶
Sets are an unordered group of values with no duplicates. Sets have rich set of operations as defined by set theory, of which a useful set are implemented in Python.
# set without duplicates
{'apple', 'orange', 'apple', 'pear', 'orange', 'banana'}
{'apple', 'banana', 'orange', 'pear'}
# count the number of values in the set
len({'apple', 'banana', 'orange', 'pear'})
4
# detecting if a value is in a set
'orange' in {'apple', 'banana', 'orange', 'pear'}
True
# detecting if a value is not in a set
'grape' not in {'apple', 'banana', 'orange', 'pear'}
True
# set intersection: fruit in both sets
{'apple', 'banana', 'orange', 'pear'} & {'grape', 'banana', 'peach', 'orange'}
{'banana', 'orange'}
# set union: fruit in either set
{'apple', 'banana', 'orange', 'pear'} | {'grape', 'banana', 'peach', 'orange'}
{'apple', 'banana', 'grape', 'orange', 'peach', 'pear'}
# set difference: fruit in one set but not in another
{'apple', 'banana', 'orange', 'pear'} - {'grape', 'banana', 'peach', 'orange'}
{'apple', 'pear'}
# fruit in one or the othere set but not both
{'apple', 'banana', 'orange', 'pear'} ^ {'grape', 'banana', 'peach', 'orange'}
{'apple', 'grape', 'peach', 'pear'}
# is one set a subset of another
{'banana', 'pear'} <= {'apple', 'banana', 'orange', 'pear'}
True
# is one set a superset of another
{'apple', 'banana', 'orange', 'pear'} >= {'banana', 'pear'}
True
Dictionaries¶
Dictionaries are a group where you look up a value with another value, a key, like looking up a word definition in a real dictionary. Dictionaries are formed with enclosing pairs of values of any type between braces {}. Dictionary values are accessed by key using square brackets.
The dictionary {'leaf': 'green', 'sky': 'blue', 'apple': 'red'} can be visualized as:
| key | value |
|---|---|
| leaf | green |
| sky | blue |
| apple | red |
# a sample dictionary
{'leaf': 'green', 'sky': 'blue', 'apple': 'red'}
{'leaf': 'green', 'sky': 'blue', 'apple': 'red'}
# select a dictionary value
{'leaf': 'green', 'sky': 'blue', 'apple': 'red'}['leaf']
'green'
# count the dictionary pairs
len({'leaf': 'green', 'sky': 'blue', 'apple': 'red'})
3
# detect if a key is in a dictionary
'leaf' in {'leaf': 'green', 'sky': 'blue', 'apple': 'red'}
True
# detect if a key is not in a dictionary
'cloud' in {'leaf': 'green', 'sky': 'blue', 'apple': 'red'}
False
# list all the keys in a dictionary. The result of keys() is a special data structure, it needs to be converted to a list.
list({'leaf': 'green', 'sky': 'blue', 'apple': 'red'}.keys())
['leaf', 'sky', 'apple']
# list all the values in a dictionary. The result of values() is a special data structure, it needs to be converted to a list.
list({'leaf': 'green', 'sky': 'blue', 'apple': 'red'}.values())
['green', 'blue', 'red']
Python RegEx¶
A RegEx, or Regular Expression, is a sequence of characters that forms a search pattern.
RegEx can be used to check if a string contains the specified search pattern.
RegEx Module¶
Python has a built-in package called re, which can be used to work with Regular Expressions.
To use it, we import the re module.
import re
RegEx Functions¶
The re module offers a set of functions that allows us to search a string for a match:
| Function | Description |
|---|---|
| findall | Returns a list containing all matches |
| search | Returns a Match object if there is a match anywhere in the string |
| split | Returns a list where the string has been split at each match |
| sub | Replaces one or many matches with a string |
Metacharacters¶
Metacharacters are characters with a special meaning:
| Character | Description | Example |
|---|---|---|
| [] | A set of characters | "[a-m]" |
| \ | Signals a special sequence (can also be used to escape special characters) | "\d" |
| . | Any character | "he..o" |
| ^ | Starts with | "^hello" |
| \$ | Ends with | "planet$" |
| * | Zero or more occurrences | "he.*o" |
| + | One or more occurrences | "he.+o" |
| ? | Zero or one occurrences | "he.?o" |
| {*m*} | Exactly *m* occurrences | "he.{2}o" |
| {*m*,*n*} | Between *m* and *n* occurrences | "he.{2,3}o" |
| \ | Either or | "falls|stays" |
| () | Capture and group |
Special Sequences¶
A special sequence is a \ followed by one of the characters in the list below, and has a special meaning:
| Character | Description |
|---|---|
| \A | Returns a match if the specified characters are at the beginning of the string |
| \b | Returns a match where the specified characters are at the beginning or at the end of a word |
| \B | Returns a match where the specified characters are present, but NOT at the beginning (or at the end) of a word |
| \d | Returns a match where the string contains digits (numbers from 0-9) |
| \D | Returns a match where the string DOES NOT contain digits |
| \s | Returns a match where the string contains a white space character |
| \S | Returns a match where the string DOES NOT contain a white space character |
| \w | Returns a match where the string contains any word characters (characters from a to Z, digits from 0-9, and the underscore _ character) |
| \W | Returns a match where the string DOES NOT contain any word characters |
| \Z | Returns a match if the specified characters are at the end of the string |
Sets¶
A set is a set of characters inside a pair of square brackets [] with a special meaning:
| Set | Description |
|---|---|
| [arn] | Returns a match where one of the specified characters (a, r, or n) is present |
| [a-n] | Returns a match for any lower case character, alphabetically between a and n |
| [^arn] | Returns a match for any character EXCEPT a, r, and n |
| [0123] | Returns a match where any of the specified digits (0, 1, 2, or 3) are present |
| [0-9] | Returns a match for any digit between 0 and 9 |
| [0-5][0-9] | Returns a match for any two-digit numbers from 00 and 59 |
| [a-zA-Z] | Returns a match for any character alphabetically between a and z, lower case OR upper case |
| [+] | In sets, +, *****, ., |, (), $, {} has no special meaning, so [+] means: return a match for any + character in the string |
The findall() Function¶
The `findall() function returns a list containing all matches.
# Print a list of all matches in the order they are found
import re
txt = "The rain in Spain"
re.findall("ai", txt)
['ai', 'ai']
# Return an empty list if no match was found
import re
txt = "The rain in Spain"
x = re.findall("Portugal", txt)
print(x)
[]
The search() Function¶
The search() function searches the string for a match.
- If there is more than one match, only the first occurrence of the match will be returned.
# Search for the first white-space character in the string
import re
txt = "The rain in Spain"
re.search("\s", txt).start()
3
The split() Function¶
The split() function returns a list where the string has been split at each match.
# Split at each white-space character
import re
txt = "The rain in Spain"
re.split("\s", txt)
['The', 'rain', 'in', 'Spain']
You can control the number of occurrences by specifying the maxsplit parameter.
# Split the string only at the first occurrence
import re
txt = "The rain in Spain"
re.split("\s", txt, maxsplit=1)
['The', 'rain in Spain']
The sub() Function¶
The sub() function replaces the matches with the text of your choice.
# Replace every white-space character with the number 9
import re
txt = "The rain in Spain"
re.sub("\s", "-", txt)
'The-rain-in-Spain'
You can control the number of replacements by specifying the count parameter.
# Replace the first 2 occurrences
import re
txt = "The rain in Spain"
re.sub("\s", "-", txt, count=2)
'The-rain-in Spain'
Matching with groups¶
Text matching a pattern can be captured into a group by putting the match pattern between parentheses ().
The text matching each group can be accessed separately.
# match each field of a date
import re
re.search("(\d{2})/(\d{2})/(\d{4})", "07/04/2022").groups()
('07', '04', '2022')
# match each field of an email address
import re
re.search("(\w+)@(\w+)", "monty_python@gmail.com").groups()
('monty_python', 'gmail')
# match each field of a web address
import re
re.search("https://www.(\w+)\.(\w+)", "https://www.google.com").groups()
('google', 'com')
# match each field of an address
import re
re.search("(\w+), (\d+ .+), (.*), (\w{2}) (\d+)", "Googleplex, 1600 Amphitheatre Pkwy, Mountain View, CA 94043").groups()
('Googleplex', '1600 Amphitheatre Pkwy', 'Mountain View', 'CA', '94043')
Placeholders¶
A dataset is the collection of the data being processed in a data analytics session. The current values for all that data at any point in time is a data state. The initial data state are all the available raw data. This data is modified step by step as it is cleaned, analyzed, transformed, and visualized. Placeholders are needed to hold the data from step to step. When all steps are completed, the data is no longer needed and is released. Any data needed for later must be saved before it is removed.
The placeholders are called variables because the data they hold may be changed from step to step. This is distinct from mathematical variables, whose value never changes once it is associated with them. The processing proceeds through a series of data states which are the values contained by all existing variables.
Programs¶
A program automates the processing of data through data states, from the initial raw data until the last data state is handled and the session is over. A program is a recipe and consists of a series of steps where some data is needed for each step, the data is modified, and the changed data results in a new data state. A recipe for making bread gives an example.
- make the dough
- input: flour, water, and yeast
- processing: mix and let rise
- output: raw dough
- knead the dough
- input: raw dough
- processing: roll, press, and fold the dough
- output: smooth dough
- bake the dough:
- input: smooth dough
- processing: heat in the over
- output: bread
The initial data state is (flour, water, yeast) and the final data state is (bread). In Python the data state is stored as the types of data and data structures above. Each data item is assigned to a variable that holds the data through the next steps, until that data is no longer needed and the variable and data are released (called garbage collection). A variable's data may be changed or combined with other variables' data and assigned to a new variable. The data state is the set of values assigned to all variables at a given point in time.
Program Statements¶
A program statement is responsible for each step of the processing that changes the data state. Program statements may:
- assign data to a variable
- modify the data associated with a variable
- pass data to a function that uses the data to cause some external effect such as:
- saving the data
- creating a report from the data
- visualizing the data
- giving the data to a machine learning algorithm to predict future effects
Each statement modifies the data state. The progression of a program as it is run is:
(initial data state)
program statement 1
(data state 1)
program statement 2
(data state 2)
...
last program statement
(last data state)
(all data is released and the program ends)
Writing Python programs¶
Program statements, for our purposes, are written in Python and first some discussion of writing Python programs is required. Python programs are plain text files. They are created in plain text editors such as:
- Notepad++ (Windows)
- TextEdit (MacOS)
- Sublime (Windows and MacOS)
- Visual Studio Code (Windows and MacOS)
- Applications, such as PyCharm and Visual Studio Code, and websites such as Google Colab and w3schools, have built in text editors The files names usually end with “.py”.
Python program structure¶
Comments are included to give text descriptions of what the code is doing in two ways:
- Text following "#" on one line is ignored through the end of the line
- Text between two single lines containing three double-quotes (“””) is ignored:
“””
This is an ignored comment about the following program code.
“””
Long program lines can be continued on the next line, they are usually indented using tabs or spaces
# A program line too long to fit on a page
foo = long_function_name(var_one, var_two,
var_three, var_four)
Occasionally a group of statements must be processed together under some condition, called a block. These lines have exactly equal indentation (any size) to indicate processing them together:
# two lines run together if a condition is true
if (x < 3 and y == 4):
z = x + y
print(x, y, z)
Blank lines can be added anywhere between program lines to make it more readable. At the beginning of files, programs usually indicate any standard libraries of code they use with “import”
import os
import sys
# rest of the program
Python program statements¶
Programs consist of a series of lines containing statements that are processed from the beginning of the file to the end. When all lines of the program have been processed, all values in the data state are released. All data needs to have been processed by that point, and once it is released the computer is available to run another program. These are various types of statements available.
Variable assignment statements¶
The most basic program statement assigns a value to a variable with "=". Variables are assigned with expressions. An expression is a combination of programming language variables, constants, operators, and functions that produce a single value. Expressions follow the rules of standard mathematical statements. Any place a variable or function appears in an expression, its value is substituted. You have seen many examples of Python expressions above. Each are in a code block where the expression is printed after it is evaluated.
In the following, code blocks will now contain a series of Python statements. The statements are processed one at a time, and the expression listed on the last line is printed.
p = 42
p
42
The data state at this point is:
- (p, value 42) Any variable then can be used and have its value substituted in an expression in any following statement.
width = 2
# data state: (width, value 2)
height = 21
# data state: (width, value 2; height, value 21)
area = width * height
# data state: (width, value 2; height, value 21; area, value 42)
area
42
A variable initially is empty. It must be assigned a value before it is used, else it gives an error. This commonly occurs when a variable name is misspelled.
Statements that modify the value of variables¶
Once variables are assigned, other statements can modify the values of variables. After assigning a variable in a statement, the variable can be assigned a new value in a later statement.
width = 2
# data state: (width, value 2)
height = 21
# data state: (width, value 2; height, value 21)
area = width * height
# data state: (width, value 2; height, value 21; area, value 42)
width = 4
# data state: (width, value 4; height, value 21; area, value 42)
area = width * height
# data state: (width, value 4; height, value 21; area, value 84)
area
84
Statements that modify lists¶
The following are operations that modify lists assigned to variables
# add a value to a list
a_list = ["string", 1, 4.2]
a_list.append(12)
a_list
['string', 1, 4.2, 12]
# add a list to the end of a list
a_list = ["string", 1, 4.2]
a_list.extend([12, "end"])
a_list
['string', 1, 4.2, 12, 'end']
# remove a value from a list
a_list = ["string", 1, 4.2]
a_list.remove(4.2)
a_list
['string', 1]
# add a value at a specific position
a_list = ["string", 1, 4.2]
a_list.insert(0, 1)
a_list
[1, 'string', 1, 4.2]
# delete a value at a specific position
a_list = ["string", 1, 4.2]
del a_list[1]
a_list
['string', 4.2]
# reverse a list
a_list = ["string", 1, 4.2]
a_list.reverse()
a_list
[4.2, 1, 'string']
# sort a list
a_list = [3, 12, 4.5, 10]
a_list.sort()
a_list
[3, 4.5, 10, 12]
Statements that modify sets¶
The following are operations that modify sets assigned to variables
# add a value to set
a_set = {'apple', 'banana', 'orange', 'pear'}
a_set.add('grape')
a_set
{'apple', 'banana', 'grape', 'orange', 'pear'}
# remove a value from set
a_set = {'apple', 'banana', 'orange', 'pear'}
a_set.remove('orange')
a_set
{'apple', 'banana', 'pear'}
# remove all values from a set
a_set = {'apple', 'banana', 'orange', 'pear'}
a_set.clear()
a_set
set()
Statements that modify dictionaries¶
The following are operations that modify dictionaries assigned to variables
# change a values in a dictionary
a_dict = {'leaf': 'green', 'sky': 'blue', 'apple': 'red'}
a_dict["apple"] = "yellow"
a_dict
{'leaf': 'green', 'sky': 'blue', 'apple': 'yellow'}
# add a value to a dictionary
a_dict = {'leaf': 'green', 'sky': 'blue', 'apple': 'red'}
a_dict["earth"] = "brown"
a_dict
{'leaf': 'green', 'sky': 'blue', 'apple': 'red', 'earth': 'brown'}
# remove a value from a dictionary
a_dict = {'leaf': 'green', 'sky': 'blue', 'apple': 'red'}
del a_dict["sky"]
a_dict
{'leaf': 'green', 'apple': 'red'}
# combine two dictionaries
a_dict = {'leaf': 'green', 'sky': 'blue', 'apple': 'red'}
another_dict = {"apple": "yellow", "sunset": "orange"}
a_dict.update(another_dict)
a_dict
{'leaf': 'green', 'sky': 'blue', 'apple': 'yellow', 'sunset': 'orange'}
# remove all pairs in a dictionary
a_dict = {'leaf': 'green', 'sky': 'blue', 'apple': 'red'}
a_dict.clear()
a_dict
{}
Statements that control the flow of processing statements¶
Once variables are assigned, other statements can modify the values of variables. After assigning a variable in a statement, the variable can be assigned a new value in a later statement.
if Statements¶
Perhaps the most well-known statement type is the if statement. For example:
x = 42
if x < 0:
x = 0
print('Negative changed to zero')
elif x == 0:
print('Zero')
elif x == 1:
print('Single')
else:
print('More')
More
There can be zero or more elif parts, and the else part is optional. The keyword ‘elif’ is short for ‘else if’, and is useful to avoid excessive indentation. An if … elif … elif … sequence is a substitute for the switch or case statements found in other languages.
for Statements¶
The for statement in Python differs a bit from what you may be used to in C or Pascal. Rather than always iterating over an arithmetic progression of numbers (like in Pascal), or giving the user the ability to define both the iteration step and halting condition (as C), Python’s for statement iterates over the items of any sequence (a list or a string), in the order that they appear in the sequence. For example (no pun intended):
# Measure some strings:
words = ['cat', 'window', 'defenestrate']
for w in words:
print(w, len(w))
cat 3 window 6 defenestrate 12
Code that modifies a collection while iterating over that same collection can be tricky to get right. Instead, it is usually more straight-forward to loop over a copy of the collection or to create a new collection:
# Create a sample collection
users = {'Hans': 'active', 'Éléonore': 'inactive', '景太郎': 'active'}
# Strategy: Iterate over a copy
for user, status in users.copy().items():
if status == 'inactive':
del users[user]
# Strategy: Create a new collection
active_users = {}
for user, status in users.items():
if status == 'active':
active_users[user] = status
The range() Function¶
If you do need to iterate over a sequence of numbers, the built-in function range() comes in handy. It generates arithmetic progressions:
for i in range(5):
print(i)
0 1 2 3 4
The given end point is never part of the generated sequence; range(10) generates 10 values, the legal indices for items of a sequence of length 10. It is possible to let the range start at another number, or to specify a different increment (even negative; sometimes this is called the ‘step’):
print(list(range(5, 10)))
print(list(range(0, 10, 3)))
print(list(range(-10, -100, -30)))
[5, 6, 7, 8, 9] [0, 3, 6, 9] [-10, -40, -70]
To iterate over the indices of a sequence, you can combine range() and len() as follows:
a = ['Mary', 'had', 'a', 'little', 'lamb']
for i in range(len(a)):
print(i, a[i])
0 Mary 1 had 2 a 3 little 4 lamb
break and continue Statements¶
The break statement breaks out of the innermost enclosing for or while loop:
for n in range(2, 10):
for x in range(2, n):
if n % x == 0:
print(f"{n} equals {x} * {n//x}")
break
4 equals 2 * 2 6 equals 2 * 3 8 equals 2 * 4 9 equals 3 * 3
The continue statement continues with the next iteration of the loop:
for num in range(2, 10):
if num % 2 == 0:
print(f"Found an even number {num}")
continue
print(f"Found an odd number {num}")
Found an even number 2 Found an odd number 3 Found an even number 4 Found an odd number 5 Found an even number 6 Found an odd number 7 Found an even number 8 Found an odd number 9
else Clauses on Loops¶
In a for or while loop the break statement may be paired with an else clause. If the loop finishes without executing the break, the else clause executes.
In a for loop, the else clause is executed after the loop finishes its final iteration, that is, if no break occurred.
In a while loop, it’s executed after the loop’s condition becomes false.
In either kind of loop, the else clause is not executed if the loop was terminated by a break. Of course, other ways of ending the loop early, such as a return or a raised exception, will also skip execution of the else clause.
This is exemplified in the following for loop, which searches for prime numbers:
for n in range(2, 10):
for x in range(2, n):
if n % x == 0:
print(n, 'equals', x, '*', n//x)
break
else:
# loop fell through without finding a factor
print(n, 'is a prime number')
2 is a prime number 3 is a prime number 4 equals 2 * 2 5 is a prime number 6 equals 2 * 3 7 is a prime number 8 equals 2 * 4 9 equals 3 * 3
(Yes, this is the correct code. Look closely: the else clause belongs to the for loop, not the if statement.)
One way to think of the else clause is to imagine it paired with the if inside the loop. As the loop executes, it will run a sequence like if/if/if/else. The if is inside the loop, encountered a number of times. If the condition is ever true, a break will happen. If the condition is never true, the else clause outside the loop will execute.
When used with a loop, the else clause has more in common with the else clause of a try statement than it does with that of if statements: a try statement’s else clause runs when no exception occurs, and a loop’s else clause runs when no break occurs. For more on the try statement and exceptions, see Handling Exceptions.
match Statements¶
A match statement takes an expression and compares its value to successive patterns given as one or more case blocks. This is superficially similar to a switch statement in C, Java or JavaScript (and many other languages), but it’s more similar to pattern matching in languages like Rust or Haskell. Only the first pattern that matches gets executed and it can also extract components (sequence elements or object attributes) from the value into variables. If no case matches, none of the branches is executed.
The simplest form compares a subject value against one or more literals:
def http_error(status):
match status:
case 400:
return "Bad request"
case 404:
return "Not found"
case 418:
return "I'm a teapot"
case _:
return "Something's wrong with the internet"
Note the last block: the “variable name” _ acts as a wildcard and never fails to match.
You can combine several literals in a single pattern using | (“or”):
Study that one carefully! The first pattern has two literals, and can be thought of as an extension of the literal pattern shown above. But the next two patterns combine a literal and a variable, and the variable binds a value from the subject (point). The fourth pattern captures two values, which makes it conceptually similar to the unpacking assignment (x, y) = point.
Defining Functions¶
We can create a function that writes the Fibonacci series to an arbitrary boundary:
def fib(n): # write Fibonacci series less than n
"""Print a Fibonacci series less than n."""
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a+b
print()
# Now call the function we just defined:
fib(2000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
The keyword def introduces a function definition. It must be followed by the function name and the parenthesized list of formal parameters. The statements that form the body of the function start at the next line, and must be indented.
The first statement of the function body can optionally be a string literal; this string literal is the function’s documentation string, or docstring. (More about docstrings can be found in the section Documentation Strings.) There are tools which use docstrings to automatically produce online or printed documentation, or to let the user interactively browse through code; it’s good practice to include docstrings in code that you write, so make a habit of it.
The execution of a function introduces a new symbol table used for the local variables of the function. More precisely, all variable assignments in a function store the value in the local symbol table; whereas variable references first look in the local symbol table, then in the local symbol tables of enclosing functions, then in the global symbol table, and finally in the table of built-in names. Thus, global variables and variables of enclosing functions cannot be directly assigned a value within a function (unless, for global variables, named in a global statement, or, for variables of enclosing functions, named in a nonlocal statement), although they may be referenced.
The actual parameters (arguments) to a function call are introduced in the local symbol table of the called function when it is called; thus, arguments are passed using call by value (where the value is always an object reference, not the value of the object). [1] When a function calls another function, or calls itself recursively, a new local symbol table is created for that call.
A function definition associates the function name with the function object in the current symbol table. The interpreter recognizes the object pointed to by that name as a user-defined function. Other names can also point to that same function object and can also be used to access the function:
fib
f = fib
f(100)
0 1 1 2 3 5 8 13 21 34 55 89
Coming from other languages, you might object that fib is not a function but a procedure since it doesn’t return a value. In fact, even functions without a return statement do return a value, albeit a rather boring one. This value is called None (it’s a built-in name). Writing the value None is normally suppressed by the interpreter if it would be the only value written. You can see it if you really want to using print():
fib(0)
print(fib(0))
None
It is simple to write a function that returns a list of the numbers of the Fibonacci series, instead of printing it:
def fib2(n): # return Fibonacci series up to n
"""Return a list containing the Fibonacci series up to n."""
result = []
a, b = 0, 1
while a < n:
result.append(a) # see below
a, b = b, a+b
return result
f100 = fib2(100) # call it
f100
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
This example, as usual, demonstrates some new Python features:
The return statement returns with a value from a function. return without an expression argument returns None. Falling off the end of a function also returns None.
The statement result.append(a) calls a method of the list object result. A method is a function that ‘belongs’ to an object and is named obj.methodname, where obj is some object (this may be an expression), and methodname is the name of a method that is defined by the object’s type. Different types define different methods. Methods of different types may have the same name without causing ambiguity. (It is possible to define your own object types and methods, using classes, see Classes) The method append() shown in the example is defined for list objects; it adds a new element at the end of the list. In this example it is equivalent to result = result + [a], but more efficient.
More on Defining Functions¶
It is also possible to define functions with a variable number of arguments. There are three forms, which can be combined.
Default Argument Values¶
The most useful form is to specify a default value for one or more arguments. This creates a function that can be called with fewer arguments than it is defined to allow. For example:
def ask_ok(prompt, retries=4, reminder='Please try again!'):
while True:
reply = input(prompt)
if reply in {'y', 'ye', 'yes'}:
return True
if reply in {'n', 'no', 'nop', 'nope'}:
return False
retries = retries - 1
if retries < 0:
raise ValueError('invalid user response')
print(reminder)
This function can be called in several ways:
giving only the mandatory argument: ask_ok('Do you really want to quit?')
giving one of the optional arguments: ask_ok('OK to overwrite the file?', 2)
or even giving all arguments: ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')
This example also introduces the in keyword. This tests whether or not a sequence contains a certain value.
The default values are evaluated at the point of function definition in the defining scope, so that
i = 5
def f(arg=i):
print(arg)
i = 6
f()
5
Keyword Arguments¶
Functions can also be called using keyword arguments of the form kwarg=value. For instance, the following function:
def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
print("-- This parrot wouldn't", action, end=' ')
print("if you put", voltage, "volts through it.")
print("-- Lovely plumage, the", type)
print("-- It's", state, "!")
accepts one required argument (voltage) and three optional arguments (state, action, and type). This function can be called in any of the following ways:
parrot(1000) # 1 positional argument
parrot(voltage=1000) # 1 keyword argument
parrot(voltage=1000000, action='VOOOOOM') # 2 keyword arguments
parrot(action='VOOOOOM', voltage=1000000) # 2 keyword arguments
parrot('a million', 'bereft of life', 'jump') # 3 positional arguments
parrot('a thousand', state='pushing up the daisies') # 1 positional, 1 keyword
-- This parrot wouldn't voom if you put 1000 volts through it. -- Lovely plumage, the Norwegian Blue -- It's a stiff ! -- This parrot wouldn't voom if you put 1000 volts through it. -- Lovely plumage, the Norwegian Blue -- It's a stiff ! -- This parrot wouldn't VOOOOOM if you put 1000000 volts through it. -- Lovely plumage, the Norwegian Blue -- It's a stiff ! -- This parrot wouldn't VOOOOOM if you put 1000000 volts through it. -- Lovely plumage, the Norwegian Blue -- It's a stiff ! -- This parrot wouldn't jump if you put a million volts through it. -- Lovely plumage, the Norwegian Blue -- It's bereft of life ! -- This parrot wouldn't voom if you put a thousand volts through it. -- Lovely plumage, the Norwegian Blue -- It's pushing up the daisies !
but all the following calls would be invalid:
parrot() # required argument missing
parrot(voltage=5.0, 'dead') # non-keyword argument after a keyword argument
parrot(110, voltage=220) # duplicate value for the same argument
parrot(actor='John Cleese') # unknown keyword argument
In a function call, keyword arguments must follow positional arguments. All the keyword arguments passed must match one of the arguments accepted by the function (e.g. actor is not a valid argument for the parrot function), and their order is not important. This also includes non-optional arguments (e.g. parrot(voltage=1000) is valid too). No argument may receive a value more than once. Here’s an example that fails due to this restriction:
def function(a):
pass
#function(0, a=0)
When a final formal parameter of the form **name is present, it receives a dictionary (see Mapping Types — dict) containing all keyword arguments except for those corresponding to a formal parameter. This may be combined with a formal parameter of the form name (described in the next subsection) which receives a tuple containing the positional arguments beyond the formal parameter list. (name must occur before **name.) For example, if we define a function like this:
def cheeseshop(kind, *arguments, **keywords):
print("-- Do you have any", kind, "?")
print("-- I'm sorry, we're all out of", kind)
for arg in arguments:
print(arg)
print("-" * 40)
for kw in keywords:
print(kw, ":", keywords[kw])
It could be called like this:
cheeseshop("Limburger", "It's very runny, sir.",
"It's really very, VERY runny, sir.",
shopkeeper="Michael Palin",
client="John Cleese",
sketch="Cheese Shop Sketch")
-- Do you have any Limburger ? -- I'm sorry, we're all out of Limburger It's very runny, sir. It's really very, VERY runny, sir. ---------------------------------------- shopkeeper : Michael Palin client : John Cleese sketch : Cheese Shop Sketch
Generators¶
There are statements that combine loops and generate data structures.
List Comprehensions¶
List comprehensions provide a concise way to create lists. Common applications are to make new lists where each element is the result of some operations applied to each member of another sequence or iterable, or to create a subsequence of those elements that satisfy a certain condition.
For example, assume we want to create a list of squares, like:
squares = []
for x in range(10):
squares.append(x**2)
squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Note that this creates (or overwrites) a variable named x that still exists after the loop completes. We can calculate the list of squares without any side effects using:
squares = [x**2 for x in range(10)]
A list comprehension consists of brackets containing an expression followed by a for clause, then zero or more for or if clauses. The result will be a new list resulting from evaluating the expression in the context of the for and if clauses which follow it. For example, this listcomp combines the elements of two lists if they are not equal:
[(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
and it’s equivalent to:
combs = []
for x in [1,2,3]:
for y in [3,1,4]:
if x != y:
combs.append((x, y))
combs
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
Note how the order of the for and if statements is the same in both these snippets.
If the expression is a tuple (e.g. the (x, y) in the previous example), it must be parenthesized.
vec = [-4, -2, 0, 2, 4]
# create a new list with the values doubled
print([x*2 for x in vec])
# filter the list to exclude negative numbers
print([x for x in vec if x >= 0])
# apply a function to all the elements
print([abs(x) for x in vec])
# call a method on each element
freshfruit = [' banana', ' loganberry ', 'passion fruit ']
print([weapon.strip() for weapon in freshfruit])
# create a list of 2-tuples like (number, square)
print([(x, x**2) for x in range(6)])
# the tuple must be parenthesized, otherwise an error is raised
"""
[x, x**2 for x in range(6)]
File "<stdin>", line 1
[x, x**2 for x in range(6)]
^^^^^^^
SyntaxError: did you forget parentheses around the comprehension target?
"""
# flatten a list using a listcomp with two 'for'
vec = [[1,2,3], [4,5,6], [7,8,9]]
[num for elem in vec for num in elem]
[-8, -4, 0, 4, 8] [0, 2, 4] [4, 2, 0, 2, 4] ['banana', 'loganberry', 'passion fruit'] [(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25)]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
List comprehensions can contain complex expressions and nested functions:
from math import pi
[str(round(pi, i)) for i in range(1, 6)]
['3.1', '3.14', '3.142', '3.1416', '3.14159']
Nested List Comprehensions¶
The initial expression in a list comprehension can be any arbitrary expression, including another list comprehension.
Consider the following example of a 3x4 matrix implemented as a list of 3 lists of length 4:
matrix = [
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
]
The following list comprehension will transpose rows and columns:
[[row[i] for row in matrix] for i in range(4)]
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
As we saw in the previous section, the inner list comprehension is evaluated in the context of the for that follows it, so this example is equivalent to:
transposed = []
for i in range(4):
transposed.append([row[i] for row in matrix])
transposed
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
which, in turn, is the same as:
transposed = []
for i in range(4):
# the following 3 lines implement the nested listcomp
transposed_row = []
for row in matrix:
transposed_row.append(row[i])
transposed.append(transposed_row)
transposed
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
In the real world, you should prefer built-in functions to complex flow statements. The zip() function would do a great job for this use case:
list(zip(*matrix))
[(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]
print()¶
print(*objects, sep=' ', end='\n', file=None, flush=False) Print objects to the text stream file, separated by sep and followed by end. sep, end, file, and flush, if present, must be given as keyword arguments.
All non-keyword arguments are converted to strings like str() does and written to the stream, separated by sep and followed by end. Both sep and end must be strings; they can also be None, which means to use the default values. If no objects are given, print() will just write end.
The file argument must be an object with a write(string) method; if it is not present or None, sys.stdout will be used. Since printed arguments are converted to text strings, print() cannot be used with binary mode file objects. For these, use file.write(...) instead.
Output buffering is usually determined by file. However, if flush is true, the stream is forcibly flushed.