from IPython.display import IFrame
from IPython.display import Markdown

# Additional styling ; should be moved into helpers
from IPython.display import display, HTML

HTML("<style>{}</style>".format(open("rise.css").read()))

Class 7A: Introduction to Programming in Python I#

We will begin soon!

Firas Moosvi

Announcements#

  1. Welcome Back!! Hope you had a good week off

  1. Grades and feedback for Labs 1-4 and feedback for LL1 - LL5 is now released; if you got a G or an R, submit a resubmission request on Ed Discussion.

  1. Milestone 3 due November 3rd, 2022!

    • Do not leave it to the last minute!

    • You should be spending at least a few hours every week working on your project

    • The more effort you put in in Milestone 3, the less you’ll have to do/fix for Milestone 5!

  1. Test 2 will be next Thursday during class!

    • Test 2 details are here

    • Make sure to check and read the rules carefully!

  1. Lab 5 is due this week.

  1. Reminder: my Student Hours are after class on Tues, Thurs and the Project TA (Islam) Student hours are Thursdays from 12:30-13:30 on Zoom.

    • Groups 11 and 41: please plan on going to the Project TA student hours this week.

Mid-Course Feedback#

  • We only had 8 respondents to the mid-course feedback (because I forgot to do it in class) - let’s take 5 minutes to do this now

  • Here’s the link

Programming in Python I#

In this class, we go through a notebook by a former colleague, Dr. Mike Gelbart, option co-director of the UBC-Vancouver MDS program.

If you prefer, you can also watch his recording of the same material.

Class Outline#

  • Project Setup and Milestone 2 review (15 min)

  • Why Python? (1 min)

  • Loops (15 min)

  • Comprehensions (5 min)

  • Functions in Python (30 min)

  • Unit tests, corner cases (10 min)

  • Multiple return values (5 min)

Attribution#

Why Python? (0 min)#

  • Why did we choose Python in COSC 301?

    • Extremely popular in DS (and beyond!)

    • Relatively easy to learn

    • Good documentation

    • Huge user community

      • Lots of Stack Overflow and other forums

      • Lots of useful packages (more on this,after the last class of the week)

Comments in python (0 min)#

x = 1  # why am I doing this? this is a comment
"""
this is a string, which does nothing
and can be used as a comment

I can Write anything I want here
"""

7


x = 1

Loops (15 min)#

  • Loops allow us to execute a block of code multiple times.

  • We will focus on for loops

for n in [2, 7, -1, 5]:
    print(f"The number is {n} and its square is {n**2}")
    # this is inside the loop 
    print(n)

# this is outside the loop 
n
The number is 2 and its square is 4
2
The number is 7 and its square is 49
7
The number is -1 and its square is 1
-1
The number is 5 and its square is 25
5
5

The main points to notice:

  • Keyword for begins the loop

  • Colon : ends the first line of the loop

  • We can iterate over any kind of iterable: list, tuple, range, string. In this case, we are iterating over the values in a list

  • Block of code indented is executed for each value in the list (hence the name “for” loops, sometimes also called “for each” loops)

  • The loop ends after the variable n has taken all the values in the list

"abc" + "def"
'abcdef'
# Doing the same thing as a for loop, with f-strings
word = "Python"
for letter in word:
    print(f"Gimme a {letter} !")

print("What's that spell?!! " + word + "!")
Gimme a P !
Gimme a y !
Gimme a t !
Gimme a h !
Gimme a o !
Gimme a n !
What's that spell?!! Python!
word = "Python"
for letter in word:
    print("Gimme a " + letter + "!")

print("What's that spell?!! " + word + "!")
Gimme a P!
Gimme a y!
Gimme a t!
Gimme a h!
Gimme a o!
Gimme a n!
What's that spell?!! Python!
  • A very common pattern is to use for with range.

  • range gives you a sequence of integers up to some value.

for i in range(0, 10):
    print(i)
0
1
2
3
4
5
6
7
8
9

We can also specify a start value and a skip-by value with range:

