cs107-lecture-examples

Example codes used during Harvard CS107 lectures
git clone https://git.0xfab.ch/cs107-lecture-examples.git
Log | Files | Refs | README | LICENSE

python_2.md (13039B)


      1 ---
      2 jupyter:
      3   jupytext:
      4     formats: ipynb,md
      5     text_representation:
      6       extension: .md
      7       format_name: markdown
      8       format_version: '1.3'
      9       jupytext_version: 1.13.8
     10   kernelspec:
     11     display_name: Python 3
     12     language: python
     13     name: python3
     14 ---
     15 
     16 # Basic Python
     17 
     18 * Booleans and Control Flow
     19 * Functions
     20 * Exceptions
     21 * Plotting
     22 
     23 
     24 We'll be embedding some HTML into our notebook.  To do so, we need to import a
     25 library:
     26 
     27 ```python
     28 from IPython.display import HTML
     29 ```
     30 
     31 We'll also probably use `numpy` so we might as well import it too:
     32 
     33 ```python
     34 import numpy as np
     35 ```
     36 
     37 ## Booleans and Control Flow
     38 
     39 These are pretty standard things in any language.  I'll just show you a little
     40 bit of syntax here. However, I will put a slight emphasis on the exception
     41 testing.  It's important.
     42 
     43 
     44 #### Testing for membership
     45 
     46 ```python
     47 some_primes = [2, 3, 5, 7, 13]
     48 print(4 in some_primes)
     49 print(13 in some_primes)
     50 ```
     51 
     52 #### Some basic if/elif/else statements
     53 
     54 ```python
     55 x = 9
     56 if x in some_primes:
     57     print('x is prime!')
     58 elif x > 13:
     59     print('x may or may not be prime.')
     60 else:
     61     print('x is definitely not prime.')
     62 ```
     63 
     64 Breaking out of a loop:
     65 
     66 ```python
     67 for x in some_primes:
     68     if x == 2:
     69         print('Only even prime.')
     70         break
     71         
     72 ```
     73 
     74 Continuing a loop.  Notice that everything after the continue statement is
     75 ignored.
     76 
     77 ```python
     78 i = 0
     79 while i < 10:
     80     i += 1
     81     if i <= 5:
     82         print(i)
     83         continue
     84         print(i-1)
     85     else:
     86         print('Done with this.')
     87         break
     88     
     89 ```
     90 
     91 #### Some basic exception handling
     92 
     93 Python has a number of built-in exceptions ([Built-in
     94 exceptions](https://docs.python.org/3/library/exceptions.html), [Python Standard
     95 Exceptions](https://www.tutorialspoint.com/python/standard_exceptions.htm)).  It
     96 is usually a good idea to let Python raise exceptions for you since it's really
     97 good at it.  However, there are times when you may want to write your own
     98 exception (we won't talk about that now) or when you want to press ahead even in
     99 the face of an error.
    100 
    101 I can make that last statement clearer.  You have two options:  catch and
    102 respond to errors when they're raised or ignore them.  If you ignore them, then
    103 Python's default exception-handling behavior takes over which will ultimately
    104 print out the error message and terminate the program.  If you respond to the
    105 errors, then you need to tell your program what to do.  In essence, you will
    106 shift the control flow of your program if you choose this second option.
    107 
    108 Let's look at an example or two.
    109 
    110 ```python
    111 x, y = 1.0, 0.0
    112 z = x / y
    113 z**2.0
    114 ```
    115 
    116 Python took care of error for us and terminated the program at the second line.
    117 
    118 But perhaps a division by zero isn't the end of the world.  What if we have a
    119 piece-wise function?
    120 
    121 ```python
    122 x, y = 1.0, 0.0
    123 try:
    124     z = x / y
    125 except ZeroDivisionError:
    126     z = 0.0
    127 print('z = {}'.format(z))
    128 ```
    129 
    130 This could, of course, have been handled with an `if-else` block.
    131 
    132 One old motivation for using exception handling is to check the input arguments
    133 of a function for validity.  You can still do this, but the latest advice is to
    134 just let Python's exception handler deal with it and terminate the program if
    135 need be.
    136 
    137 
    138 ## Functions
    139 
    140 #### Python functions are first class:
    141 
    142 * You can assign variables to them
    143 * You can pass them into functions
    144 * You can return them from functions
    145 
    146 
    147 ### Example: 
    148 
    149 ```python
    150 alist = [1,13,5,7]
    151 newmax = max # Assign newmax to the function max
    152 mymax1 = max(alist) # Get the maximum of the list using the max built-in
    153 mymax2 = newmax(alist) # Get the maximum of the list using the new max function
    154 print('Original maximum gives {0} and new maximum gives {1}.'.format(mymax1, mymax2))
    155 ```
    156 
    157 The syntax for defining functions is pretty straightforward.
    158 
    159 ```python
    160 def rect(w,h):
    161     return w * h
    162 def circle(r):
    163     return np.pi * r * r
    164 def parabola(x, a, b, c):
    165     return a * x * x + b * x + c
    166 ```
    167 
    168 Notice that the function name is preceded by the keyword `def`.  The end of the
    169 line **must** have a colon!  The function body is indented.  The quantity to be
    170 returned is returned using the `return` statement.
    171 
    172 Because functions are first class, we can pass functions to other functions.
    173 
    174 ```python
    175 def parab_extrema(a, b, c, parab):
    176     x_extreme = - b / 2.0 / a # Location of max or min
    177     x_left = x_extreme - 1.0  # Point to the left of max or min
    178     x_right = x_extreme + 1.0 # Point to the right of max or min
    179     p_left = parab(x_left, a, b, c) # Value at left point
    180     p_right = parab(x_right, a, b, c) # Value at right point
    181     p_extreme = parab(x_extreme, a, b, c) # Value at max or min
    182     # Check if extremum is maximum or minimum and print out result
    183     if (p_left > p_extreme) & (p_right > p_extreme):
    184         print('The extremum for this parabola has coordinates ({x:4.3f}, {y:4.3f}) and is a minimum.'.format(x=x_extreme, y=p_extreme))
    185     elif (p_left < p_extreme) & (p_right < p_extreme):
    186         print('The extremum for this parabola has coordinates ({x:4.3f}, {y:4.3f}) and is a maximum.'.format(x=x_extreme, y=p_extreme))
    187     else:
    188         print('Something went wrong.')
    189 ```
    190 
    191 ```python
    192 a, b, c = 0.2, 1.0, -3.0
    193 parab_extrema(a, b, c, parabola)
    194 a, b, c = -5.0, -5.0, 5.0
    195 parab_extrema(a, b, c, parabola)
    196 ```
    197 
    198 There are some other convenient ways to interact with function arguments.
    199 
    200 One thing you may wish to do is provide a default argument to the function.  If
    201 that argument is not specified when the function is called, then the default
    202 value is assumed.  This is a type of keyword argument.
    203 
    204 Let's return to our nice parabola example.  We'll make $b=0$ the default.
    205 
    206 ```python
    207 def parabola(x, a, c, b=0.0):
    208     return a * x * x + b * x + c
    209 ```
    210 
    211 Notice that we had to move our default argument to a position _after_ the
    212 mandatory arguments.  That hurts the readability a little bit in this example,
    213 but we'll press forward regardless.
    214 
    215 Now call the `parab_extreme() function` again.
    216 
    217 ```python
    218 def parab_extrema(a, b, c, parab):
    219     x_extreme = - b / 2.0 / a # Location of max or min
    220     x_left = x_extreme - 1.0  # Point to the left of max or min
    221     x_right = x_extreme + 1.0 # Point to the right of max or min
    222     p_left = parab(x_left, a, c) # Value at left point
    223     p_right = parab(x_right, a, c) # Value at right point
    224     p_extreme = parab(x_extreme, a, c) # Value at max or min
    225     # Check if extremum is maximum or minimum and print out result
    226     if (p_left > p_extreme) & (p_right > p_extreme):
    227         print('The extremum for this parabola has coordinates ({x:4.3f}, {y:4.3f}) and is a minimum.'.format(x=x_extreme, y=p_extreme))
    228     elif (p_left < p_extreme) & (p_right < p_extreme):
    229         print('The extremum for this parabola has coordinates ({x:4.3f}, {y:4.3f}) and is a maximum.'.format(x=x_extreme, y=p_extreme))
    230     else:
    231         print('Something went wrong.')
    232 ```
    233 
    234 We changed the
    235 [API](https://en.wikipedia.org/wiki/Application_programming_interface) a little
    236 bit and so we had to update the calls to `parab()`.  However, everything works
    237 just fine if we're careful.
    238 
    239 
    240 It's probably better to give all the parameter arguments default values.  Let's
    241 re-write the API again.
    242 
    243 ```python
    244 def parabola(x, a=1.0, b=-1.0, c=-1.0):
    245     return a * x * x + b * x + c
    246 
    247 def parab_extrema(parab, a=1.0, b=-1.0, c=-1.0):
    248     x_extreme = - b / 2.0 / a # Location of max or min
    249     x_left = x_extreme - 1.0  # Point to the left of max or min
    250     x_right = x_extreme + 1.0 # Point to the right of max or min
    251     p_left = parab(x_left, a, b, c) # Value at left point
    252     p_right = parab(x_right, a, b, c) # Value at right point
    253     p_extreme = parab(x_extreme, a, b, c) # Value at max or min
    254     # Check if extremum is maximum or minimum and print out result
    255     if (p_left > p_extreme) & (p_right > p_extreme):
    256         print('The extremum for this parabola has coordinates ({x:4.3f}, {y:4.3f}) and is a minimum.'.format(x=x_extreme, y=p_extreme))
    257     elif (p_left < p_extreme) & (p_right < p_extreme):
    258         print('The extremum for this parabola has coordinates ({x:4.3f}, {y:4.3f}) and is a maximum.'.format(x=x_extreme, y=p_extreme))
    259     else:
    260         print('Something went wrong.')
    261 
    262 parab_extrema(parabola)
    263 ```
    264 
    265 Great!  Looks pretty nice.
    266 
    267 But there's more!  We can also provide _positional_ and _keyword_ arguments to a
    268 function.  This allows permits a variable number of arguments to be passed to a
    269 function.
    270 
    271 * positional arguments:  `def func(*args):`
    272   + Python collects all the remaining positional arguments into a tuple.  You
    273     can then access the tuple with the usual indexing.
    274 * keyword arguments:  `def func(**kwargs):`
    275   + Python collects all the remaining keyword arguments into a dictionary.  You
    276     can then access the dictionary with the usual indexing.
    277 
    278 
    279 ### Variable Positional Arguments
    280 
    281 We will once again work with the quadratic equation example.  This time, we'll
    282 just work with the `parabola` function to save some space.  Let's change the
    283 `parabola` function to permit a variable number of arguments.
    284 
    285 ```python
    286 def parabola(x, *args):
    287     return args[0] * x * x + args[1] * x + args[2]
    288 parabola(1.0, 1.0, -1.0, -1.0)
    289 ```
    290 
    291 Seems to work okay.  But this is not a very robust code.  Everything breaks if
    292 we don't provide the exact number of necessary arguments.
    293 
    294 ```python
    295 parabola(1.0)
    296 ```
    297 
    298 ### Variable keyword arguments
    299 
    300 We can make our API more flexible.  Let's give more descriptive names to the
    301 coefficients $a$, $b$, and $c$.  We'll call $a$ the `width` since it controls
    302 the width of the parabola, $b$ `trans` since it controls the horizontal
    303 translation of the parabola, and we'll call $c$ `shift` since it controls the
    304 vertical shift of the parabola.  Our `parabola` function might now look like:
    305 
    306 ```python
    307 def parabola(x, **kwargs):
    308     print(kwargs)
    309     return kwargs['width'] * x * x + kwargs['trans'] * x + kwargs['shift']
    310 ```
    311 
    312 Calling it gives:
    313 
    314 ```python
    315 parabola(1.0, width=1.0, trans=-1.0, shift=-1.0)
    316 ```
    317 
    318 **Note:** Using variable positional and keyword arguments provides exceptional
    319 flexibility in how you design your programs.
    320 
    321 
    322 One final note about variable arguments:  You can perform the reverse operation
    323 by passing in the `*` or `**` operators to the function.  This will _unpack_ the
    324 arguments whereas the previous pattern _packed_ the arguments.  Let's take a
    325 quick look.
    326 
    327 ```python
    328 def parabola(x, a, b, c):
    329     return a * x * x + b * x + c
    330 ```
    331 
    332 ```python
    333 # Store coefficients in a list
    334 coeffs = [1.0, -1.0, -1.0]
    335 parabola(1.0, *coeffs)
    336 
    337 # Store coefficients in a dictionary
    338 coeffs = {'a':1.0, 'b':-1.0, 'c':-1.0}
    339 parabola(1.0, **coeffs)
    340 ```
    341 
    342 ---
    343 
    344 ## Plotting
    345 
    346 There are many, many ways to make plots in Python.  The most common way is to
    347 use [`matplotlib`](https://matplotlib.org/).
    348 
    349 Another package, which is gaining popularity, is called
    350 [`seaborn`](https://seaborn.pydata.org/).
    351 
    352 I don't care which package you use, as long as your plots are readable and
    353 reproducible.
    354 
    355 
    356 To make plots in the Jupyter notebook, you need to include the line `%matplotlib
    357 inline` before you make any plots.  This line ensures that the plots will be
    358 displayed in your notebook and not in a separate window.
    359 
    360 ```python
    361 %matplotlib inline
    362 ```
    363 
    364 Next, you should `import matplotlib`:
    365 
    366 ```python
    367 import matplotlib               # Import all of matplotlib
    368 import matplotlib.pyplot as plt # Only import pyplot (which includes the plot function) and give it the alias `plt`
    369 ```
    370 
    371 Now you're basically ready to do some plots.
    372 
    373 **WARNING!** When making plots in an actual Python script, you must **always**
    374 include the command `plt.show()` at the **end** of your program.  **Always.** If
    375 you don't do so, then your plots will not display and you will be wondering
    376 where they are.  However, when plotting in the Jupyter notebook, there is no
    377 need to use `plt.show()`.
    378 
    379 
    380 We can generate some toy data using `numpy`.
    381 
    382 ```python
    383 x = np.linspace(-2.0*np.pi, 2.0*np.pi, 500) # x-grid
    384 ys = np.sin(x) # sin function
    385 yc = np.cos(x) # cos function
    386 ```
    387 
    388 Now plot!
    389 
    390 ```python
    391 plt.plot(x, ys, lw=4, ls='-', label=r'$\sin(x)$')  # linewidth = 4, linestyle = solid, raw string label
    392 plt.plot(x, yc, lw=4, ls='--', label=r'$\cos(x)$')
    393 plt.legend() # show legend
    394 plt.xlabel(r'$x$', fontsize=24) # label x axis
    395 plt.ylabel(r'$y$', fontsize=24) # label y axis
    396 plt.title('Sine and Cosine', fontsize=24)
    397 ```
    398 
    399 Notice that we used a few things:
    400 1. We changed the line widths
    401 2. We changed the line style
    402 3. We labeled the plots
    403 4. We changed the font size of the labels
    404 
    405 We also use a `raw string` because we're including Latex commands to render
    406 mathematics.  A `raw string` is preceded by the letter `r`.
    407 
    408 
    409 There is **much** more to plotting than this example, but this should get you
    410 started.  Some things you may want to look up are how to change the size of the
    411 tick marks and tick labels and how to use a `config` file: [Customizing a
    412 Plot](https://matplotlib.org/users/customizing.html).  You may also want to
    413 understand _contour_ plots as well as _scatter_ plots and other statistical
    414 plots such as `pdfs` and `histograms`.  Note that `seaborn` has fantastic
    415 support for statistical plots.