Introduction to Programming with Python#
A Tufts University Data Lab Workshop
Written by Uku-Kaspar Uustalu
Website: go.tufts.edu/introPython
Contact: datalab-support@elist.tufts.edu
Resources: go.tufts.edu/python
Last updated: 2022-10-25
Introduction to Python Notebooks#
Python Notebooks are made up of cells. There are three different kinds of cells:
Code cells for Python code.
Markdown cells for text, images, LaTeX equations, etc.
Raw cells for additional non-Python code that modifies the notebook when exporting it to a different format.
Each cell can be run (and re-run) independently.
To run a cell, select it by clicking on it and then press Shift+Enter or Ctrl+Enter.
After running a cell, the following cell gets automatically selected.
This is a Markdown cell. Markdown is a lightweight language for formatting text.
To see the code behind a Markdown cell, double-click on it.
To render the Markdown code back into formatted text, you must run the cell.
Here are some useful Markdown references:
# this is a code cell
# select this cell and press Shift+Enter or Ctrl+Enter to run it
# in Python, the hashtag (or pound) symbol denotes a comment line
# anything on a comment line is intended for humans and ignored by Python
# adding comments to your code is very important for debugging and sharing
print("Hello World!")
Hello World!
↑↑↑
The output of code cells appears right here in the notebook just below the corresponding cell.
Output is also stored in the notebook. Any output of a saved notebook will still be there after you close it and open it up again.
If you run a cell again, the output will be overwritten. To clear outputs, go to Edit > Clear All Outputs.
To learn more about Python Notebooks:
Understanding how Outputs Work#
The output from the last line in a block usually gets printed out (even without a print statement).
spam = 500
spam * 2
1000
But if the output from the last line in a block gets diverted elsewhere (like into a variable), no output will be displayed.
new_spam = spam * 2
Referring to a variable will output its contents, but only if it happens on the last line of a block.
# the contents of spam get outputted
spam
500
# only the contents of new_spam get outputted
spam
new_spam
1000
# nothing gets outputted
new_spam
new_spam = new_spam * 2 // 3 # remember, this is floor division
Knowing this behavior makes it easy to see the contents of variables and the results of expressions. Just make sure they are on the last line in a block, or run them in their own block. However, if you want to be completely sure that something gets outputted in the notebook, it is wise to rely on the good old print()
function.
print("now we have", new_spam, "cans of spam")
print("previously we had", spam, "cans of spam")
now we have 666 cans of spam
previously we had 500 cans of spam
Hands-On Exercise#
Remember this from before? Run this cell, then change the value of the variables name
and age
and run the cell again.
Note how the previous output gets overwritten.
name = 'Uku' # remember, in Python you can use either ' or " to denote strings
age = 100
print("My name is", name, "and I'm", age)
My name is Uku and I'm 100
Lists#
A list is a mutable collection/sequence of values that preserves order
Similar to arrays or vectors in other languages, but more flexible and forgiving
Values can be added to or removed from a list and the entire list can be reordered
A list can contain many different data types and even objects (other lists or data structures)
You use square brackets,
[ ]
, to denote a list in PythonIndividual elements are separated by commas
# example of a list
# these might or might not be my exam scores from freshman year
scores = [94, 87, 85, 89, 72, 98, 96, 82, 92]
scores
[94, 87, 85, 89, 72, 98, 96, 82, 92]
If you are ever unsure what kind of data is stored in a variable, you can use type()
.
It is also a useful debugging tool.
type(scores)
list
We can access elements of a list using list[index]
. This provides both read and write functionality.
We can use it to read elements from a list but we can also use it with the assignment operator =
to replace/overwrite elements.
However, despite the flexibility of python, you cannot use this method to add new elements to a list.
Using list[index]
with a non-existing index will result in an error.
Remember that Python uses zero-based indexing!
# let's take a look at the first element
scores[0]
94
# let's say we got to retake the third exam and we improved our score by 6 points
scores[2] = scores[2] + 6
scores
[94, 87, 91, 89, 72, 98, 96, 82, 92]
Note how the expression to the right of the assignment operator =
always gets evaluated before the expression to the left of the assignment operator. That allows us to easily get and overwrite the values of variables or list elements using a simple one-line statement.
To check if a list contains an element, we can simply ask in English.
print(94 in scores)
print(93 in scores)
print(93 not in scores)
True
False
True
Python has a lot of built-in functions and methods for working with lists. Check out the documentation for an overview.
Built-in functions: https://docs.python.org/3/library/functions.html
List methods: https://docs.python.org/3/tutorial/datastructures.html
For example, we can use the .append()
method to add elements to a list.
# I just took another test and got a 70 :(
# let's add it to the list anyways
scores.append(70)
# now take a look at `scores` again
scores
[94, 87, 91, 89, 72, 98, 96, 82, 92, 70]
The len()
function gives us the number of elements in a list (or any other iterable).
len(scores)
10
To look at a range of elements, we can use slicing: list[start:end]
# let's look at the fist three elements
scores[0:3]
[94, 87, 91]
To slice from the beginning of the list or until the end of the list, we can just omit the respective index.
Note that you can also use negative indices to count from the end of the list.
# we can omit the zero to get the first three elements
scores[:3]
[94, 87, 91]
# let's look at the last element using negative indexing
scores[-1]
70
# using the same logic, we can also easily look at the last three elements
scores[-3:]
[82, 92, 70]
Slicing actually also has an optional third argument: list[start:end:step]
When we do not specify step
, it defaults to one.
You can still omit the start
or end
index as before, even when using step
.
# let's look at the second, fourth, and sixth element
# remember that the first element is at index zero
scores[1:6:2]
[87, 89, 98]
# what about the first, third, and fifth?
scores[:5:2]
[94, 91, 72]
# omit every second element in the whole list
scores[::2]
[94, 91, 72, 96, 92]
# only look at every second element in the whole list
scores[1::2]
[87, 89, 98, 82, 70]
But what if we want to see the three highest scores?
Let’s try the built-in function sorted()
.
sorted(scores)
[70, 72, 82, 87, 89, 91, 92, 94, 96, 98]
Is there any way to change the order from ascending to descending?
We can use help(sorted)
to find out.
help(sorted)
Help on built-in function sorted in module builtins:
sorted(iterable, /, *, key=None, reverse=False)
Return a new list containing all items from the iterable in ascending order.
A custom key function can be supplied to customize the sort order, and the
reverse flag can be set to request the result in descending order.
Or we could take a look at the documentation online: https://docs.python.org/3/library/functions.html#sorted
sorted(scores, reverse=True)
[98, 96, 94, 92, 91, 89, 87, 82, 72, 70]
# now we can extract the three highest scores
sorted(scores, reverse=True)[0:3]
[98, 96, 94]
Functions and methods#
A quick search on StackOverflow reveals that you can also use the .sort()
method to sort a list.
But what is the difference?
sorted(scores) # this is a function
[70, 72, 82, 87, 89, 91, 92, 94, 96, 98]
scores # the function does not modify the original list, it outputs a copy
[94, 87, 91, 89, 72, 98, 96, 82, 92, 70]
scores.sort() # this is a method, it does not output anything
scores # instead, it modifies the original list
[70, 72, 82, 87, 89, 91, 92, 94, 96, 98]
Strings as Lists#
You can use slicing and indexing to extract letters from a string, just like you would from a list.
food = 'egg and spam'
# we can select single letters (remember that the first letter is at index 0)
print(food[0])
print(food[1])
print(food[2])
e
g
g
# or we can select a range of letters
print(food[:3])
print(food[4:7])
print(food[8:])
egg
and
spam
You can also use in
to check for substrings.
print('e' in food)
print('egg' in food)
print('ham' in food)
True
True
False
String Methods#
Strings also have a tons of useful methods.
Here is a good reference: https://www.w3schools.com/python/python_ref_string.asp
# for example, we can easily convert strings to uppercase
food.upper()
'EGG AND SPAM'
Note that string methods usually do not modify the original string, unlike list methods.
food
'egg and spam'
Exercises involving Lists and Strings#
Replace "baked beans"
with "eggs"
in the string menu_item
below.
You might what to search for an appropriate method here: https://www.w3schools.com/python/python_ref_string.asp
menu_item = 'spam spam spam spam spam spam baked beans spam spam spam and spam'
# answer here
Click to reveal solution
A quick peek at the string methods documentation reveals that
.replace()
would be useful for this.
Because string methods create a new string, we need to overwrite the variable in order to replace it.menu_item = menu_item.replace('baked beans', 'eggs')
Feel free to try this out by copying the code from above into a code cell and running it.
Callmenu_item
or useprint(menu_item)
to investigate the variable and validate your implementation.
We can use the .split()
method to split a string into list elements on white space or any other specified string.
menu_item_list = menu_item.split()
menu_item_list
['spam',
'spam',
'spam',
'spam',
'spam',
'spam',
'baked',
'beans',
'spam',
'spam',
'spam',
'and',
'spam']
How many times does "spam"
appear in the list menu_item_list
?
You might want to search the web or refer to the documentation on list methods: https://docs.python.org/3/tutorial/datastructures.html
# answer here
Click to reveal solution
Another quick peek at the documentation and we find that the
.count()
method does exactly what we desire.menu_item_list.count('spam')
Dictionaries#
What if you want to keep a record of students in a class, their class year, their exam scores, and whether they are a graduate or an undergraduate student?
This is where dictionaries come in handy. They allow us to store related data with labels, also known as key-value pairs.
Dictionaries are denoted with curly braces { }
in python.
More information on dictionaries: https://docs.python.org/3/tutorial/datastructures.html#dictionaries
# example of a dictionary
student1 = {'first': 'John',
'last': 'Smith',
'year': 2022,
'graduate': True,
'scores': [98, 94, 95, 86]}
student1
{'first': 'John',
'last': 'Smith',
'year': 2022,
'graduate': True,
'scores': [98, 94, 95, 86]}
To access data from a dictionary, you use square brackets [ ]
with the key: dict[key]
student1['first']
'John'
student1['scores']
[98, 94, 95, 86]
If the elements of a dictionary are also iterables (like lists), you can use chained indexing to access nested elements.
This also applies to other iterables like lists or arrays.
# let's say we want to know what grade John got on the third exam
student1['scores'][2]
95
# here are some more students
student2 = {'first': 'Mary', 'last': 'Johnson', 'year': 2024, 'graduate': False, 'scores': [89, 92, 96, 82]}
student3 = {'first': 'Robert', 'last': 'Williams', 'year': 2022, 'graduate': False, 'scores': [88, 72, 64, 91]}
student4 = {'first': 'Jennifer', 'last': 'Jones', 'year': 2023, 'graduate': True, 'scores': [92, 91, 94, 99]}
# we can store our students in a list
students = [student1, student2, student3, student4]
students
[{'first': 'John',
'last': 'Smith',
'year': 2022,
'graduate': True,
'scores': [98, 94, 95, 86]},
{'first': 'Mary',
'last': 'Johnson',
'year': 2024,
'graduate': False,
'scores': [89, 92, 96, 82]},
{'first': 'Robert',
'last': 'Williams',
'year': 2022,
'graduate': False,
'scores': [88, 72, 64, 91]},
{'first': 'Jennifer',
'last': 'Jones',
'year': 2023,
'graduate': True,
'scores': [92, 91, 94, 99]}]
# what is the first name of the third student in the list?
students[2]['first']
'Robert'
# what score did Robert get on his third exam?
students[2]['scores'][2]
64
We can also construct a dictionary iteratively. Using dict[key] = value
with a key that doesn’t exist will automatically add a new key-value pair into the dictionary.
student = {}
student['first'] = 'Linda'
student['last'] = 'Wilson'
student['year'] = 2025
student['graduate'] = False
student['scores'] = [84, 92, 89, 94]
student
{'first': 'Linda',
'last': 'Wilson',
'year': 2025,
'graduate': False,
'scores': [84, 92, 89, 94]}
# let's add this student to our list of students
students.append(student)
students
[{'first': 'John',
'last': 'Smith',
'year': 2022,
'graduate': True,
'scores': [98, 94, 95, 86]},
{'first': 'Mary',
'last': 'Johnson',
'year': 2024,
'graduate': False,
'scores': [89, 92, 96, 82]},
{'first': 'Robert',
'last': 'Williams',
'year': 2022,
'graduate': False,
'scores': [88, 72, 64, 91]},
{'first': 'Jennifer',
'last': 'Jones',
'year': 2023,
'graduate': True,
'scores': [92, 91, 94, 99]},
{'first': 'Linda',
'last': 'Wilson',
'year': 2025,
'graduate': False,
'scores': [84, 92, 89, 94]}]
Exercises involving Dictionaries#
Create a new student record using the first four elements from our previous list scores
.
Make sure to include all of the same fields as previous student records. Make up the values for other fields except scores.
Finally, add the newly create student to the list students
(and verify it’s there).
Hint: Remember that you can use slicing to extract a range of elements from a list.
# answer here
Click to reveal solution
One option would be to create a new variable like above and then add it to the list of dictionaries.
Or, instead, we could both create a new dictionary and add it to our list all in one line.students.append({'first': 'Monty', 'last': 'Python', 'year': 2025, 'graduate': True, 'scores': scores[:4]})
Remember that you can omit the zero when slicing from the beginning of a list, hence
scores[:4]
.
As this does not output anything, you would need to callstudents
or useprint()
to see if it actually worked.
Functions#
Functions take an input, do something with it, and provide an output.
# example of a function that takes a number and output its square
def square(x):
sq = x * x
return sq
square(5)
25
Note that any variables defined within a function definition are temporary and only exist within that function.
For example, the variable sq
does not exist, even though we just used it when we called square()
.
# attempting to refer to sq will gives us an error because it is not defined
sq
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[58], line 2
1 # attempting to refer to sq will gives us an error because it is not defined
----> 2 sq
NameError: name 'sq' is not defined
We must use the return
statement to make the function output any desired values.
Note that the whole expression to the right of the return
statement gets evaluated first, and then the result of the whole expression gets outputted.
# knowing this we can rewrite our function
def square(x):
return x * x
square(3)
However, functions do not need to take input nor do they need to return anything.
# example of a function that does not take input or return anything
def print_spam():
print('spam')
print_spam()
returned_value = print_spam()
print(returned_value)
Note that printing and returning are not the same:
print()
is used to display information intended for humans on the computer screenreturn
is used for a function to output a value that can be stored in a variable or used elsewhere
# note that printing and returning are not the same things
# this function does not take input, but still returns something
def return_spam():
return 'spam'
returned_value = return_spam()
print(returned_value)
What happens if you call square()
on a list?
square(scores)
Because you do not need to specify the types of variables in Python, it is very important to think about the type of the input when using or defining functions.
Exercises involving Functions#
Write a function that adds 42 to whatever number you give it.
# answer here
Click to reveal solution
Using the
square
function from above as an example, we can come up with something like this.def add_forty_two(number): output = number + 42 return output
You should always give your functions and variables short yet descriptive names.
Note that the variableoutput
in the function definition above is redundant and can be omitted.def add_forty_two(number): return number + 42
Also note that there is never a single right answer to these exercises. Your function might look different, but still work.
You should always test your function with various input and ensure the outputs are as expected.
Write a function with the following characteristics:
Input: A word (string).
Output: The same word but with " and spam"
added to the end of it.
# answer here
Click to reveal solution
Very similar to the function above, except this time we use string arithmetic.
def append_spam(word): return word + ' and spam'
Note that you should always use
return
to emit output from a function.
Useprint()
only when the purpose of your function is actually to display content on the screen.
Write a function with the following characteristics:
Input: Two numbers: the base and the exponent.
Output: The first number (base) to the power of the second number (exponent).
Bonus: Use a named argument and make the function square the base by default.
Here is a reference to help out with that: https://docs.python.org/3/tutorial/controlflow.html#more-on-defining-functions
# answer here
Click to reveal solution
A basic solution without using any named arguments would be as follows:
def power(base, exponent): return base ** exponent
Alternatively, we could make
exponent
a named argument and give it a default value of two.def power(base, exponent=2): return base ** exponent
Now you can omit
exponent
completely and the function will automatically square thebase
.
Also note that when defining function arguments like this, you can use them both as positional and named arguments when calling the function.
You can try this out by copying and running the code from below. Note all the different ways you can call functions.# omitting the exponent will square the number given print(power(2)) # you can use the function with positional arguments as you normally would print(power(2, 3)) # for better clarity you can refer to the exponent argument by name print(power(2, exponent=4)) # you can also refer to all the arguments by name print(power(base=2, exponent=5)) # you are free to change the order of the arguments when referring to all of them by name print(power(exponent=6, base=2))
Write a function with the following characteristics:
Input: A string and a number.
Output: The same string except the first and last letters (characters) have been duplicated by the specified number of times.
# answer here
Click to reveal solution
By combining string arithmetic and slicing, we can write a quick one-liner.
def enbiggen_word(word, x): return word[0] * x + word[1:-1] + word[-1] * x
Remember that the last element of a list (or the last character of a string) is at index-1
.
Again, your function could look different and span across multiple lines, but still work. This is just a compact example.
Loops#
For loops allow us to iterate over elements of a list.
# let's say we want to print out each of my scores one by one
for score in scores:
print(score)
# some of the low scores make us really sad
# let's create an illusion of happiness by adding five points to each grade
for score in scores:
score = score + 5
print(score)
# did that change our original scores?
scores
Note that the score
in for score in scores
is a temporary variable that only exists within the loop and contains a copy of the element in scores
that corresponds to the current iteration. Because it is a copy, any modifications to it will not be represented in the original list scores
. If we want to modify elements of a list within a loop, we have to iterate over the indices of the list and then use scores[index]
to access the element within the loop.
While loops allow us to repeat a code segment as long as a certain condition is met.
# there really are not any good examples of while loops at the beginner level
# so here is one that just prints "spam" ten times
count = 0 # how many times we have printed spam
while count < 10:
print('spam')
count = count + 1 # we must update the count to eventually exit the loop
# we could also achieve this using a for loop on a random 10-element list
In Python range()
is often used as an alternative to while loops or to iterate over the indices of a list.
# we can use range to do something a specified number of times
for i in range(10):
print('spam')
# or we can use it to iterate over the indices of a list, as opposed to the elements themselves
for i in range(len(scores)):
print('the score at index', i, 'is', scores[i])
However, when we want to access both every element and their respective index number, enumerate()
is a much better option.
for i, score in enumerate(scores):
print('the score at index', i, 'is', score)
Sidenote: Tuples#
Why did we need to use i, scores
when we used enumerate(grades)
?
That’s because enumerate()
returns a list of tuples.
A tuple is an ordered unchangeable collection of elements.
# example of a tuple
triple = (1, 2, 3)
# we can use indexing to access tuple elements
triple[1]
# we can also easily extract all elements from a tuple
a, b, c = triple
print(a, b, c)
# we can also ignore elements we are not interested in
_, second, _ = triple
second
Exercises involving Loops#
Use a for-loop to write a function with the following characteristics:
Input: A list of numbers.
Output: A copy of the input where the number 42 has been added to each element.
If nothing comes to mind at first, these hints might be of help:
Remember that simply modifying a list element in a for-loop does not change the original list.
Empty square brackets
[ ]
denote an empty list.You might want to use the
.append()
method and a function from a previous challenge.
# answer here
Click to reveal solution
def new_list_by_adding_42(xs): output = [] # create a new list to output for x in xs: # for every element x in the list xs output.append(x + 42) # add 42 to x and store the result in the list output return output # return the final output list once all elements of xs processed
Use a loop to write a function with the following characteristics:
Input: A number.
Output: The word "spam"
printed out said number of times, each on a new line.
# answer here
Click to reveal solution
One option would be to use
while
like in the example above.def print_many_spam(n): count = 0 # to keep track of how many times we have printed spam while count < n: # keep printing until count reaches specified number print('spam') # using return instead of print would exit the function count = count + 1 # update count to eventually exit loop
An easier and more Python-esque way of doing this would be to use
range()
andfor
instead.def print_many_spam(n): for _ in range(n): print('spam')
Remember that we can use the underscore
_
to denote variables we do not care about.
When usingrange()
in this context, we are not interested in the actual elements of the list.
Use range()
to write a function that takes a list of numbers as an input, but does not output anything.
Instead, it modifies the input list such that 42 gets added to each element.
You might be tempted use scores
to test this function. Do not do that! Instead, use the provided copy if desired.
We will talk about the importance of the .copy()
method in a little bit.
# create a copy of scores for testing purposes
scores_copy = scores.copy()
# answer here
Click to reveal solution
To modify the elements of a list, we need to loop over the indices of the list and refer to each element via its index.
We can userange()
withlen()
to generate a list of index numbers to use.def modify_list_by_adding_42(xs): for i in range(len(xs)): xs[i] = xs[i] + 42
Alternatively we could use
enumerate()
. Study the example below and see how it behaves the same.def modify_list_by_adding_42(xs): for i, x in enumerate(xs): xs[i] = x + 42
Note that because these functions modify the list provided as input, they do not output anything.
You should take a look at the list provided as input to see if they actually worked.
Remember the dictionary students
from before?
Write a function that curves the scores of all the students in the dictionary by adding five points to each score.
# this is the dictionary in case you do not remember
students
# answer here
Click to reveal solution
After some trial and error or a quick web search, you learn that using a for-loop to iterate over dictionary entries actually gives you both read and write access.
However, because the grades themselves are stored in a list, we must userange()
orenumerate()
to change them.def curve_scores(students): for student in students: for i, score in enumerate(student['scores']): student['scores'][i] = score + 5
Advanced: Alternatives to Loops#
Lambda Functions and Mapping#
Remember the function square()
from before?
Such a simple function can easily be written as a quick one-liner.
square = lambda x : x * x
square(4)
These one-line functions called lambda functions, but also often referred to as anonymous functions.
That’s because they do not have a name. You can easily define a lambda function as follows, without ever having to specify a function name.
lambda arguments : expression
Note how you do not need to use return
. The result of the one-line expression gets automatically outputted.
If desired, you can store a lambda function definition into a variable and use it as you would a normal function.
However, the true power of lambda functions are revealed when dealing with functions that expect other functions as input. One of these functions is the built-in map()
function. It takes a function and an iterable (list), and returns a new iterable where the function has been applied to each element. It is kind of like using a for
loop, except it is much more efficient and often the preferred alternative to using a for loop for simple element-wise operations.
# let's square all of the scores
map(square, scores)
By default map()
just creates a recipe on how to construct our new iterable or list. It will not actually create a new iterable unless explicitly asked to do so.
list(map(square, scores))
But what if we want to cube all of the scores?
We do not have to define a new function for that, we can just use lambda
.
list(map(lambda x : x ** 3, scores))
More information on lambda functions in Python: https://www.w3schools.com/python/python_lambda.asp
List Comprehension#
While the concepts of using anonymous functions and mapping over elements of an iterable are commonplace in other programming languages and very useful in Python when working with more complex data structures and using common data science libraries like NumPy and Pandas, they are not commonly used by Python programmers when working with lists. That is because Python has something called list comprehensions, which are a simpler and more pythonic alternative to using lambda
and map()
. This is what a general structure of a list comprehension looks like:
[expression for element in iterable]
Or, to use some dummy variables and functions:
[fun(x) for x in xs]
Using list comprehension, we can easily cube all of the scores as follows.
[score ** 3 for score in scores]
Note how list comprehension automatically creates a list. Also note how we could use whatever variable name we want, just like in a loop.
We can use both expressions and predefined functions with list comprehensions. For example, the square()
function from before could be used to square all of our scores.
[square(x) for x in scores]
You can also combine list comprehension with other list generators to create specific lists. For example, we could easily get a list of the first ten squares as follows.
[x ** 2 for x in range(10)]
One potential drawback when using list comprehensions is that it always generates a list. Every single element of the list gets calculated, even if you do not actually care about individual elements. When working with big data, this could be a problem as it would waste resources and slow down your processing. To overcome this, you could create a generator instead.
For example, we could use a generator to get the sum of the first 10,000 squares.
squares = (x ** 2 for x in range(10000))
squares
sum(squares)
Exercises involving Map, Lambda, and List Comprehensions#
Use map()
and a lambda
function to rewrite this function from before:
Input: A list of numbers.
Output: A copy of the input where the number 42 has been added to each element.
# answer here
Click to reveal solution
def new_list_by_adding_42(xs): return list(map(lambda x : x + 42, xs))
Now do the same using list comprehension instead.
# answer here
Click to reveal solution
def new_list_by_adding_42(xs): return [x + 42 for x in xs]
Conditionals#
If … else blocks can be used to run different segments of code depending on specified criteria.
# we would like to know if an exam score corresponds to the letter grade A
# we can write a function that congratulates us if we have an A
def check_if_A(score):
if score >= 90:
print('Score', score, 'corresponds to an A.')
print('Congratulations!')
else:
print('Score', score, 'does not correspond to an A.')
print('Sorry...')
check_if_A(91)
check_if_A(89)
We can also omit the else
if desired, in that case the code does not get run if the specified condition is not met.
def yay_iff_A(score):
if score >= 90:
print('YAY!')
yay_iff_A(91)
yay_iff_A(89)
We can also stack conditionals using elif
to provide more functionality.
The conditional statements get evaluated in order, from top to bottom.
# knowing the letter grade ranges, we can write a function that maps a score to a letter grade
def get_letter_grade(score):
if score < 60:
return 'F'
elif score < 70:
return 'D'
elif score < 80:
return 'C'
elif score < 90:
return 'B'
else:
return 'A'
# Now we can use a loop to get a list of letter grades from our scores
letter_grades = []
for score in scores:
letter_grades.append(get_letter_grade(score))
letter_grades
Exercises involving Conditionals#
Use a conditional and a for-loop to write a function with the following characteristics:
Input: A list of exam scores.
Output: A new list containing only the exam scores that correspond to the letter grade A, in order.
If nothing comes to mind at first, these hints might be of help:
Feel free to use the function we just created and your code from a previous challenge.
Use
==
to check for equality. This also works for strings.
# answer here
Click to reveal solution
One option would be to recycle the
get_letter_grade()
function from before.def extract_good_scores(scores): output = [] for score in scores: if (get_letter_grade(score) == 'A'): output.append(score) return output
However, because we only care about a very small fraction of the scores, it makes more sense to just check the value of the score directly.
def extract_good_scores(scores): output = [] for score in scores: if (score >= 90): output.append(score) return output
Advanced: Copy vs View#
What if we would like create a copy of a list?
Our first thought would be to use the assignment operator =
as we would with any variable.
numbers = [5, 4, 3, 2, 1]
numbers_copy = numbers
numbers_copy
numbers
Both lists appear identical, so it looks like it worked.
But what if we make some modifications to the copy?
numbers_copy[0] = -1
numbers_copy
numbers
Even though we only changed numbers_copy
, the original list numbers
also changed.
That is because Python optimized in the background and did not give us a copy of the list.
Instead, it took a shortcut and gave us a view, meaning that the names numbers_copy
and numbers
actually refer to the same object.
This kind of behavior is often referred to as pass-by-reference. While the behavior where a copy is returned instead is known as pass-by-value.
Simple (immutable) data structures like numbers are passed by value in Python, meaning that we always get a copy.
number = 42
number_copy = number
number_copy
number
number_copy = number_copy + 1
number_copy
number
However, when it comes to more complex (mutable) data structures like lists, Python almost always attempts to optimize and uses pass-by-reference.
Hence, when working with iterables and other complex data structures, it is important to keep in mind that Python returns a view unless explicitly asked to do otherwise.
To ensure we get a copy, we should use the .copy()
method.
numbers = [5, 4, 3, 2, 1]
numbers_copy = numbers.copy()
numbers_copy
numbers
Now numbers
and numbers_copy
are actually two different lists, so we can modify one without changing the other.
numbers_copy[0] = -1
numbers_copy
numbers
It is crucial to keep this behavior in mind when working with more advanced packages like Pandas or NumPy. Because Python might or might not choose to be clever and optimize in the background, you can never be completely sure whether you are dealing with a view or a copy. In most cases, the default behavior is to return a view, unless explicitly asked to do otherwise. However, this might differ from package to package or even function to function.
Hence, always read the documentation and use the appropriate function to ensure you know whether you are dealing with a view or copy.
If you would like to learn more about when Python uses pass-by-value and when it uses pass-by-reference, check out these resources:
Working with Libraries#
This is how you import modules, packages, or libraries.
import numpy as np
import matplotlib.pyplot as plt
Let us experiment with some NumPy functions.
# create an array and fill it with evenly spaced numbers from 0 to 2pi
x = np.linspace(0, 2*np.pi, num=100, dtype=float)
What are num
and dytpe
?
Use help()
or check out the documentation online:
https://docs.scipy.org/doc/numpy/reference/generated/numpy.linspace.html
help(np.linspace)
NumPy arrays have an attribute .size
that equals the number of elements.
x.size
Let’s calculate the sine of x
and plot it using Matplotlib.
y = np.sin(x)
# define the plot
plt.plot(x, y, '*-')
# add labels
plt.xlabel('x')
plt.ylabel('y')
# show the plot
plt.show()
# the '*-' is just a shorthand for a line graph with tick marks
Additional Resources#
Kaggle Python Course: https://www.kaggle.com/learn/python
W3Schools Python Tutorial: https://www.w3schools.com/python
Google’s Python Class: https://developers.google.com/edu/python
Official Python Tutorial: https://docs.python.org/3/tutorial
Software Carpentry Lessons
Programming with Python: https://swcarpentry.github.io/python-novice-inflammation
Plotting and Programming in Python: http://swcarpentry.github.io/python-novice-gapminder