for i in range(1, 101, 10):
    print(i * 2)
2
22
42
62
82
102
122
142
162
182

We can write a loop inside another loop to iterate over multiple dimensions of data. Consider the following loop as enumerating the coordinates in a 3 by 3 grid of points.

for x in [1, 2, 3]:
    for y in ["a", "b", "c"]:
        print((x, y))
(1, 'a')
(1, 'b')
(1, 'c')
(2, 'a')
(2, 'b')
(2, 'c')
(3, 'a')
(3, 'b')
(3, 'c')
list_1 = [1, 2, 3]
list_2 = ["a", "b", "c"]
for i in range(len(list_1)):
    print(list_1[i], list_2[i])
1 a
2 b
3 c

We can loop through key-value pairs of a dictionary using .items():

courses = {
    300: "awesome",
    301: "naptime!",
    302: "riveting",
}

for course_num,description  in courses.items():
    print(f"DATA {course_num} is {description}")
DATA 300 is awesome
DATA 301 is naptime!
DATA 302 is riveting
val1= [1,2,3]
val2= [4,5,6]
val3= [10,100,100]

for i,j,k in zip(val1,val2,val3):
    
    print(i,j,k)
1 4 10
2 5 100
3 6 100
for course_num in courses:
    print(course_num, courses[course_num]) 
300 awesome
301 naptime!
302 riveting

Above: the general syntax is for key, value in dictionary.items():

Optional while loops (rarely used)#

  • We can also use a while loop to excute a block of code several times.

  • In reality, I rarely use these.

  • Beware! If the conditional expression is always True, then you’ve got an infintite loop!

    • (Use the “Stop” button in the toolbar above, or Ctrl-C in the terminal, to kill the program if you get an infinite loop.)

n = 10
while n > 0:
    print(n)
    #n = n - 1
    n-=1 # shortcut for doing n = n-1
print("Blast off!")
10
9
8
7
6
5
4
3
2
1
Blast off!

Comprehensions (5 min)#

Comprehensions allow us to build lists/tuples/sets/dictionaries in one convenient, compact line of code.

%%timeit

words = ["hello", "goodbye", "the", "antidisestablishmentarianism"]
y = list()
for word in words:
    y.append(word[-1])
y
629 ns ± 12.5 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
%%timeit
y = [word[-1] for word in words]  # list comprehension
y
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[17], line 1
----> 1 get_ipython().run_cell_magic('timeit', '', 'y = [word[-1] for word in words]  # list comprehension\ny\n')

File /opt/hostedtoolcache/Python/3.10.9/x64/lib/python3.10/site-packages/IPython/core/interactiveshell.py:2422, in InteractiveShell.run_cell_magic(self, magic_name, line, cell)
   2420 with self.builtin_trap:
   2421     args = (magic_arg_s, cell)
-> 2422     result = fn(*args, **kwargs)
   2423 return result

File /opt/hostedtoolcache/Python/3.10.9/x64/lib/python3.10/site-packages/IPython/core/magics/execution.py:1162, in ExecutionMagics.timeit(self, line, cell, local_ns)
   1160 for index in range(0, 10):
   1161     number = 10 ** index
-> 1162     time_number = timer.timeit(number)
   1163     if time_number >= 0.2:
   1164         break

File /opt/hostedtoolcache/Python/3.10.9/x64/lib/python3.10/site-packages/IPython/core/magics/execution.py:156, in Timer.timeit(self, number)
    154 gc.disable()
    155 try:
--> 156     timing = self.inner(it, self.timer)
    157 finally:
    158     if gcold:

File <magic-timeit>:1, in inner(_it, _timer)

NameError: name 'words' is not defined
test ={}
test['duplicate'] = True
test['testkey']= False
test['newkey'] = 0
test
# duplicate keys are not possible in python!
{'duplicate': True, 'testkey': False, 'newkey': 0}
word_lengths = {word: len(word) for word in words}  # dictionary comprehension
word_lengths
{'hello': 5, 'goodbye': 7, 'the': 3, 'antidisestablishmentarianism': 28}
word_lengths = {}

