CS 2120: Topic 3 ================= .. highlight:: python :linenothreshold: 7 Video for this week: ^^^^^^^^^^^^^^^^^^^^ .. raw:: html .. raw:: html .. admonition:: Yikes, what happened to your voice? :class: Warning Sorry for my voice in videos 2 and 3 for this week.. I've caught a bit of a cold, and it happened to show up between recording parts 1 and 2. .. raw:: html Functions ^^^^^^^^^ .. image:: ../img/conjunction_junction.jpg * Say you want to be able to **do** a certain thing (i.e. find the area of a circle) * You may want to do it multiple times.. ideally you'd want a cleaner way to do this than to copy and paste it X times in your editor.. * In Python, we can use a *function*:: def my_function(parameter): result = parameter * 2 print(result) * Once you've defined a function, you can *call* it exactly the same way you'd call a *built-in function* like ``print``. * Let's call our function: >>> my_function(2) 4 >>> my_function(7) 14 * When we call ``my_function``, the Python interpreter executes the statements that make up the function, in order. Function Parameters ^^^^^^^^^^^^^^^^^^^ * Imagine an ``add_print(a,b)`` function that adds two numbers and prints the result. You want it to add *any* two numbers, not just two specific numbers. A function ``add_print(3,5)`` that can only add 3 to 5 wouldn't be very useful. It would only ever print ``8``. So we introduce *parameters*. * Parameters are like variables. When you *call* the function, the parameter values get filled with whatever you set them to in the call. * Let's create an ``add_print`` function:: def add_print(a,b): print(a+b) * Now that the function is defined, we can *call* it. Like this: >>> add_print(5,2) 7 * The *call* ``add_print(5,2)`` gets handled like this: * The interpreter checks to see if it knows about a function named ``add_print`` * We just defined ``add_print``, so it does. * When we defined it, we told the interpreter it should have two parameters: ``a`` and ``b``. * The interpreter now takes the values in the call (in this case, ``5`` and ``2``) and assigns those values to the function parameters ``a`` and ``b``. * In other words, the first thing the interpreter does in this case is set ``a = 5`` and ``b = 2`` * Then the interpreter executes the body of the function, with the parameters having their new values. Abstraction ^^^^^^^^^^^ .. admonition:: What is **abstraction**? :class: Warning Abstraction: reduce complexity by hiding unnecessary information .. image:: ../img/push_for_coffee.jpeg When you press a button to make coffee, you probably don't care about what the machine is doing, and you don't need to. * Why is abstraction important? .. admonition:: Try this... :class: Note Write down a sequence of instructions (i.e. a "program") for boiling an egg. You can use the functions ``locate(pot)``, ``setup(egg_in_water_in_pot)``, and ``boil(egg)``. .. admonition:: Now try this... :class: Note Write down a sequence of instructions (i.e. a "program") for boiling an egg. You cannot use any functions. * You've now written two programs at two levels of abstraction. Which was easier? * Functions allow us to add on *layers of abstraction*. * A low level function might worry about how to set the individual pixels of the display to show the letter ``A`` . * Would you want to cut-and-paste that code every time you needed to print ``A``? * Instead, we have a function called ``print`` that hides all those messy details from us. * We call ``print``, ``print`` calls other functions, which call other functions, which call other functions... * Without organizing things into *levels of abstraction*, writing complex software would be impossibly difficult. A note on indentation ^^^^^^^^^^^^^^^^^^^^^ * The general format for defining a function is:: def function_name(p1,p2,p3,p4, ... ): statement 1 statement 2 ... statement m * ``statement 1, statement 2, ... , statement m`` make up the **body** of the function * You tell Python which statements make up the *body* of the function by using **indentation** (i.e. hitting the ``tab`` key). * without indenting, you will get a *syntax error* (more specifically an *IndentationError*) * make sure to be **consistent** in your indentation (whether you use a ``tab``, ``4 spaces``, ...). Execution Flow ^^^^^^^^^^^^^^^ * To make sense of programs, we need to know *which* instruction gets executed *when* * Execution begins at the first ``statement`` of the program. Statements are executed from top to bottom, one line at a time. * However, when the interpreter reaches a function, it processes the ``def`` statement only.. it does not go into the body of the function. * it "skips over" those lines and executes the first line outside of the function. * The statements in the body of the function are not executed until we call the function. * Let's trace through this program:: def dostuff(a,b): c = b*2 d = (a+4)*2 c = d + c return c x = 2 y = 3 print(dostuff(x,y)) .. admonition:: Test yourself :class: Warning In what order are these lines of code above processed by Python? (i.e. 1,2,3,5,9,etc.) Function values ^^^^^^^^^^^^^^^^ * Notice how ``dostuff`` ended with a ``return`` statement. * The ``return`` statement tells Python: "*return* this value to whoever called this function" * With ``return``, *functions* evaluate into *values*. * Consider: >>> print(dostuff(2,2)) 16 >>> print(dostuff(4,4)) 24 >>> print(dostuff(2,2) + dostuff(4,4)) 40 * When the interpreter hits a ``dostuff`` function call, it executes the function, which results in a returned value. * So, when execution flow comes back to the calling program, the call to ``dostuff`` gets replaced with whatever value got ``return`` ed. Composition ^^^^^^^^^^^^ * Python functions can be *composed* just like mathematical functions. * We've already seen ``print`` composed with ``dostuff`` * We can nest functions, too: >>> dostuff(dostuff(2,2),dostuff(2,2)) 72 * If you get confused tracing nested functions, try the following: * Find a function call you can evaluate. Evaluate it. * Replace the function call with the *value* it returns * Keep doing this until you're down to one value. Variable scope ^^^^^^^^^^^^^^^ * If you set a variable inside a function, it is *local* to that function. * No other function can see a function's local variables. They are *local*. Consider this code:: def domore(a,b): c = 2*a + b return c * What happens if I do this:: >>> print(domore(4,4)) 12 >>> print(c) NameError: name 'c' is not defined * Error! But ``c`` is defined in ``domore``! Why did we get an error? * Variables have *scope*. We will come back to this later in the course. Optional parameters for functions ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * Sometimes you want a function to have an optional parameter, with a pre-specified default value. * This is done very easily:: def my_function(a,b,c=3): do_stuff() * When you call ``my_function(5,12)``, ``a`` will have value ``5``, ``b`` value ``12`` and ``c`` value ``3``. * Because we specified a *default* value for ``c``, we don't have to provide one when we call the function. * If we want to *override* the default though, we can: ``my_function(4,3,2)`` . Import ^^^^^^^ * Sometimes you want to make a big library of functions. Maybe they're related to analysis data from your research. * You'd like to access some of those functions from another program that you're writing. * If you put your functions in a file called 'myfuncs.py', you can *import* them into another program like this: >>> from myfuncs import * * (The ``*`` here means "everything) * You could also use: >>> import myfuncs * **BUT**, after importing in this way, to access a function called ``dostuff`` in the file ``myfunc`` you'd have to type: >>> myfuncs.dostuff(...) For next class ^^^^^^^^^^^^^^^ * Read `chapter 4 of the text `_