4 "cell_type": "markdown",
9 "# Python Tutorial II: Logic, functions, and loops\n",
10 "In this lecture, we will explore how to control workflow in a code using conditionals and loops.,\n",
11 "For more information, we recommend reviewing some of the online documentation available at https://docs.python.org/3/tutorial/controlflow.html.\n",
12 "We will also see how to make code more modular by creating user-defined functions and modules."
17 "execution_count": null,
23 "import numpy as np #We will use numpy in this lecture"
27 "cell_type": "markdown",
32 "## Conditionals & Comparisons in Python\n",
34 "### Conditionals in Python \n",
35 "0. In Python, there are no \"case\" or \"switch\" options that are available in other languages, but we can get the same functionality by using dictionary mappings. However, this is beyond the scope of this tutorial. \n",
36 "1. You will find that most things can be done using the if/elif/else syntax:\n",
40 "elif condition: #elif is interpreted as \"else if\"\n",
46 "***All parts of the conditional are indented.*** \n",
47 "Unlike other languages that use terms like \"end\" or \"end if\" (or perhaps make use of brackets like \"{ }\") to signify the block of code corresponding to an if-elseif-else, ***Python interprets everything in terms of indenting.*** \n",
48 "This is also true in for-loops as we will see below.\n",
50 "### Comparison operators (*LET a=3, b=5*)\n",
51 "==:\tIf the values of two operands are equal, then the condition becomes true.\t*(a == b) is not true.*\n",
53 "!=:\tIf values of two operands are not equal, then condition becomes true. *(a != b) is true.* (Alternatively, one could use `<>` in place of `!=`, but this is not very common.)\n",
55 " \">\":\tIf the value of left operand is greater than the value of right operand, then condition becomes true.\t*(a > b) is not true.*\n",
57 "\"<\": If the value of left operand is less than the value of right operand, then condition becomes true.\t*(a < b) is true.*\n",
59 "\">=\":\tIf the value of left operand is greater than or equal to the value of right operand, then condition becomes true.\t*(a >= b) is not true.*\n",
61 "\"<=\":\tIf the value of left operand is less than or equal to the value of right operand, then condition becomes true.\t*(a <= b) is true.*\n",
63 "We can use **and** and **or** to combine sets of comparison operators and **not** to negate a statement. \n",
65 "Try changing different values of `x` in the code snippets below and see what happens."
70 "execution_count": null,
76 "x = 1.3 # Try different values\n",
78 "if x >= 0 and not(x == 2 or x == 3):\n",
79 " f = np.power(x,.5)/((x-2.0)*(x-3.0))\n",
82 " print( 'Square root of negative number' )\n",
85 " print( 'Division by zero with different limits' )\n",
90 "cell_type": "markdown",
95 "nan is a special value to signal the result of an invalid operation:"
100 "execution_count": null,
106 "a = np.float('inf')\n",
112 "cell_type": "markdown",
117 "Note that nan does not equal to anything, not even itself!"
121 "cell_type": "markdown",
126 "## Mini-exercise 1\n",
128 "Complete the code below to evaluate\n",
130 " f(x) = \\begin{cases}\n",
131 " 1 + x, & x\\in[-1,0], \\\\\n",
132 " 1 - x, & x\\in(0, 1], \\\\\n",
133 " 0, & \\text{else}.\n",
140 "execution_count": null,
146 "x = -.25 # Try different values\n",
148 "if -1 <= x and :\n",
159 "cell_type": "markdown",
164 "## Functions in Python (Motivation) \n",
165 "In the code snippets above, a value of `x` serves as the input into a conditional statement that determines what output value `f` should be assigned based on the value of `x`. If we wish to use this functionality many times in the code, we would probably like to avoid writing the if/elif/else structure at each point where it is to be used for a variety of reasons including, but not limited to, the following:\n",
167 "- If we ever decide to change how `f` is computed, then we would have to find/replace every instance of it within the code (likely leading to errors, or worse yet, code that does not crash but gives wrong outputs).\n",
169 "- Even the most terse scientific code can easily become hundreds (if not thousands) of lines long, and we want to avoid making the code more difficult to read, use, and debug than is absolutely necessary. \n",
171 "This motivates the development of user-defined functions in Python. The basic syntax is shown below.\n",
174 "def functionname( parameters ):\n",
175 " \"\"\"function_docstring\"\"\"\n",
177 " return [expression]\n",
182 "cell_type": "markdown",
187 "### A brief discussion on docstrings and commenting in code\n",
188 "The (triple) quotes is where you put in a documentation string for your function. It is entirely optional, but it is always a good idea to document your code even when it is entirely in the developmental/testing phase. There are some best practices that you can read about at https://docs.python.org/devguide/documenting.html or http://docs.python-guide.org/en/latest/writing/documentation/. \n",
190 "Good tools such as Sphinx http://www.sphinx-doc.org/en/1.4.8/ can turn properly documented code into easy to read/navigate html files to help expand the community of users for any code you develop. For example, see http://ut-chg.github.io/BET/ where Sphinx was used to generate the documentation. These tools are outside the scope of this tutorial, but we highly recommend that you learn a bit about them before attemping to make very sophisticated software packages. "
194 "cell_type": "markdown",
199 "### parameters and keyword arguments in a function\n",
200 "Notice that in the definition of the function, there is a `parameters` variable, which is often a list of parameters (as shown below). These are normally ordered **UNLESS** you supplement them with *keyword args* in the function call (i.e., when you actually use the function you may specify which argument corresponds to which parameter). \n",
201 "The next few code snippet illustrates this."
206 "execution_count": null,
212 "def myfun1(x,y):\n",
222 "execution_count": null,
228 "print( myfun1(2,3) )"
233 "execution_count": null,
239 "print( myfun1(2.0,3.0) )"
244 "execution_count": null,
250 "print( myfun1(3.0,2.0) ) #switching order of inputs"
255 "execution_count": null,
261 "print( myfun1(x=2,y=3.0) ) #keyword argument"
266 "execution_count": null,
272 "print( myfun1(y=3.0,x=2.0) ) #switching the order of inputs of keyword arguments does nothing"
277 "execution_count": null,
283 "# Try printing myfun1(x=2,3.0). \n",
285 "print( myfun1(x=2,3.0) )\n",
287 "# The take home message? \n",
288 "# Once you commit to using keywords in a function call, \n",
289 "# then you better be all in."
294 "execution_count": null,
300 "print( myfun1('silly ','test') )"
305 "execution_count": null,
316 "cell_type": "markdown",
321 "Obviously Python does not use type-checking for functions, but it allows useful types of polymorphism.\n",
323 "Python also allows to set defaults within the parameter list of a function call. Let's tweak `myfun1` a little.\n",
325 "Defaults need to **come after** functions parameters with non-default values."
330 "execution_count": null,
336 "def myfun2(x=1,y=2):\n",
346 "execution_count": null,
348 "id": "t1-p00wP7Ry9",
353 "print( myfun2() )\n",
354 "print( myfun2(1.0) )\n",
355 "print( myfun2(y=3) )"
359 "cell_type": "markdown",
364 "**We can even pass the output of a function to another function!**"
369 "execution_count": null,
375 "print( myfun1( myfun2(), myfun2(y=3) ) )"
379 "cell_type": "markdown",
389 "execution_count": null,
397 "print( myfun1( a, b ) )"
401 "cell_type": "markdown",
406 "## Mini-exercise 2\n",
408 "Complete the code below that either converts a temperature given in Celsius to Fahrenheit, a temperature given in Fahrenheit to Celsius, or if two temperatures are given (one in Celsius and one in Fahrenheit) will check if they are the same."
413 "execution_count": null,
416 "base_uri": "https://localhost:8080/",
419 "id": "PuuNBNdU7Ry-",
420 "outputId": "39079b42-99db-4e8b-97d3-cd6a5936c102"
424 "def tempFunc(F=None, C=None):\n",
425 " if F == None and C == None:\n",
426 " print('You want nothing?')\n",
427 " elif F == None: #So C is given\n",
428 " print( str(C) + ' Celsius = ' \n",
429 " + str(C * 9/5 + 32) + ' Fahrenheit' )\n",
430 " elif : #So F is given\n",
432 " else: #So F and C are both given\n",
433 " if np.abs(F - (C*9/5+32)) < np.finfo(np.float32).eps:\n",
434 " print('Those temperatures are the same!')\n",
436 " print('Those temperatures are different!')"
441 "execution_count": null,
447 "tempFunc(F=212, C = 100)\n",
452 "cell_type": "markdown",
457 "## Python uses passing by reference OR passing by value\n",
459 "First, we want to say that it is okay if this does not make perfect sense the first (ten) time(s) you work through this. \n",
460 "For more information on this, we encourage you to do some searching online (the answers provided here: https://stackoverflow.com/questions/373419/whats-the-difference-between-passing-by-reference-vs-passing-by-value are a good place to start) and playing around with the simple code below to build intuition.\n",
461 "***Basically, there is very little substitute for some time and patience in learning this correctly. Here, we seek to just build some basic intuition about what is going on so that you can be aware of some potential pitfalls.***\n",
463 "### If you know C or C++...\n",
464 "Passing by reference is similar passing a pointer to a variable instead of copying the value of the variable. But in Python, passing by reference can occur without creating the pointer explicitly, as it is done in C or C++. \n",
466 "### Metaphorically speaking...\n",
467 "Passing by reference or by value frequently occurs in calling functions. Suppose you are the function and I am passing you information metaphorically in terms of information written on paper:\n",
469 " * Call by value is where I copy something from one piece of paper onto another piece of paper and hand the copy to you. Maybe the information includes that `x=5`. This information is now on a piece of paper which I have given to you, so now it is effectively your piece of paper. You are now free to write on that piece of paper to use that information however you please. Maybe you decide to act upon this information by multiplying the variable `x` by 2, so that you write that now `x=10` on your piece of paper. Suppose you return that piece of paper to me. Does this change what was written on my piece of paper? No! However, I may choose to use your information to then update the information on my original paper.\n",
471 " * Call by reference is when I give you my original paper which has something written down in it like `x=5`. Now, if you decide that the value of `x` should be double so that you erase and replace `x=5` with `x=10` and then give me the paper back, my paper now contains this updated information about `x` regardless if I wanted it to or not. I guess if I did not want that information to be there, then I should have passed by value.\n",
473 "### Technically speaking...\n",
474 "#### *Passing by reference*\n",
475 "This means Python passes the reference to the variable, not just the value. This can cause some different behavior when certain *in place* operators are used. Classes, numpy arrays, etc. are passed by reference.\n",
477 "#### *Passing by value* \n",
478 "This means Python passes the value and creates a new copy. Variables that are strings, floats, and ints are passed by value (*they are immutable data types* meaning that the value is left unchanged).\n",
480 "Python variables created within a function also have local *scope*.\n",
481 "- *scope* usually refers to the visibility of variables. In other words, which parts of your program can see or use it. ***Local scope*** means visible only within the function. "
486 "execution_count": null,
492 "def scope_test(var):\n",
494 " print( 'The variable passed to scope_test is ' +\n",
495 " '\\n var = ', var)\n",
496 " var *= 4 #if var is mutable, replaces in place (pass-by-reference) by var*4\n",
498 " print( 'Within scope_test, the passed variable is ' +\n",
499 " 'changed to \\n var = ', var)\n",
502 " print('Within scope_test, we set the variable \\n a =', a)\n",
508 "execution_count": null,
514 "a = 2 #An integer is an immutable data type\n",
516 "print( 'Before scope_test,\\n a =', a )\n",
519 "print( 'After scope_test,\\n a =', a )"
523 "cell_type": "markdown",
528 "You see that even if variable a was passed to the function and changed inside, and a variable with the same name a was referred to in the function, the value of the variable a outside of the function **did not change**. This happened because the variable a was integer, which is in Python **immutable** (like a constant). But numpy arrays behave differently."
533 "execution_count": null,
539 "a = np.ones([2,2]) # numpy arrays are mutable\n",
541 "print( 'Before scope_test, \\n a =', a )\n",
544 "print( 'After scope_test, \\n a =', a )"
549 "execution_count": null,
555 "a = np.ones([2,2]) # numpy arrays are mutable\n",
557 "print( 'Before scope_test, \\n a =', a )\n",
560 "print( 'After scope_test(2*a), \\n a =', a )"
564 "cell_type": "markdown",
569 "### Wait a minute...\n",
571 "What if I want to do local work to a *mutable* data type (i.e. a numpy array) but not have the change reflected back after function exit? \n",
573 "The answer is to ***not*** use *in place* operators like +=, \\*=, etc. `var = var*2` creates a local copy of var and multiplies by 2 every entry."
578 "execution_count": null,
584 "def scope_test2(var):\n",
585 " var = var * 2 #if mutable, creates local copy of var.\n",
586 " print('Inside scope_test2,\\n',var)\n",
592 "execution_count": null,
595 "base_uri": "https://localhost:8080/"
597 "id": "jFAWgBT07RzA",
598 "outputId": "0c160997-9676-4c21-a8ae-26c22dc05f18"
602 "a = np.eye(3) # creates 3x3 array with a_ii = 1, 0 otherwise.\n",
603 "print('Before scope_test2,\\n a =', a)\n",
606 "print('After scope_test2,\\n a =', a)"
610 "cell_type": "markdown",
615 "## Looping in Python\n",
621 "for iterator in list:\n",
622 " #indent for the loop\n",
623 " #do cool stuff in the loop\n",
624 "#noindent to close the loop'\n",
626 "The list can be strings, for example:\n",
628 "for string in ('Alpha','Romeo','Sailor','Foxtrot'):\n",
629 " #string takes on values 'Alpha', 'Romeo', etc. in order.\n",
634 "You will commonly use the ``range`` command to build lists of numbers for iterating (see https://docs.python.org/3/library/functions.html#func-range).\n",
638 "range(stop) #assumes start=0\n",
639 "range(start, stop[, step])\n",
642 "Note that it **DOES NOT** execute the stop value.\n",
644 "Let's sum the first 20 terms of the geometric series corresponding to $2^{-n}$"
649 "execution_count": null,
652 "base_uri": "https://localhost:8080/"
654 "id": "VSSaHx817RzA",
655 "outputId": "99cd8174-90b4-42d8-b94f-368341b47e10"
659 "# You may want to create a new code block above this one to print out what the range function is doing\n",
662 "for n in range(20): #identical to range(0,20) or range(0,20,1)\n",
663 " partial_sum += 2**(-n)\n",
664 " print( 'Sum from n=0 to ' + str(n) + ' of 2^{-n} = ', partial_sum )"
669 "execution_count": null,
672 "base_uri": "https://localhost:8080/"
674 "id": "ozMNxBrD7RzA",
675 "outputId": "c607ba0d-3c23-477d-8ffd-f8dbad39571b"
679 "print( 'Now start subtracting from the sum' )\n",
681 "for n in range(19,-1,-1): \n",
682 " partial_sum -= 2**(-n) \n",
683 " print( 'Sum from n=0 to ' + str(n-1) + ' of 2^{-n} = ', partial_sum )"
687 "cell_type": "markdown",
692 "### While loops \n",
694 "We often use ***while*** loops for iterative methods, such as fixed-point iterations. These are typically used when we are unsure exactly how many iterations a process should take.\n",
697 "while condition: #this condition is true\n",
698 " do something cool\n",
699 " update condition, or use break or continue for loop control\n",
700 "#no indent as at end of loop\n",
702 "If the the ``condition`` never becomes false, then this will result in an infinite loop, so be careful. It is therefore fairly common practice to include some type of counter which tracks the number of iterations, and negating the condition if the counter reaches a specified value.\n",
704 "You can also exit from any for loop by using ``break`` to exit the innermost loop, and ``continue`` to continue to the next iteration of this loop\n",
706 "Let's look at an example where we are trying to iterate a logistic equation \n",
708 " x_{n+1}=a\\cdot x_n (1-x_n)\n",
710 "until we arrive at a fixed point."
715 "execution_count": null,
718 "base_uri": "https://localhost:8080/"
720 "id": "a9S4BAC67RzB",
721 "outputId": "6ef072be-9722-43fc-fd7c-621dd9610e28"
725 "import math as m\n",
730 "doit = True #boolean True and False \n",
732 "while doit and it <= 5:\n",
735 " xnew *= a * (1-xnew)\n",
736 " if m.fabs(xnew-xold) > tol:\n",
737 " print('Iterating, X = ', xnew)\n",
738 " continue #play it again, Sam\n",
741 " print('This is skipped if the continue command is not commented out above.')\n",
742 "print(\"Fixed Point=\",xnew)"
747 "anaconda-cloud": {},
749 "collapsed_sections": [],
750 "name": "02-Python-functions-modules.ipynb",
754 "display_name": "Python 3",
755 "language": "python",
763 "file_extension": ".py",
764 "mimetype": "text/x-python",
766 "nbconvert_exporter": "python",
767 "pygments_lexer": "ipython3",