for word in words:
    word_lengths[word] = len(word)

word_lengths

Functions in Python (20 mins)#

  • Define a function to re-use a block of code with different input parameters, also known as arguments.

  • For example, define a function called square which takes one input parameter n and returns the square n**2.

def square(n):
    n_squared = n**2
    return n_squared
var = 50

square(n=var)
n_squared # not available to us because the "scope" of it is only within square(n)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [87], in <cell line: 4>()
      1 var = 50
      3 square(n=var)
----> 4 n_squared

NameError: name 'n_squared' is not defined
import numpy as np
np.mean([5,10,15,20])
12.5
square(np.mean([5,10,15,20]))
156.25
square(12345)
152399025
  • Begins with def keyword, function name, input parameters and then colon (:)

  • Function block defined by indentation

  • Output or “return” value of the function is given by the return keyword

Null return type#

If you do not specify a return value, the function returns None when it terminates:

def f(x):
    x + 1  # no return!
    if x == 999:
        return


print(f(998)) 
None

DRY principle, designing good functions#

  • DRY: Don’t Repeat Yourself

  • See Wikipedia article

  • Consider the task of, for each element of a list, turning it into a palindrome

    • e.g. “mike” –> “mikeekim”

names = ["milad", "rodolfo", "tiffany", "Firas"]
#Aside: to reverse a string, use ::-1
name = "mike"
name[::-1]
'ekim'
names_backwards = list()

names_backwards.append(names[0] + names[0][::-1])
names_backwards.append(names[1] + names[1][::-1])
names_backwards.append(names[2] + names[2][::-1])
names_backwards.append(names[3] + names[3][::-1])

names_backwards
['miladdalim', 'rodolfooflodor', 'tiffanyynaffit', 'FirassariF']
  • Above: this is gross, terrible, yucky code

    1. It only works for a list with 3 elements

    2. It only works for a list named names

    3. If we want to change its functionality, we need to change 3 similar lines of code (Don’t Repeat Yourself!!)

    4. It is hard to understand what it does just by looking at it

names_backwards = list()

for name in names:
    names_backwards.append(name + "-" + name[::-1])

names_backwards
['milad-dalim', 'rodolfo-oflodor', 'tiffany-ynaffit', 'Firas-sariF']

Above: this is slightly better. We have solved problems (1) and (3).

def make_palindromes(names):
    names_backwards = list()

    for name in names:
        names_backwards.append(name + name[::-1])

    return names_backwards


make_palindromes(names)
['miladdalim', 'rodolfooflodor', 'tiffanyynaffit', 'FirassariF']
  • Above: this is even better. We have now also solved problem (2), because you can call the function with any list, not just names.

  • For example, what if we had multiple lists:

names1 = ["milad", "rodolfo", "tiffany"]
names2 = ["Trudeau", "Scheer", "Singh", "Blanchet", "May"]
names3 = ["apple", "orange", "banana"]
names_backwards_1 = list()

for name in names1: 
    names_backwards_1.append(name + name[::-1])

names_backwards_1 
['miladdalim', 'rodolfooflodor', 'tiffanyynaffit']
names_backwards_2 = list()

for name in names2:
    names_backwards_2.append(name + name[::-1])
 
names_backwards_2
['TrudeauuaedurT', 'ScheerreehcS', 'SinghhgniS', 'BlanchettehcnalB', 'MayyaM']
names_backwards_3 = list()

for name in names3:
    names_backwards_3.append(name + name[::-1])

names_backwards_3
['appleelppa', 'orangeegnaro', 'bananaananab']

Above: this is very bad also (and imagine if it was 20 lines of code instead of 2). This was problem (2). Our function makes it much better:

make_palindromes(names1)
['miladdalim', 'rodolfooflodor', 'tiffanyynaffit']
make_palindromes(names2)
['TrudeauuaedurT', 'ScheerreehcS', 'SinghhgniS', 'BlanchettehcnalB', 'MayyaM']
make_palindromes(names3)
['appleelppa', 'orangeegnaro', 'bananaananab']
  • You could get even more fancy, and put the lists of names into a list (so you have a list of lists).

  • Then you could loop over the list and call the function each time:

