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 `_