In the previous Python lecture, you were introduced to lists. Let's look at a special list, the command line arguments.
When you run a Python script, everything from the command line used to run the script is stored as a list in a special variable, sys.argv.
Let's write a short program that prints out the values in sys.argv and call it argv.py.
1import sys
2# this library contains all sorts of built in functionality in Python
3
4arguments = sys.argv
5print(arguments)
Let's write a short program that prints out the values in sys.argv and call it argv.py.
1import sys
2# this library contains all sorts of built in functionality in Python
3
4arguments = sys.argv
5print(arguments)
Now, if we run this script, what should we see?
1> python argv.py
Let's write a short program that prints out the values in sys.argv and call it argv.py.
1import sys
2# this library contains all sorts of built in functionality in Python
3
4arguments = sys.argv
5print(arguments)
Now, if we run this script, what should we see?
1> python argv.py
2["argv.py"]
What about if we want to get everything from the command line except the name of our script (presumably we already know that)?
Here is our command line call:
1> python argv.py arg1 arg2 arg3
And here is what we want our program to print:
1["arg1", "arg2", "arg3"]
Here is our command line call:
1> python argv.py arg1 arg2 arg3
2["arg1", "arg2", "arg3"]
We can use something like
1import sys
2
3print(sys.argv[1:])
4# We use the empty argument on the right side of the index
5# since we don't know how many arguments there are
or
Obviously our scripts are of limited use if they can do only one things without responding to different inputs or circumstances. Luckily, there's an app for that.
We can ask Python to check conditions to decide what code to execute or skip with a set of commands: if, else, and elif. Let's see how they work.
Let's consider the following script:
1# myscript.py
2
3num = 37
4if num > 50:
5 print("Big!")
6else:
7 print("Small.")
8print("done")
What should we see as the output?
Let's consider the following script:
1# myscript.py
2
3num = 37
4if num > 50:
5 print("Big!")
6else:
7 print("Small.")
8print("Done")
1> python myscript.py
2Small.
3Done
So, what is happening? If we think of our script this way:
1num = 37 # Input value
2if num > 50: # Conditional test, branch 1
3 print("Big!") # 1st block of code
4else: # Conditional test, branch 2
5 print("Small.") # 2nd block of code
6print("Done") # unconditioned block of code
If we need more possible outcomes, we could add additional nested if statements.
1# myscript.py
2
3num = 37
4if num > 50:
5 print("Big!")
6else:
7 if num < 50:
8 print("Small.")
9 else:
10 print("Equal")
11print("Done")
This can make for harder to read code and more difficult to follow logic.
If we wanted more nuance, we could add additional conditions using elif. This stands for "else if" but means that we don't have to keep adding nested if statements.
1# myscript.py
2
3num = 37
4if num > 50:
5 print("Big!")
6elif num < 50:
7 print("Small.")
8else:
9 print("Equal")
10print("Done")
Note that you can have more than one elif statement but the else must be the last part of the conditional code. It is essentially the default option.
There are several ways to compare values in Python.
You can reverse the value of any boolean in Python with the keyword not
Looking at our previous script
1num = 37
2if num > 50:
3 print("Big!")
4elif not num > 50: # This is the same logic as above, but negated
5 print("Small.")
6else:
7 print("Equal")
8print("Done")
The if command simply looks to see if the argument is true or false. This means you can pass a statement of any complexity so long as it resolves to True or False
For example:
1if 1 > 2 or 2 < 3:
2 print("True")
What do each of the parts of the conditional statement resolve as? Together?
In our example:
1if 1 > 2 or 2 < 3:
2 print("True")
So, the code will print "True"
One nice thing is that as Python is evaluating the conditional statement, if it sees that the value will resolve to be True or False regardless of what comes next in the statement, it stops looking.
1a = []
2if len(a) > 0 and a[0] > 3:
3 print("Big")
4
5a.append(5)
6if len(a) > 0 and a[0] > 3:
7 print("Big")
This is useful in cases where a condition might result in an error if we haven't done something yet, like define a variable or try to index outside the bounds of a list or array.
There are several other really useful statements that evaluate to a boolean that you can use in an if statement.
This keywork lets you check if something is in a list (and other things which we will get to)
1a = [1, 2, 3]
2if 1 in a: # This will evaluate as True
3 print("True")
There are several other really useful statements that evaluate to a boolean that you can use in an if statement.
This keywork lets you check if something is in a list (and other things which we will get to)
1a = [1, 2, 3]
2if 1 in a: # This will evaluate as True
3 print("True")
If you want to check a string's value but don't care about matching the whole string, it can be useful to just check the start or end
1a = "# header line"
2if a.startswith("#"): # This will evaluate as True
3 print("header")
4if a.endswith("#"): # This will evaluate as False
5 print("false")
You can also evaluate other variables, like integers, strings, and lists as booleans.
Take for example the following code:
1a = [1, 2]
2if len(a) > 0:
3 print("list isn't empty")
4if not a:
5 print("list is empty")
How would the second if statement resolve?
You can also evaluate other variables, like integers, strings, and lists as booleans.
Take for example the following code:
1a = [1, 2]
2if len(a) > 0:
3 print("list isn't empty")
4if not a:
5 print("list is empty")
How would the second if statement resolve?
Instead of an exact match, think about how you would check for "close enough".
Try writing a conditional statement that checks whether a is within 10% of b.
Try writing a conditional statement that checks whether a is within 10% of b.
1if a < b * 0.9:
2 print("False")
3elif a > b * 1.1:
4 print("False")
5else:
6 print("True")
7
8if (a - b) / b >= -0.1 or (a - b) / b <= 0.1:
9 print("True")
10else:
11 print("False")
12
13if max(a - b, b - a) / b <= 0.1:
14 print("True")
15else:
16 print("False")
Sometimes you will need to loop through data but won't know for how long. In this case, a for loop is not appropriate. That is where the while loop comes in. It's like a for loop and if statement had a baby together.
The while loop consists for two parts.
Here is a simple example of a while loop:
1counter = 0
2while counter < 5:
3 counter += 1
4 print(counter)
Like the if and for statements, the while statement ends with a : and the block of code inside the loop is indented.
What should the output of this script be?
Here is a simple example of a while loop:
1counter = 0
2while counter < 5:
3 counter += 1
4 print(counter)
Like the if and for statements, the while statement ends with a : and the block of code inside the loop is indented.
What should the output of this script be?
1
2
3
4
5
Sometimes you want to exit a while loop without getting to the beginning of the loop, or for a condition other than the one tested in the while statement. In this case, you can use the break command.
1a = 0
2while a < 10:
3 a += 1
4 if a == 4:
5 break
6print(a)
The break will immediately exit a loop without executing any more of the code within the loop. It can also be used with for loops.
What do you think the above code would print?
Sometimes you want to exit a while loop without getting to the beginning of the loop, or for a condition other than the one tested in the while statement. In this case, you can use the break command.
1a = 0
2while a < 10:
3 a += 1
4 if a == 4:
5 break
6print(a)
The break will immediately exit a loop without executing any more of the code within the loop. It can also be used with for loops.
What do you think the above code would print?
4
What situations might a while loop be useful?
What situations might a while loop be useful?
For example, if you are estimating a parameter and getting closer and closer, you could use a while loop with a stop condition of "error < something"
Write a while loop to find the first power of 2 (2^1, 2^2, etc.) that is greater than 1000 (no cheating and using logarithms).
Write a while loop to find the first power of 2 (2^1, 2^2, etc.) that is greater than 1000 (no cheating and using logarithms).
1power = 0
2while 2 ** power < 1000:
3 power += 1
4print(power)
And the result:
10
So far we've seen one way to collect data together, lists. Let's look at another data structure, dicts, short for dictionaries.
Unlike lists, entries in dictionaries are comprised of 2 parts, a "key" and a "value". To create a dictionary, we can use the function dict().
1my_dictionary = dict(
2 key1: value1,
3 key2: value2,
4 key3: value3)
5print(my_dictionary[key2])
First, notice that each entry has a key, :, and then a value. Second, just like indexing with numbers to get a specific value from a list, we use indexes with dictionaries. However, these indexes are the keys, not just position values.
Note: we can also use a short-cut notation to create/indicate that something is a dictionary.
1my_dictionary = {key1: value1,...}
While they are both variable containers, there are several key differences between lists and dictionaries.
Lists
- Ordered, the values in a list are always in the same order
- Position indexed; to get a value from a list, you use [position] to retrieve it
- Can contain anything in each position
- The same value can appear as many times as you want in the list
- Slower to determine if a particular value is in the list
While they are both variable containers, there are several key differences between lists and dictionaries.
Lists
- Ordered, the values in a list are always in the same order
- Position indexed; to get a value from a list, you use [position] to retrieve it
- Can contain anything in each position
- The same value can appear as many times as you want in the list
- Slower to determine if a particular value is in the list
Dictionaries
- Unordered, think of your key-value pairs as jumbled up in a bag
- Key-indexed; to get a value from a dictionary, you use [key] to retrieve it
- Can contain anything as a value
- Keys can only be unchangable (immutable in the comp sci lingo).
- Can contain the same value as many times as you want
- Each key must be unique
- Very fast to check if a key exists in a dictionary and get its value
A practical exercise
What do we need another data structure for? What uses might a dictionary have that a list would be bad at?
What do we need another data structure for? What uses might a dictionary have that a list would be bad at?
Immutable means "can't be changed". In Python, this means that a variable is immutable if changing its value will overwrite it with the new value.
Everything we've looked at so far is immutable, except lists and dictionaries. Even strings are recreated anytime they changed.
So, things that can be used as dictionary keys (because they're immutable):
Wait, what's a tuple??!?!?
A tuple is like a list that's been frozen. Once created, it can't be changed. You can make a list into a tuple with the function tuple(). You can also create a tuple directly with the function or the short-hand (a, b, c, ...).
1a = [1, 2, 3] # A list, so mutable
2b = tuple(a) # A tuple, so immutable
3c = (1, 2, 3) # We can also create tuples directly
4d = {b: a, c: a} # Because b and c are immutable, they can be dictionary keys
Write a script that will read in a file and create a list of values for "Male" and one for "Female". The file format is:
Female 4
Female 9
Male 1
Female 11
Male 3
...
The first value in each line will always be "Male" or "Female". Try to write the script without using if.
Write a script that will read in a file and create a list of values for "Male" and one for "Female".
1import sys
2
3fs = open(sys.argv[1])
4data = {"Male": [], "Female": []}
5for line in fs:
6 line = line.rstrip().split()
7 data[line[0]].append(int(line[1]))
So far, we've seen one way to get values from a dictionary, indexing with the key for the desired value. But what if we want to see everything in a dictionary? Like stepping through a list item by item, there are similar options for iterating through a dictionary.
So far, we've seen one way to get values from a dictionary, indexing with the key for the desired value. But what if we want to see everything in a dictionary? Like stepping through a list item by item, there are similar options for iterating through a dictionary.
We can essentially get a a list of keys to step through with the dictionary method .keys()
1mydict = {1: 'a', 2: 'b', 3: 'c'}
2for key in mydict.keys():
3 print(key, mydict[key])
1 a
2 b
3 c
So far, we've seen one way to get values from a dictionary, indexing with the key for the desired value. But what if we want to see everything in a dictionary? Like stepping through a list item by item, there are similar options for iterating through a dictionary.
We can essentially get a a list of keys to step through with the dictionary method .keys()
1mydict = {1: 'a', 2: 'b', 3: 'c'}
2for key in mydict.keys():
3 print(key, mydict[key])
Note: I say essentially as mydict.keys() creates an iterator, not a list. The only time this distinction is important is if you want to get a list of the keys, in which case you need to explicityly convert it into a list
keys = list(mydict.keys())
So far, we've seen one way to get values from a dictionary, indexing with the key for the desired value. But what if we want to see everything in a dictionary? Like stepping through a list item by item, there are similar options for iterating through a dictionary.
We can essentially get a a list of values to step through much like keys with the dictionary method .values()
1mydict = {1: 'a', 2: 'b', 3: 'c'}
2for value in mydict.values():
3 print(value)
a
b
c
Like with mydict.keys(), you will need explicitly convert this to a list if you want a list of values.
So far, we've seen one way to get values from a dictionary, indexing with the key for the desired value. But what if we want to see everything in a dictionary? Like stepping through a list item by item, there are similar options for iterating through a dictionary.
We can step through with the dictionary getting key-value pairs with the method .items(). A tuple will be returned with the pair which we can unpack in the for loop line.
1mydict = {1: 'a', 2: 'b', 3: 'c'}
2for key, value in mydict.items():
3 print(key, value)
1 a
2 b
3 c
So far, you have used a variety of functions. Any time you use something in your script immediately followed by parentheses (e.g. print() ), you are using a function.
Let's define our own function. This requires 3 parts:
1# Lets define the function max()
2def max(a, b):
3 if a >= b:
4 val = a
5 else:
6 val = b
7 return val
1def max(a, b):
1def max(a, b):
2 if a >= b:
3 val = a
4 else:
5 val = b
1return val
You can even include multiple return commands, with the function ending as soon as it executes one of them.
For example we could have written the function:
1# Lets define the function max()
2def max(a, b):
3 if a >= b:
4 return a
5 else:
6 return b
This is a little cleaner, but in this case it makes no difference in the way the function acts.
One extension to function parameters that may be useful to know is the keyword / default parameters. Sometimes we don't need to pass a value for every parameter.
1def plot_my_data(X, Y, title="", xlabel=""):
2 fig = plt.figure()
3 fig.plot(X, Y)
4 if title:
5 fig.set_title(title)
6 if xlabel:
7 fig.set_xlabel(xlabel)
8 plt.show()
In this case, we can pass 2, 4, or 4 arguments. There are only a couple rules:
So, given our function plot_my_data(X, Y, title="", xlabel=""), here are several ways we could call this function:
1fig = plot_my_data(X, Y)
2
3fig = plot_my_data(X, Y, "myplot", "x-axis")
4
5fig = plot_my_data(X, Y, title="myplot")
6
7fig = plot_my_data(X, Y, xlabel="x-axis")
In order to practice what you've just learned, try writing your own function to find the area of a rectangle. It should:
Hopefully it was clear that in order to have a default value, each parameter also needed a name. A simple solution would be
1def area(height=1, width=1):
2 return height * width
3
4A = area(3, width=3)
5print(A) # should print 9
Notice that I used clear names for my function and my parameters. It makes reading and understanding it MUCH easier, especially when you return to your own code days or weeks later.
What advantage is there to putting your code in a function?
What advantage is there to putting your code in a function?
What advantage is there to putting your code in a function?
1def apply_to_list(data, func):
2 result = data[0]
3 for i in range(1, len(data)):
4 result = func(result, data[i])
5 return result
We could use this to sum a list, find the product of the list, find the maximum value of the list, etc.
1from math import prod # We need to import the function for finding products
2data = [1, 2, 3, 4, 5]
3print(apply_to_list(data, sum)) # prints 15
4print(apply_to_list(data, prod)) # prints 120
5print(apply_to_list(data, max)) # prints 5
It is inevitable that you will have bugs in your code. Luckily Python gives detailed information to help you understand what wrong.
Take the following code as an example:
1def sum_data(a):
2 return a + b
3
4def run_test():
5 data = 120
6 result = sum_data(data)
7 return result
8
9run_test()
What will happen when we run this?
1def sum_data(a):
2 return a + b
3
4def run_test():
5 data = 120
6 result = sum_data(data)
7 return result
8
9run_test()
We get the output:
1Traceback (most recent call last):
2 File ".../error.py", line 9, in <module>
3 run_test()
4 File ".../error.py", line 6, in run_test
5 result = sum_data(data)
6 File ".../error.py", line 2, in sum_data
7 return a + b
8NameError: name 'b' is not defined
1Traceback (most recent call last):
2 File ".../error.py", line 9, in <module>
3 run_test()
4 File ".../error.py", line 6, in run_test
5 result = sum_data(data)
6 File ".../error.py", line 2, in sum_data
7 return a + b
8NameError: name 'b' is not defined
The error Traceback is a map of the chain of events that led to the error in your code. It may be easier to read it from bottom to top to start with.
1Traceback (most recent call last):
2 File ".../error.py", line 9, in <module>
3 run_test()
4 File ".../error.py", line 6, in run_test
5 result = sum_data(data)
6 File ".../error.py", line 2, in sum_data
7 return a + b
8NameError: name 'b' is not defined
This is the specific kind of error, or "Exception" that occurred. There are several defined exceptions that are built in to Python, but some external libraries define their own.
In this case, Python is telling us that we used a name that the currently executing code doesn't know.
1Traceback (most recent call last):
2 File ".../error.py", line 9, in <module>
3 run_test()
4 File ".../error.py", line 6, in run_test
5 result = sum_data(data)
6 File ".../error.py", line 2, in sum_data
7 return a + b
8NameError: name 'b' is not defined
This line is telling us what function called the code that failed, which script it was in, and at which line.
This can be important when you are using code that isn't yours (built in or imported) and the error occurs in that external code.
1Traceback (most recent call last):
2 File ".../error.py", line 9, in <module>
3 run_test()
4 File ".../error.py", line 6, in run_test
5 result = sum_data(data)
6 File ".../error.py", line 2, in sum_data
7 return a + b
8NameError: name 'b' is not defined
These last two sets of lines are moving up the error chain, showing the path of function calls that ultimately led to the flawed function being called.
As mentioned earlier, there are a variety of built in exceptions, each with a well defined use case.
There are many more exceptions, most which you should never encounter, some that you might but are self-explanatory, and a couple that we will see shortly.
Given the code and traceback, how could you fix our script?
1def sum_data(a):
2 return a + b
3
4def run_test():
5 data = 120
6 result = sum_data(data)
7 return result
8
9run_test()
1Traceback (most recent call last):
2 File ".../error.py", line 9, in <module>
3 run_test()
4 File ".../error.py", line 6, in run_test
5 result = sum_data(data)
6 File ".../error.py", line 2, in sum_data
7 return a + b
8NameError: name 'b' is not defined
Some possible answers
1def sum_data(a, b):
2 return a + b
3
4def run_test():
5 data = 120
6 data2 = 5
7 result = sum_data(data, data2)
8 return result
1def sum_data(a, b=0):
2 return a + b
There are some common errors that even seasoned programmers still make, Be on the lookout in your own code.
Create a script with the following code:
1for number in range(10):
2 # use a if the number is a multiple of 3, otherwise use b
3 if (Number % 3) == 0:
4 message = message + a
5 else:
6 message = message + 'b'
7print(message)
Correct errors until you can get it to successfully run.
1for number in range(10):
2 # use a if the number is a multiple of 3, otherwise use b
3 if (Number % 3) == 0: # Number isn't a variable we have defined
4 message = message + a # We never defined message. It should be message = ""
5 else: # Also, we need the letter 'a', not the variable a
6 message = message + 'b'
7print(message)
One of the biggest mistakes you can make in programming is assuming people (including you) will use your code correctly. Instead, it's a good idea to make your code idiot-proof (so to speak).
One of the biggest mistakes you can make in programming is assuming people (including you) will use your code correctly. Instead, it's a good idea to make your code idiot-proof (so to speak).
One way to do this is with the assert statement. This statements says "this should be True" and will throw an AssertionError if the result is False. This let's you check that things meet your expectations before moving on.
Consider the following function:
1def area(a, b):
2 return a * b
This is completely straigth-forward and works, if used properly. But how could it be used incorrectly?
Consider the following function:
1def area(a, b):
2 return a * b
This is completely straigth-forward and works, if used properly. But how could it be used incorrectly?
So let's make the function more robust with assert statements.
1def area(a, b):
2 assert type(a) in [int, float] and type(b) in [int, float]
3 assert a >= 0 and b >= 0
4 return a * b
Now, if we use this function improperly, our assertions will catch it rather than getting unexpected behavior.
1area(1, [2])
1Traceback (most recent call last):
2 File "./area.py", line 6, in <module>
3 area(1, [2])
4 File "./area.py", line 2, in area
5 assert type(a) in [int, float] and type(b) in [int, float]
6AssertionError
We can also add more informative messages to the assert statements
1def area(a, b):
2 assert type(a) in [int, float] and type(b) in [int, float], "a and b need to be numbers"
3 assert a >= 0 and b >= 0, "must not be negative"
4 return a * b
Now when we misuse the function, we get a clear reason why it doesn't work
1area(1, [2])
1Traceback (most recent call last):
2 File "./area.py", line 6, in <module>
3 area(1, [2])
4 File "./area.py", line 2, in area
5 assert type(a) in [int, float] and type(b) in [int, float], "a and b need to be numbers"
6AssertionError: a and b need to be numbers
Finally, how do you test your code? First, always try to follow the rule "Fail fast". Second, check things at each step, don't wait for a finished script to find a giant pile of errors.
Table of Contents | t |
---|---|
Exposé | ESC |
Full screen slides | e |
Presenter View | p |
Source Files | s |
Slide Numbers | n |
Toggle screen blanking | b |
Show/hide slide context | c |
Notes | 2 |
Help | h |