for list_of_names in [names1, names2, names3]:
    print(make_palindromes(list_of_names))
['miladdalim', 'rodolfooflodor', 'tiffanyynaffit']
['TrudeauuaedurT', 'ScheerreehcS', 'SinghhgniS', 'BlanchettehcnalB', 'MayyaM']
['appleelppa', 'orangeegnaro', 'bananaananab']

Designing good functions#

  • How far you go with this is sort of a matter of personal style, and how you choose to apply the DRY principle: DON’T REPEAT YOURSELF!

  • These decisions are often ambiguous. For example:

    • Should make_palindromes be a function if I’m only ever doing it once? Twice?

    • Should the loop be inside the function, or outside?

    • Or should there be TWO functions, one that loops over the other??

  • In my personal opinion, make_palindromes does a bit too much to be understandable.

  • I prefer this:

def make_palindrome(name):
    return name + name[::-1]


make_palindrome("milad")
'miladdalim'
  • From here, we want to “apply make_palindrome to every element of a list”

  • It turns out this is an extremely common desire, so Python has built-in functions.

  • One of these is map, which we’ll cover later. But for now, just a comprehension will do:

[make_palindrome(name) for name in names1]

Other function design considerations:

  • Should we print output or produce plots inside or outside functions?

    • I would usually say outside, because this is a “side effect” of sorts

  • Should the function do one thing or many things?

    • This is a tough one, hard to answer in general

Optional & keyword arguments#

  • Sometimes it is convenient to have default values for some arguments in a function.

  • Because they have default values, these arguments are optional, hence “optional arguments”

  • Example:

def square(num=6):
    return num**2

square()
36
def repeat_string(s, n=2):
    return s * n
repeat_string("COSC301",5)
'COSC301COSC301COSC301COSC301COSC301'
repeat_string("mds-", 5)
'mds-mds-mds-mds-mds-'
repeat_string("mds")  # do not specify `n`; it is optional
'mdsmds'

Sensible defaults:

  • Ideally, the default should be carefully chosen.

  • Here, the idea of “repeating” something makes me think of having 2 copies, so n=2 feels like a sensible default.

Syntax:

  • You can have any number of arguments and any number of optional arguments

  • All the optional arguments must come after the regular arguments

  • The regular arguments are mapped by the order they appear

  • The optional arguments can be specified out of order

def example(a, b, c="DEFAULT", d="DEFAULT"):
    print(a, b, c, d)


example(1, 2,  )
1 2 3 4

Using the defaults for c and d:

example(1, 2)
1 2 DEFAULT DEFAULT

Specifying c and d as keyword arguments (i.e. by name):

example(1, 2, c=3, d=4)

Specifying only one of the optional arguments, by keyword:

example(1, 2, c=3)
1 2 3 DEFAULT

Or the other:

example(1, 2, d=4)
1 2 DEFAULT 4

Specifying all the arguments as keyword arguments, even though only c and d are optional:

example(a=1, b=2, c=3, d=4)
1 2 3 4

Specifying c by the fact that it comes 3rd (I do not recommend this because I find it is confusing):

example(1, 2, 3)  # not recommended

Specifying the optional arguments by keyword, but in the wrong order (this is also somewhat confusing, but not so terrible - I am OK with it):

example(1, 2, d=4, c=3)

Specifying the non-optional arguments by keyword (I am fine with this):

example(a=1, b=2)
1 2 DEFAULT DEFAULT

Specifying the non-optional arguments by keyword, but in the wrong order (not recommended, I find it confusing):

example(b=2, a=1)

Specifying keyword arguments before non-keyword arguments (this throws an error):

example(a=2, 1)
  • In general, I am used to calling required arguments in order, and any optional arguments by keyword.

  • The language allows us to deviate from this, but it can be unnecessarily confusing sometimes.

