{ "cells": [ { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" }, "user_expressions": [] }, "source": [ "# Class 2B: Review of Programming in Python II\n", "\n", "We will begin soon!\n", "\n", "\n", "\n", "
\n", "
\n", "
\n", " Photo by Christina Morillo from Pexels\n", "
\n", "\n", "[Download the (logistics) Slides from today](https://github.com/ubc-cs/cpsc203/raw/main/files/Lec04PythonReview2.pdf)\n", "\n", "
\n", " Firas Moosvi\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Lecture Outline\n", "\n", "- Comments (0 min)\n", "- Loops\n", "- Comprehensions\n", "- Break (5 min)\n", "- Functions\n", "- Exceptions\n", "- Testing\n", "- Questions and Recap" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Attribution\n", "\n", "- The original version of these Python lectures were by [Patrick Walls](https://www.math.ubc.ca/~pwalls/).\n", "- These lectures were originally delivered by [Mike Gelbart](https://mikegelbart.com) and are [available publicly here](https://www.youtube.com/watch?v=7FLv1ACEl-E&list=PLWmXHcz_53Q26aQzhknaT3zwWvl7w8wQE&index=2)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Comments in python" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [], "source": [ "x = 1 # 5 + 5 this is a comment" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [], "source": [ "# \"\"\"\n", "# this is a string, which does nothing\n", "# and can be used as a comment\n", "# \"\"\"\n", "\n", "7\n", "\n", "\n", "x = 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Loops" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Loops allow us to execute a block of code multiple times. \n", "- We will focus on [`for` loops](https://docs.python.org/3/tutorial/controlflow.html#for-statements)" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'I have 6 apples'" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"I have 5 apples\"\n", "\"I have 6 apples\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### `for` loops" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The number is 2 its square is 4\n", "The number is 7 its square is 49\n", "hallelujah\n", "The number is -1 its square is 1\n", "The number is 5 its square is 25\n", "hallelujah\n" ] } ], "source": [ "for n in [2, 7, -1, 5]:\n", " print(\"The number is\", n, \"its square is\", n**2)\n", " if n > 2:\n", " print(\"hallelujah\")\n", " # this is inside the loop\n", "# this is outside the loop" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32,\n", " 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66,\n", " 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98])" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import numpy as np\n", "np.arange(0,100,2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The main points to notice:\n", "\n", "* Keyword `for` begins the loop\n", "* Colon `:` ends the first line of the loop\n", "* We can iterate over any kind of iterable: list, tuple, range, string. In this case, we are iterating over the values in a list\n", "* Block of code indented is executed for each value in the list (hence the name \"for\" loops, sometimes also called \"for each\" loops)\n", "* The loop ends after the variable `n` has taken all the values in the list" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'abcdef'" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"abc\" + \"def\"" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Gimme a P!\n", "Gimme a y!\n", "Gimme a t!\n", "Gimme a h!\n", "Gimme a o!\n", "Gimme a n!\n", "\tWhat's that spell?!! Python!\n" ] } ], "source": [ "word = \"Python\"\n", "for letter in word:\n", " print(\"Gimme a \" + letter + \"!\")\n", "\n", "print(\"\\tWhat's that spell?!! \" + word + \"!\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- A very common pattern is to use `for` with `range`. \n", "- `range` gives you a sequence of integers up to some value." ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "6\n", "7\n", "8\n", "9\n" ] } ], "source": [ "for i in range(10):\n", " print(i)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also specify a start value and a skip-by value with `range`:" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n", "11\n", "21\n", "31\n", "41\n", "51\n", "61\n", "71\n", "81\n", "91\n" ] } ], "source": [ "for i in range(1,101,10):\n", " print(i)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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." ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(1, 'a')\n", "(1, 'b')\n", "(1, 'c')\n", "(2, 'a')\n", "(2, 'b')\n", "(2, 'c')\n", "(3, 'a')\n", "(3, 'b')\n", "(3, 'c')\n" ] } ], "source": [ "for x in [1,2,3]:\n", " for y in [\"a\",\"b\",\"c\"]:\n", " print((x,y))" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1 a\n", "2 b\n", "3 c\n", "4 d\n" ] }, { "ename": "IndexError", "evalue": "list index out of range", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mIndexError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[38], line 4\u001b[0m\n\u001b[1;32m 2\u001b[0m list_2 \u001b[38;5;241m=\u001b[39m [\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124ma\u001b[39m\u001b[38;5;124m\"\u001b[39m,\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mb\u001b[39m\u001b[38;5;124m\"\u001b[39m,\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mc\u001b[39m\u001b[38;5;124m\"\u001b[39m,\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124md\u001b[39m\u001b[38;5;124m\"\u001b[39m]\n\u001b[1;32m 3\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m i \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mrange\u001b[39m(\u001b[38;5;241m5\u001b[39m):\n\u001b[0;32m----> 4\u001b[0m \u001b[38;5;28mprint\u001b[39m(list_1[i], \u001b[43mlist_2\u001b[49m\u001b[43m[\u001b[49m\u001b[43mi\u001b[49m\u001b[43m]\u001b[49m)\n", "\u001b[0;31mIndexError\u001b[0m: list index out of range" ] } ], "source": [ "list_1 = [1,2,3,4,5]\n", "list_2 = [\"a\",\"b\",\"c\",\"d\"]\n", "for i in range(5):\n", " print(list_1[i], list_2[i])" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1 a\n", "2 b\n", "3 c\n", "4 d\n" ] } ], "source": [ "for i,j in zip(list_1,list_2):\n", " print(i,j)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can loop through key-value pairs of a dictionary using `.items()`:" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello\\nWorld\n" ] } ], "source": [ "print(\"Hello\\\\nWorld\")" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello \\n\n", "World\n" ] } ], "source": [ "h = \"\"\"Hello \\\\n\n", "World\"\"\"\n", "\n", "print(h)" ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "DSCI 521 is awesome\n", "DSCI 551 is riveting\n", "DSCI 511 is naptime!\n" ] } ], "source": [ "courses = {521 : \"awesome\",\n", " 551 : \"riveting\",\n", " 511 : \"naptime!\"}\n", "\n", "for course_num, description in courses.items():\n", " print(\"DSCI\", course_num, \"is\", description)" ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "521 awesome\n", "551 riveting\n", "511 naptime!\n" ] } ], "source": [ "for course_num,course_desc in courses.items():\n", " print(course_num, course_desc)\n", "# print(course_num, courses[course_num])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Above: the general syntax is `for key, value in dictionary.items():`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### `while` loops\n", "\n", "- We can also use a [`while` loop](https://docs.python.org/3/reference/compound_stmts.html#while) to excute a block of code several times. \n", "- In reality, I rarely use these.\n", "- Beware! If the conditional expression is always `True`, then you've got an infintite loop! \n", " - (Use the \"Stop\" button in the toolbar above, or Ctrl-C in the terminal, to kill the program if you get an infinite loop.)" ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "10\n", "9\n", "8\n", "7\n", "6\n", "5\n", "4\n", "3\n", "2\n", "1\n", "Blast off!\n" ] } ], "source": [ "n = 10\n", "while n > 0:\n", " print(n)\n", " n = n - 1\n", "\n", "print(\"Blast off!\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Comprehensions\n", "\n", "Comprehensions allow us to build lists/sets/dictionaries in one convenient, compact line of code." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### List Comprehensions\n", "\n", "List comprehensions are one of my favourite features of Python!" ] }, { "cell_type": "code", "execution_count": 50, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[0, 10, 20, 30, 40, 50, 60, 70, 80, 90]" ] }, "execution_count": 50, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[hh*10 for hh in range(10)]\n" ] }, { "cell_type": "code", "execution_count": 51, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [ { "data": { "text/plain": [ "['llo', 'bye', 'the', 'ism']" ] }, "execution_count": 51, "metadata": {}, "output_type": "execute_result" } ], "source": [ "words = [\"hello\", \"goodbye\", \"the\", \"antidisestablishmentarianism\"]\n", "\n", "y = [word[-3:] for word in words] # list comprehension\n", "y" ] }, { "cell_type": "code", "execution_count": 52, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "llo\n", "bye\n", "the\n", "ism\n" ] } ], "source": [ "for word in words:\n", " print(word[-3:])" ] }, { "cell_type": "code", "execution_count": 53, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['llo', 'bye', 'the', 'ism']" ] }, "execution_count": 53, "metadata": {}, "output_type": "execute_result" } ], "source": [ "y = list()\n", "for word in words:\n", " y.append(word[-3:])\n", "y" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "source": [ "### Dictionary comprehension" ] }, { "cell_type": "code", "execution_count": 54, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [ { "data": { "text/plain": [ "{'hello': 5, 'goodbye': 7, 'the': 3, 'antidisestablishmentarianism': 28}" ] }, "execution_count": 54, "metadata": {}, "output_type": "execute_result" } ], "source": [ "word_lengths = {word : len(word) for word in words} # dictionary comprehension\n", "word_lengths" ] }, { "cell_type": "code", "execution_count": 55, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'hello': 'HELLO',\n", " 'goodbye': 'GOODBYE',\n", " 'the': 'THE',\n", " 'antidisestablishmentarianism': 'ANTIDISESTABLISHMENTARIANISM'}" ] }, "execution_count": 55, "metadata": {}, "output_type": "execute_result" } ], "source": [ "word_upper = {word : word.upper() for word in words}\n", "word_upper" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Set Comprehensions" ] }, { "cell_type": "code", "execution_count": 56, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'o', 'm', 'e'}\n" ] } ], "source": [ "y = {word[-1] for word in words} # set comprehension\n", "print(y)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Tuple Comprehensions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Tuple comprehensions actually don't exist! But, you can use generators.\n", "\n", "See [this StackOverflow post](https://stackoverflow.com/questions/16940293/why-is-there-no-tuple-comprehension-in-python) for (some fun) details!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Break (5 min)" ] }, { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "## Functions\n", "\n", "- Define a [**function**](https://docs.python.org/3/tutorial/controlflow.html#defining-functions) to re-use a block of code with different input parameters, also known as **arguments**. \n", "- For example, define a function called `square` which takes one input parameter `n` and returns the square `n**2`." ] }, { "cell_type": "code", "execution_count": 57, "metadata": {}, "outputs": [], "source": [ "def square(n):\n", " n_squared = n**2\n", " return n_squared\n", "\n", "# quick test:\n", "assert square(2) ==4" ] }, { "cell_type": "code", "execution_count": 58, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "152399025" ] }, "execution_count": 58, "metadata": {}, "output_type": "execute_result" } ], "source": [ "square(12345)" ] }, { "cell_type": "code", "execution_count": 59, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4" ] }, "execution_count": 59, "metadata": {}, "output_type": "execute_result" } ], "source": [ "square(2)" ] }, { "cell_type": "code", "execution_count": 60, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "10000" ] }, "execution_count": 60, "metadata": {}, "output_type": "execute_result" } ], "source": [ "square(100)" ] }, { "cell_type": "code", "execution_count": 61, "metadata": {}, "outputs": [], "source": [ "# this will fail \n", "# square('hello')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* Begins with `def` keyword, function name, input parameters and then colon (`:`)\n", "* Function block defined by indentation\n", "* Output or \"return\" value of the function is given by the `return` keyword" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Side effects\n", "\n", "- If a function changes the variables passed into it, then it is said to have **side effects**\n", "- Example:" ] }, { "cell_type": "code", "execution_count": 62, "metadata": {}, "outputs": [], "source": [ "def silly_sum(sri):\n", " sri.append(0)\n", " return sum(sri)" ] }, { "cell_type": "code", "execution_count": 63, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "10" ] }, "execution_count": 63, "metadata": {}, "output_type": "execute_result" } ], "source": [ "silly_sum([1,2,3,4])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Looks good, like it sums the numbers? But wait...\n" ] }, { "cell_type": "code", "execution_count": 64, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "10" ] }, "execution_count": 64, "metadata": {}, "output_type": "execute_result" } ], "source": [ "lst = [1,2,3,4]\n", "silly_sum(lst)" ] }, { "cell_type": "code", "execution_count": 65, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "10" ] }, "execution_count": 65, "metadata": {}, "output_type": "execute_result" } ], "source": [ "silly_sum(lst)" ] }, { "cell_type": "code", "execution_count": 66, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[1, 2, 3, 4, 0, 0]" ] }, "execution_count": 66, "metadata": {}, "output_type": "execute_result" } ], "source": [ "lst" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- If your function has side effects like this, you must mention it in the documentation (later today).\n", "- More on how this works next class." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Null return type\n", "\n", "If you do not specify a return value, the function returns `None` when it terminates:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def f(x):\n", " x + 1 # no return!\n", " if x == 999:\n", " return\n", " else:\n", " return('hello')\n", "print(f(998))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Designing good functions - DRY principle (15 min)\n", "\n", "- DRY: **Don't Repeat Yourself**\n", "- See [Wikipedia article](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself)\n", "- Consider the task of, for each element of a list, turning it into a palindrome\n", " - e.g. \"mike\" --> \"mikeekim\"" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "names = [\"milad\", \"rodolfo\", \"tiffany\", \"khalad\", \"jeff\", \"christel\",\"firas\"]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "name = \"mike\"\n", "name[::-1]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "names_backwards = list()\n", "\n", "names_backwards.append(names[0] + names[0][::-1])\n", "names_backwards.append(names[1] + names[1][::-1])\n", "names_backwards.append(names[2] + names[2][::-1])\n", "names_backwards.append(names[3] + names[3][::-1])\n", "names_backwards.append(names[4] + names[4][::-1])\n", "names_backwards.append(names[5] + names[5][::-1])\n", "names_backwards.append(names[6] + names[6][::-1])\n", "\n", "\n", "names_backwards" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Above: this is gross, terrible, yucky code\n", " 1. It only works for a list with 3 elements\n", " 2. It only works for a list named `names`\n", " 3. If we want to change its functionality, we need to change 3 similar lines of code (Don't Repeat Yourself!!)\n", " 4. It is hard to understand what it does just by looking at it" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "names_backwards = list()\n", "\n", "for name in names:\n", " names_backwards.append(name + name[::-1])\n", " \n", "names_backwards" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Above: this is slightly better. We have solved problems (1) and (3)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def make_palindromes(names):\n", " names_backwards = list()\n", " \n", " for name in names:\n", " names_backwards.append(name + name[::-1])\n", " \n", " return names_backwards" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "p_name = make_palindromes(names)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "for p in p_name:\n", " print(p.upper())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Above: this is even better. We have now also solved problem (2), because you can call the function with any list, not just `names`. \n", "- For example, what if we had multiple _lists_:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "names1 = [\"milad\", \"rodolfo\", \"tiffany\"]\n", "names2 = [\"Trudeau\", \"Scheer\", \"Singh\", \"Blanchet\", \"May\"]\n", "names3 = [\"apple\", \"orange\", \"banana\"]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "names_backwards_1 = list()\n", "\n", "for name in names1:\n", " names_backwards_1.append(name + name[::-1])\n", " \n", "names_backwards_1" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "names_backwards_2 = list()\n", "\n", "for name in names2:\n", " names_backwards_2.append(name + name[::-1])\n", " \n", "names_backwards_2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "names_backwards_3 = list()\n", "\n", "for name in names3:\n", " names_backwards_3.append(name + name[::-1])\n", " \n", "names_backwards_3" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "make_palindromes(names1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "make_palindromes(names2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "make_palindromes(names3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- You could get even more fancy, and put the lists of names into a list (so you have a list of lists). \n", "- Then you could loop over the list and call the function each time:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "for list_of_names in [names1, names2, names3]:\n", " print(make_palindromes(list_of_names))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Designing good functions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 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!\n", "- These decisions are often ambiguous. For example: \n", " - Should `make_palindromes` be a function if I'm only ever doing it once? Twice?\n", " - Should the loop be inside the function, or outside?\n", " - Or should there be TWO functions, one that loops over the other??" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- In my personal opinion, `make_palindromes` does a bit too much to be understandable.\n", "- I prefer this:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def make_palindrome(name):\n", " return name + name[::-1]\n", "\n", "make_palindrome(\"milad\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- From here, we want to \"apply `make_palindrome` to every element of a list\"\n", "- It turns out this is an extremely common desire, so Python has built-in functions.\n", "- One of these is `map`, which we'll cover later. But for now, just a comprehension will do:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "[make_palindrome(name) for name in names]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Other function design considerations:\n", "\n", "- Should we print output or produce plots inside or outside functions? \n", " - I would usually say outside, because this is a \"side effect\" of sorts\n", "- Should the function do one thing or many things?\n", " - This is a tough one, hard to answer in general" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Optional & keyword arguments\n", "\n", "- Sometimes it is convenient to have _default values_ for some arguments in a function. \n", "- Because they have default values, these arguments are optional, hence \"optional arguments\"\n", "- Example:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def repeat_string(s, n=2):\n", " return s*n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "repeat_string(\"mds\", 2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "repeat_string(\"mds\", 5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Sane defaults:\n", "\n", "- Ideally, the default should be carefully chosen. \n", "- Here, the idea of \"repeating\" something makes me think of having 2 copies, so `n=2` feels like a sane default." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Syntax:\n", "\n", "- You can have any number of arguments and any number of optional arguments\n", "- **All the optional arguments must come after the regular arguments\n", "- The regular arguments are mapped by the order they appear\n", "- The optional arguments can be specified out of order" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def example(a, b, c=\"DEFAULT\", d=\"DEFAULT\"):\n", " print(a,b,c,d)\n", " \n", "example(1,2,3,4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Using the defaults for `c` and `d`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "example(1,2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Specifying `c` and `d` as **keyword arguments** (i.e. by name):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "example(1,2,c=3,d=4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Specifying only one of the optional arguments, by keyword:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "example(1,2,c=3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Or the other:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "example(1,2,d=4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Specifying all the arguments as keyword arguments, even though only `c` and `d` are optional:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "example(a=1,b=2,c=3,d=4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Specifying `c` by the fact that it comes 3rd (I do not recommend this because I find it is confusing):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "example(1,2,3) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "example(1,2,d=4,c=3) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Specifying the non-optional arguments by keyword (I am fine with this):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "example(a=1,b=2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Specifying the non-optional arguments by keyword, but in the wrong order (not recommended, I find it confusing):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "example(b=2,a=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Specifying keyword arguments before non-keyword arguments (this throws an error):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "example(a=2,b=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- In general, I am used to calling non-optional arguments by order, and optional arguments by keyword.\n", "- The language allows us to deviate from this, but it can be unnecessarily confusing sometimes." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### `kwargs`:\n", "\n", "- You can also call/define functions with `*args` and `**kwargs`; see, e.g. [here](https://realpython.com/python-kwargs-and-args/)\n", "- Do not instantiate objects in the function definition - see [here](https://docs.python-guide.org/writing/gotchas/) under \"Mutable Default Arguments\"" ] }, { "cell_type": "code", "execution_count": 67, "metadata": {}, "outputs": [], "source": [ "def example(a, b=[]): # don't do this!\n", " return 0" ] }, { "cell_type": "code", "execution_count": 68, "metadata": {}, "outputs": [], "source": [ "def example(a, b=None): # insted, do this\n", " if b is None:\n", " b = []\n", " return 0" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Anonymous functions (5 min)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There are two ways to define functions in Python:" ] }, { "cell_type": "code", "execution_count": 69, "metadata": {}, "outputs": [], "source": [ "def add_one(x):\n", " return x+1" ] }, { "cell_type": "code", "execution_count": 70, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "8.2" ] }, "execution_count": 70, "metadata": {}, "output_type": "execute_result" } ], "source": [ "add_one(7.2)" ] }, { "cell_type": "code", "execution_count": 71, "metadata": {}, "outputs": [], "source": [ "add_one = lambda x: x+1 " ] }, { "cell_type": "code", "execution_count": 72, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "function" ] }, "execution_count": 72, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(add_one)" ] }, { "cell_type": "code", "execution_count": 73, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "8.2" ] }, "execution_count": 73, "metadata": {}, "output_type": "execute_result" } ], "source": [ "add_one(7.2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The two approaches above are identical. The one with `lambda` is called an **anonymous function**.\n", "\n", "Some differences:\n", "\n", "- anonymous functions can only take up one line of code, so they aren't appropriate in most cases.\n", "- anonymous functions evaluate to a function (remember, functions are first-class objects) immediate, so we can do weird stuff with them." ] }, { "cell_type": "code", "execution_count": 74, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "13" ] }, "execution_count": 74, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(lambda x,y: x+y)(6,7)" ] }, { "cell_type": "code", "execution_count": 75, "metadata": {}, "outputs": [ { "ename": "NameError", "evalue": "name 'evaluate_function_on_x_plus_1' is not defined", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[75], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mevaluate_function_on_x_plus_1\u001b[49m(\u001b[38;5;28;01mlambda\u001b[39;00m x: x\u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39m\u001b[38;5;241m2\u001b[39m, \u001b[38;5;241m5\u001b[39m)\n", "\u001b[0;31mNameError\u001b[0m: name 'evaluate_function_on_x_plus_1' is not defined" ] } ], "source": [ "evaluate_function_on_x_plus_1(lambda x: x**2, 5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Above:\n", "\n", "- First, `lambda x: x**2` evaluates to a value of type `function`\n", " - Notice that this function is never given a name - hence \"anonymous functions\" !\n", "- Then, the function and the integer `5` are passed into `evaluate_function_on_x_plus_1`\n", "- At which point the anonymous function is evaluated on `5+1`, and we get `36`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exceptions, `try`/`except` (10 min)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- If something goes wrong, we don't want the code to crash - we want it to **fail gracefully**.\n", "- In Python, this can be accomplished using `try`/`except`:\n", "- Here is a basic example:" ] }, { "cell_type": "code", "execution_count": 76, "metadata": {}, "outputs": [ { "ename": "NameError", "evalue": "name 'this_variable_does_not_exist' is not defined", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[76], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mthis_variable_does_not_exist\u001b[49m\n", "\u001b[0;31mNameError\u001b[0m: name 'this_variable_does_not_exist' is not defined" ] } ], "source": [ "this_variable_does_not_exist" ] }, { "cell_type": "code", "execution_count": 77, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "You did something bad!\n" ] } ], "source": [ "try:\n", " this_variable_does_not_exist\n", "except:\n", "# pass\n", " print(\"You did something bad!\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Python tries to execute the code in the `try` block.\n", "- If an error is encountered, we \"catch\" this in the `except` block (also called `try`/`catch` in other languages).\n", "- There are many different error types, or **exceptions** - we saw `NameError` above. " ] }, { "cell_type": "code", "execution_count": 78, "metadata": {}, "outputs": [ { "ename": "ZeroDivisionError", "evalue": "division by zero", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mZeroDivisionError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[78], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;241;43m5\u001b[39;49m\u001b[38;5;241;43m/\u001b[39;49m\u001b[38;5;241;43m0\u001b[39;49m\n", "\u001b[0;31mZeroDivisionError\u001b[0m: division by zero" ] } ], "source": [ "5/0" ] }, { "cell_type": "code", "execution_count": 79, "metadata": {}, "outputs": [ { "ename": "IndexError", "evalue": "list index out of range", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mIndexError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[79], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m my_list \u001b[38;5;241m=\u001b[39m [\u001b[38;5;241m1\u001b[39m,\u001b[38;5;241m2\u001b[39m,\u001b[38;5;241m3\u001b[39m]\n\u001b[0;32m----> 2\u001b[0m \u001b[43mmy_list\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m5\u001b[39;49m\u001b[43m]\u001b[49m\n", "\u001b[0;31mIndexError\u001b[0m: list index out of range" ] } ], "source": [ "my_list = [1,2,3]\n", "my_list[5]" ] }, { "cell_type": "code", "execution_count": 80, "metadata": {}, "outputs": [ { "ename": "IndexError", "evalue": "list index out of range", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mIndexError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[80], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# (note: this is also valid syntax, just very confusing)\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m \u001b[43m[\u001b[49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m,\u001b[49m\u001b[38;5;241;43m2\u001b[39;49m\u001b[43m,\u001b[49m\u001b[38;5;241;43m3\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m5\u001b[39;49m\u001b[43m]\u001b[49m\n", "\u001b[0;31mIndexError\u001b[0m: list index out of range" ] } ], "source": [ "# (note: this is also valid syntax, just very confusing)\n", "[1,2,3][5]" ] }, { "cell_type": "code", "execution_count": 81, "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "'tuple' object does not support item assignment", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[81], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m my_tuple \u001b[38;5;241m=\u001b[39m (\u001b[38;5;241m1\u001b[39m,\u001b[38;5;241m2\u001b[39m,\u001b[38;5;241m3\u001b[39m)\n\u001b[0;32m----> 2\u001b[0m \u001b[43mmy_tuple\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m]\u001b[49m \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m0\u001b[39m\n", "\u001b[0;31mTypeError\u001b[0m: 'tuple' object does not support item assignment" ] } ], "source": [ "my_tuple = (1,2,3)\n", "my_tuple[0] = 0" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Ok, so there are apparently a bunch of different errors one could run into. \n", "- With `try`/`except` you can also catch the exception itself:" ] }, { "cell_type": "code", "execution_count": 82, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "You did something bad!\n", "name 'this_variable_does_not_exist' is not defined\n", "\n" ] } ], "source": [ "try:\n", " this_variable_does_not_exist\n", "except Exception as ex:\n", " print(\"You did something bad!\")\n", " print(ex)\n", " print(type(ex))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- In the above, we caught the exception and assigned it to the variable `ex` so that we could print it out.\n", "- This is useful because you can see what the error message would have been, without crashing your program." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- You can also catch specific exceptions types, like so:" ] }, { "cell_type": "code", "execution_count": 83, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "You made a name error!\n" ] } ], "source": [ "try:\n", " this_variable_does_not_exist\n", "except TypeError:\n", " print(\"You made a type error!\")\n", "except NameError:\n", " print(\"You made a name error!\")\n", "except:\n", " print(\"You made some other sort of error\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- The final `except` would trigger if the error is none of the above types, so this sort of has an `if`/`elif`/`else` feel to it. \n", "- There are some extra features, in particular an `else` and `finally` block; if you are interested, see e.g., [here](https://www.w3schools.com/python/python_try_except.asp)." ] }, { "cell_type": "code", "execution_count": 84, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "You made some other sort of error\n" ] } ], "source": [ "try:\n", " 5/0\n", "except TypeError:\n", " print(\"You made a type error!\")\n", "except NameError:\n", " print(\"You made a name error!\")\n", "except Exception as ex:\n", " print(\"You made some other sort of error\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Ideally, try to make your `try`/`except` blocks specific, and try not to put more errors inside the `except`... " ] }, { "cell_type": "code", "execution_count": 85, "metadata": {}, "outputs": [ { "ename": "ZeroDivisionError", "evalue": "division by zero", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[85], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m----> 2\u001b[0m \u001b[43mthis_variable_does_not_exist\u001b[49m\n\u001b[1;32m 3\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m:\n", "\u001b[0;31mNameError\u001b[0m: name 'this_variable_does_not_exist' is not defined", "\nDuring handling of the above exception, another exception occurred:\n", "\u001b[0;31mZeroDivisionError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[85], line 4\u001b[0m\n\u001b[1;32m 2\u001b[0m this_variable_does_not_exist\n\u001b[1;32m 3\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m:\n\u001b[0;32m----> 4\u001b[0m \u001b[38;5;241;43m5\u001b[39;49m\u001b[38;5;241;43m/\u001b[39;49m\u001b[38;5;241;43m0\u001b[39;49m\n", "\u001b[0;31mZeroDivisionError\u001b[0m: division by zero" ] } ], "source": [ "try:\n", " this_variable_does_not_exist\n", "except:\n", " 5/0" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- This is a bit much, but it does happen sometimes :(" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Using `raise`\n", "\n", "- You can also write code that raises an exception on purpose, using `raise`" ] }, { "cell_type": "code", "execution_count": 86, "metadata": {}, "outputs": [], "source": [ "def add_one(x):\n", " return x+1" ] }, { "cell_type": "code", "execution_count": 87, "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "can only concatenate str (not \"int\") to str", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[87], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43madd_one\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mblah\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n", "Cell \u001b[0;32mIn[86], line 2\u001b[0m, in \u001b[0;36madd_one\u001b[0;34m(x)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21madd_one\u001b[39m(x):\n\u001b[0;32m----> 2\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mx\u001b[49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[38;5;241;43m1\u001b[39;49m\n", "\u001b[0;31mTypeError\u001b[0m: can only concatenate str (not \"int\") to str" ] } ], "source": [ "add_one(\"blah\")" ] }, { "cell_type": "code", "execution_count": 88, "metadata": {}, "outputs": [], "source": [ "def add_one(x):\n", " if not isinstance(x, float) and not isinstance(x, int):\n", " raise Exception(\"Sorry, x must be numeric\")\n", " \n", " return x+1" ] }, { "cell_type": "code", "execution_count": 89, "metadata": {}, "outputs": [ { "ename": "Exception", "evalue": "Sorry, x must be numeric", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mException\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[89], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43madd_one\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mblah\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n", "Cell \u001b[0;32mIn[88], line 3\u001b[0m, in \u001b[0;36madd_one\u001b[0;34m(x)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21madd_one\u001b[39m(x):\n\u001b[1;32m 2\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(x, \u001b[38;5;28mfloat\u001b[39m) \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(x, \u001b[38;5;28mint\u001b[39m):\n\u001b[0;32m----> 3\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mSorry, x must be numeric\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 5\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m x\u001b[38;5;241m+\u001b[39m\u001b[38;5;241m1\u001b[39m\n", "\u001b[0;31mException\u001b[0m: Sorry, x must be numeric" ] } ], "source": [ "add_one(\"blah\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- This is useful when your function is complicated and would fail in a complicated way, with a weird error message.\n", "- You can make the cause of the error much clearer to the _caller_ of the function.\n", "- Thus, your function is more usable this way.\n", "- If you do this, you should ideally describe these exceptions in the function documentation, so a user knows what to expect if they call your function. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- You can also raise other types of exceptions, or even define your own exception types, as in lab 2.\n", "- You can also use `raise` by itself to raise whatever exception was going on:" ] }, { "cell_type": "code", "execution_count": 90, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "You did something bad!\n" ] }, { "ename": "NameError", "evalue": "name 'this_variable_does_not_exist' is not defined", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[90], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m----> 2\u001b[0m \u001b[43mthis_variable_does_not_exist\u001b[49m\n\u001b[1;32m 3\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m:\n\u001b[1;32m 4\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mYou did something bad!\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", "\u001b[0;31mNameError\u001b[0m: name 'this_variable_does_not_exist' is not defined" ] } ], "source": [ "try:\n", " this_variable_does_not_exist\n", "except:\n", " print(\"You did something bad!\")\n", " raise" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Here, the original exception is raised after we ran some other code." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Testing" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### `assert` statements\n", "\n", "- `assert` statementS cause your program to fail if the condition is `False`.\n", "- They can be used as sanity checks for your program.\n", "- There are more sophisticated way to \"test\" your programs, which we'll discuss in Workflows.\n", "- The syntax is:\n", "\n", "```python\n", "assert expression , \"Error message if expression is False or raises an error.\"\n", "```" ] }, { "cell_type": "code", "execution_count": 91, "metadata": {}, "outputs": [ { "ename": "AssertionError", "evalue": "4 is not equal to 5.", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[91], line 5\u001b[0m\n\u001b[1;32m 2\u001b[0m b \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m5\u001b[39m\n\u001b[1;32m 4\u001b[0m \u001b[38;5;66;03m# Change this assert statement so a and b is used instead of \"hard-coded 1 and 2\"\u001b[39;00m\n\u001b[0;32m----> 5\u001b[0m \u001b[38;5;28;01massert\u001b[39;00m a \u001b[38;5;241m==\u001b[39m b , \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00ma\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m is not equal to \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mb\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m.\u001b[39m\u001b[38;5;124m\"\u001b[39m\n", "\u001b[0;31mAssertionError\u001b[0m: 4 is not equal to 5." ] } ], "source": [ "a = 4\n", "b = 5\n", "\n", "# Change this assert statement so a and b is used instead of \"hard-coded 1 and 2\"\n", "assert a == b , f\"{a} is not equal to {b}.\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Systematic Program Design" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A systematic approach to program design is a general set of steps to follow when writing programs. Our approach includes:\n", "\n", "1. Write a stub: a function that does nothing but accept all input parameters and return the correct datatype.\n", "2. Write tests to satisfy the design specifications.\n", "3. Outline the program with pseudo-code.\n", "4. Write code and test frequently.\n", "5. Write documentation.\n", "\n", "The key point: write tests BEFORE you write code.\n", "\n", "- You do not have to do this in MDS, but you may find it surprisingly helpful.\n", "- Often writing tests helps you think through what you are trying to accomplish.\n", "- It's best to have that clear before you write the actual code." ] }, { "cell_type": "code", "execution_count": 105, "metadata": {}, "outputs": [], "source": [ "# Task: Let's calculate area of a rectangle, given length and width" ] }, { "cell_type": "code", "execution_count": 106, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "the area should be : 25 and is 25\n", "the area should be : 25 and is 25\n" ] }, { "data": { "text/plain": [ "25" ] }, "execution_count": 106, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def calculate_rectangle_area(length, width):\n", " \"\"\" Computes the area of a rectangle given a length and a width\n", " inputs: length (float) and width(float)\n", " \n", " oututs: area (float)\n", " \"\"\"\n", " \n", " # Check the inputs\n", " # check if length and width are floats\n", " \n", " # Compute area\n", " area = length * width\n", " \n", " print(f\"the area should be : {length * width} and is {area}\")\n", " \n", " return area\n", "\n", "assert calculate_rectangle_area(5,5) == 25, \"the function is not quite correct because the area of a 5x5 should be 25.\"\n", "calculate_rectangle_area(5,5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Testing woes - false positives\n", "\n", "- **Just because all your tests pass, this does not mean your program is correct!!**\n", "- This happens all the time. How to deal with it?\n", " - Write a lot of tests!\n", " - Don't be overconfident, even after writing a lot of tests!" ] }, { "cell_type": "code", "execution_count": 101, "metadata": {}, "outputs": [], "source": [ "def sample_median(x):\n", " \"\"\"Finds the median of a list of numbers.\"\"\"\n", " x_sorted = sorted(x)\n", " return x_sorted[len(x_sorted)//2]\n", "\n", "assert sample_median([1,2,3,4,5]) == 3\n", "assert sample_median([0,0,0,0]) == 0" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Looks good? ... ?\n", "\n", "




" ] }, { "cell_type": "code", "execution_count": 102, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2" ] }, "execution_count": 102, "metadata": {}, "output_type": "execute_result" } ], "source": [ "4//2 \n", "\n", "#is equivalent to\n", "\n", "int(4/2)" ] }, { "cell_type": "code", "execution_count": 103, "metadata": {}, "outputs": [ { "ename": "AssertionError", "evalue": "", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[103], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;28;01massert\u001b[39;00m sample_median([\u001b[38;5;241m1\u001b[39m,\u001b[38;5;241m2\u001b[39m,\u001b[38;5;241m3\u001b[39m,\u001b[38;5;241m4\u001b[39m]) \u001b[38;5;241m==\u001b[39m \u001b[38;5;241m2.5\u001b[39m\n", "\u001b[0;31mAssertionError\u001b[0m: " ] } ], "source": [ "assert sample_median([1,2,3,4]) == 2.5" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "




" ] }, { "cell_type": "code", "execution_count": 100, "metadata": {}, "outputs": [], "source": [ "assert sample_median([1,3,2]) == 2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Testing woes - false negatives\n", "\n", "- It can also happen, though more rarely, that your tests fail but your program is correct.\n", "- This means there is something wrong with your test.\n", "- For example, in the autograding for lab1 this happened to some people, because of tiny roundoff errors." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Corner cases\n", "\n", "- A **corner case** is an input that is reasonable but a bit unusual, and may trip up your code.\n", "- For example, taking the median of an empty list, or a list with only one element. \n", "- Often it is desirable to add test cases to address corner cases." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "assert sample_median([1]) == 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- In this case the code worked with no extra effort, but sometimes we need `if` statements to handle the weird cases.\n", "- Sometimes we **want** the code to throw an error (e.g. median of an empty list)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Questions and Recap?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.2" } }, "nbformat": 4, "nbformat_minor": 4 }