Advanced stuff (optional):#

  • You can also call/define functions with *args and **kwargs; see, e.g. here

  • Do not instantiate objects in the function definition - see here under “Mutable Default Arguments”

def example(a, b=[]):  # don't do this!
    return 0
def example(a, b=None):  # insted, do this
    if b is None:
        b = []
    return 0

Docstrings (10 mins)#

  • We got pretty far above, but we never solved problem (4): It is hard to understand what it does just by looking at it

  • Enter the idea of function documentation (and in particular docstrings)

  • The docstring goes right after the def line.

def make_palindrome(string):
    """Turns the string into a palindrome by concatenating itself with a reversed version of itself."""

    return string + string[::-1]

In IPython/Jupyter, we can use ? to view the documentation string of any function in our environment.

make_palindrome?
print?

Docstring structure#

  1. Single-line: If it’s short, then just a single line describing the function will do (as above).

  2. PEP-8 style Multi-line description + a list of arguments; see here.

  3. Scipy style: The most elaborate & informative; see here and here.

The PEP-8 style:

def make_palindrome(s):
    """
    Turns the string into a palindrome by concatenating itself
    with a reversed version of itself.

    Arguments:
    s - (str) the string to turn into a palindrome
    """
    return s + s[::-1]
make_palindrome?

The scipy style:

def make_palindrome(s):
    """
    Turn a string into a palindrome.

    Turns the string into a palindrome by concatenating itself
    with a reversed version of itself, so that the returned
    string is twice as long as the original.

    Parameters
    ----------
    s : str
        The string to turn into a palindrome.

    Returns
    -------
    str
        The new palindrome string.

    Examples
    --------
    >>> make_palindrome("abc")
    "abccba"
    """
    return s + s[::-1]
make_palindrome("hello")  # press shift-tab HERE to get docstring!!
'helloolleh'

Below is the general form of the scipy docstring (reproduced from the scipy/numpy docs):

def function_name(param1, param2, param3):
    """First line is a short description of the function.

    A paragraph describing in a bit more detail what the
    function does and what algorithms it uses and common
    use cases.

    Parameters
    ----------
    param1 : datatype
        A description of param1.
    param2 : datatype
        A description of param2.
    param3 : datatype
        A longer description because maybe this requires
        more explanation and we can use several lines.

    Returns
    -------
    datatype
        A description of the output, datatypes and behaviours.
        Describe special cases and anything the user needs to
        know to use the function.

    Examples
    --------
    >>> function_name(3,8,-5)
    2.0
    """

Docstrings with optional arguments#

When specifying the parameters, we specify the defaults for optional arguments:

# PEP-8 style
def repeat_string(s, n=2):
    """
    Repeat the string s, n times.

    Arguments:
    s -- (str) the string
    n -- (int) the number of times (default 2)
    """
    return s * n
# scipy style
def repeat_string(s, n=2):
    """
    Repeat the string s, n times.

    Parameters
    ---------- 
    s : str
        the string
    n : int, optional (default = 2)
        the number of times

    Returns
    -------
    str
        the repeated string

    Examples
    --------
    >>> repeat_string("Blah", 3)
    "BlahBlahBlah"
    """
    return s * n

Automatically generated documentation#

  • By following the docstring conventions, we can automatically generate documentation using libraries like sphinx, pydoc or Doxygen.

    • For example: compare this documentation with this code.

    • Notice the similarities? The webpage was automatically generated because the authors used standard conventions for docstrings!

What makes good documentation?#

  • What do you think about this?

################################
#
# NOT RECOMMENDED TO DO THIS!!!
#
################################


def make_palindrome(string):
    """
    Turns the string into a palindrome by concatenating itself
    with a reversed version of itself. To do this, it uses the
    Python syntax of `[::-1]` to flip the string, and stores
    this in a variable called string_reversed. It then uses `+`
    to concatenate the two strings and return them to the caller.

    Arguments:
    string - (str) the string to turn into a palindrome

    Other variables:
    string_reversed - (str) the reversed string
    """

    string_reversed = string[::-1]
    return string + string_reversed



  • This is poor documentation! More is not necessarily better!

  • Why?

    • Very verbose

    • Write documentation about “what it does” and not “how you did it” (that is an implementation detail)

Side effects (careful!)#

  • If a function changes the variables passed into it, then it is said to have side effects

  • Example:

def silly_sum(sri):
    sri.append(0)
    return sum(sri)
silly_sum([1, 2, 3, 4])
10

Looks good, like it sums the numbers? But wait…

lst = [1, 2, 3, 4]
silly_sum(lst)
10
silly_sum(lst)
10
lst
[1, 2, 3, 4, 0]
  • If you function has side effects like this, you must mention it in the documentation (later today).

  • In general avoid this!

Unit tests, corner cases (10 mins)#

assert statements#

  • assert statementS cause your program to fail if the condition is False.

  • They can be used as sanity checks for your program.

  • There are more sophisticated way to “test” your programs, beyond the scope of this course

  • The syntax is:

assert expression , "Error message if expression is False or raises an error."
assert 1 == 2, "1 is not equal to 2."
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
Input In [133], in <cell line: 1>()
----> 1 assert 1 == 2, "1 is not equal to 2."

AssertionError: 1 is not equal to 2.

Try and except blocks#

g = [1,2,3,4,5,6,7]

#g[8] = 'Hello' # This way will cause an error

# this is a more graceful way of dealing with errors

try:
    g[4] = 'Hello'
    h = 7/0
except IndexError:
    print(f'your list is less than 8 elements long, your list is actually {len(g)} elements')
# except ZeroDivisionError:
#     print("stop doing stupid things")
# except:
#     print('there is some sort of error in your code')
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
Input In [148], in <cell line: 7>()
      7 try:
      8     g[4] = 'Hello'
----> 9     h = 7/0
     10 except IndexError:
     11     print(f'your list is less than 8 elements long, your list is actually {len(g)} elements')

ZeroDivisionError: division by zero
len(g)
7

Systematic Program Design#

A systematic approach to program design is a general set of steps to follow when writing programs. Our approach includes:

  1. Write a stub: a function that does nothing but accept all input parameters and return the correct datatype.

  2. Write tests to satisfy the design specifications.

  3. Outline the program with pseudo-code.

  4. Write code and test frequently.

  5. Write documentation.

The key point: write tests BEFORE you write code.

  • You do not have to do this in MDS, but you may find it surprisingly helpful.

  • Often writing tests helps you think through what you are trying to accomplish.

  • It’s best to have that clear before you write the actual code.

Testing woes - false positives#

  • Just because all your tests pass, this does not mean your program is correct!!

  • This happens all the time. How to deal with it?

    • Write a lot of tests!

    • Don’t be overconfident, even after writing a lot of tests!

def sample_median(x):
    """Finds the median of a list of numbers."""
    x_sorted = sorted(x)
    return x_sorted[len(x_sorted) // 2]


assert sample_median([1, 2, 3, 4, 5]) == 3
assert sample_median([0, 0, 0, 0]) == 0

Looks good? … ?






assert sample_median([1, 2, 3, 4]) == 2.5






assert sample_median([1, 3, 2]) == 2

Testing woes - false negatives#

  • It can also happen, though more rarely, that your tests fail but your program is correct.

  • This means there is something wrong with your test.

  • For example, in the autograding for lab1 this happened to some people, because of tiny roundoff errors.

Corner cases#

  • A corner case is an input that is reasonable but a bit unusual, and may trip up your code.

  • For example, taking the median of an empty list, or a list with only one element.

  • Often it is desirable to add test cases to address corner cases.

assert sample_median([1]) == 1
  • In this case the code worked with no extra effort, but sometimes we need if statements to handle the weird cases.

  • Sometimes we want the code to throw an error (e.g. median of an empty list); more on this later.

Multiple return values (5 mins)#

  • In most (all?) programming languages I’ve seen, functions can only return one thing.

  • That is technically true in Python, but there is a “workaround”, which is to return a tuple (or similarly, a dictionary).

# not good from a design perspective!
def sum_and_product(x, y):
    return (x + y, x * y)
sum_and_product(5, 6)

In some cases in Python, the parentheses can be omitted:

def sum_and_product(x, y):
    return x + y, x * y
sum_and_product(5, 6)

It is common to store these in separate variables, so it really feels like the function is returning multiple values:

s, p = sum_and_product(5, 6)
s
p
  • Question: is this good function design.

  • Answer: usually not, but sometimes.

  • You will encounter this in some Python packages.

That’s it for today!

See you on Thursday

Demo of writing code#

Task: GIve me a string, find the longest palindrome in the strong

  • Reverse a string

  • Find the largest number in a given list, if it’s even multiply it by 10, if it’s odd, multiply it by a 1000

def largest_num_operation(provided_list):
    
    result = provided_list
    
    return result
def largest_num_operation(provided_list):
    
    result = provided_list
    
    return result

assert largest_num_operation([1,2,3,4,5,6]) == 60, "Your function does not work for even numbers!"
assert largest_num_operation([1,2,3,4,5]) == 5000, "Your function does not work for odd numbers!"
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
Input In [154], in <cell line: 7>()
      3     result = provided_list
      5     return result
----> 7 assert largest_num_operation([1,2,3,4,5,6]) == 60, "Your function does not work for even numbers!"
      8 assert largest_num_operation([1,2,3,4,5]) == 5000, "Your function does not work for odd numbers!"

AssertionError: Your function does not work for even numbers!
def largest_num_operation(provided_list):
    
    result = provided_list
    
    # 1. Find the largest number in the list and store it
        # largest_val = max(provided_list)
    # 2. I check to see if the number is odd or even
        # %2 ==0 (even)
        # %2 !=0 (odd)
    # 3. If it's odd I multiply by 1000
        # x 1000
    # 4. If it's even, I multiply by 10
        # x 10
    
    return result

assert largest_num_operation([1,2,3,4,5,6]) == 60, "Your function does not work for even numbers!"
assert largest_num_operation([1,2,3,4,5]) == 5000, "Your function does not work for odd numbers!"
def largest_num_operation(provided_list):
    
    result = provided_list
    
    # 1. Find the largest number in the list and store it
        # largest_val = max(provided_list)
        
    largest_val = max(provided_list)
        
    # 2. I check to see if the number is odd or even
        # %2 ==0 (even)
        # %2 !=0 (odd)
        
    if largest_val % 2 == 0:
        # x 1000
    elif largest_val:
        # x 10
        
    return result
    # 3. If it's odd I multiply by 1000
        # x 1000
    # 4. If it's even, I multiply by 10
        # x 10
    
    return result

assert largest_num_operation([1,2,3,4,5,6]) == 60, "Your function does not work for even numbers!"
assert largest_num_operation([1,2,3,4,5]) == 5000, "Your function does not work for odd numbers!"
  Input In [156]
    elif largest_val:
    ^
IndentationError: expected an indented block after 'if' statement on line 14
def largest_num_operation(provided_list):
    
    largest_val = max(provided_list)
    
    if largest_val == 0:
        print("please put in a more useful number, instead of 0")
        return
    
    if largest_val % 2 == 0:
        result = largest_val * 10
    elif largest_val:
        result = largest_val * 1000
    return result

    

assert largest_num_operation([1,2,3,4,5,6]) == 60, "Your function does not work for even numbers!"
assert largest_num_operation([1,2,3,4,5]) == 5000, "Your function does not work for odd numbers!"
largest_num_operation([-1,-2,-3,-4,-5,0])
please put in a more useful number, instead of 0
test_list = [1,2,3,4,5,6]

max(test_list)
6
  • Write a stub: a function that does nothing but accept all input parameters and return the correct datatype.

  • Write tests to satisfy the design specifications.

  • Outline the program with pseudo-code.

  • Write code and test frequently.

  • Write documentation.