======================= Understanding functions ======================= Every function you will write today will have the same structure: :: def name(input1, input2, ...): ''' docstring ''' pass # body goes here where ``name`` is the name of the function, ``input1`` is name of the first input to the function, ``input2`` is the name of the second input to the function, etc. Function inputs are often called either *arguments* or *parameters*: typically, the former is what we use when referring to the actual values given to a function at the specific places it is called, and the latter is used when talking about the named inputs a function declares within the scope of its body. As an aside, you may see the term *actual parameters* used to refer to the actual values passed to the function through a function call and the term *formal parameters* used to refer to the names used for the function inputs within the function definition. Python functions often include a comment (in triple quotes), called a docstring, immediately following the function header (everything from the ``def`` to the ``:``). This comment typically contains a brief description of the purpose of the function along with some information about the parameters and the expected return value. The body of the function is everything following the ``:`` that is indented at least one level past the ``def`` in the function's header. In the following example: .. code:: def sinc(x): ''' Real valued sinc function f(x) == sin(x)/x Inputs: x: float Return: float ''' # Make sure we don't divide by zero if x != 0: return math.sin(x) / x else: # sin(0) / 0 == 1 return 1.0 the function's name is ``sinc`` and it has a single parameter named ``x``. We do not indicate the type of the parameters or return value explicitly in Python. Instead, the types are checked at runtime. For some functions, a given parameter may take on many different types, perhaps even depending on the value of other parameters. For others, one specific type is needed. It is common to specify the expected types for parameters and return value in the function's docstring, as seen in this example. The return type is merely the type of the expression in the return statement. A call to a function is an expression of the form: :: name(argument1, argument2, ...) Arguments to a function are also expressions. Each input should evaluate to a value with the expected type for the corresponding parameter of the function. Particular care should be taken with respect to argument types: if you provide an argument with the wrong type to a function, the call will not necessarily always fail at runtime! For example, if that specific parameter with the wrong type happens to go unused during that particular function invocation at runtime, then the parameter's type is not checked. This can lead to type errors which pop up even if initial function testing succeeded. In our next example, we use ``sinc`` in an example function named ``go``: .. code:: def go(): a = sinc(3.14) b = 3.14 print("A is {:f}".format(a)) c = sinc(b * 2) + sinc(b * 3.0) print("C is {:f}".format(c)) Notice that the argument to the first call to ``sinc`` is a constant of type ``float``, whereas the arguments to the second and third calls are more complex expressions that yield floating point values when evaluated. Although the ``2`` in ``b * 2`` is of type ``int``, because ``b`` is a ``float``, ``2`` is implicitly converted to a ``float`` to match it. Also, notice that the results of the second and third calls yield floating point values that are added together to yield a new value with type ``float``. When we use a function we need to make sure that the parameters have the expected type. ``sinc`` expects a float so the following is correct: .. code:: b = sinc(5.3) # OK But the following is incorrect: .. code:: b = sinc("cat") # INCORRECT and will fail at runtime. ``sinc`` returns a float and can only be used in a context that requires a float. The following is correct: .. code:: sinc(5.3) + 1.0 # OK But the following is wrong: .. code:: sinc(5.3)[0] # INCORRECT: because we're trying to index into # a float. The above is just a simple example, and fairly easy to catch. However, as functions and their types become more complex, this practice of thinking about the types will become both more challenging and more important. Many problems in programming can be detected and resolved quickly just by being careful about using values with the right types. **Returning None** Some functions, such as ``print``, perform an effect, but do not compute a value to be returned. Because all functions in Python return *something*, such functions return a special value that represents nothing: ``None``. For example, here's a function that just takes a name and prints a message: .. code:: def hello(name): print("Hello " + name) Anytime a return statement is left off in a function definition, ``None`` is implicitly returned, so the above is equivalent to: .. code:: def hello(name): print("Hello " + name) return None What is the value of ``x`` after I run the following code? .. code:: x = hello("Sam") The value of ``x`` is an object where anything useful you may try to do with it results in an error. Although the expression ``x == None`` evaluates to ``True``, and ``print(x)`` does print ``None``, basically anything else will cause a runtime error. Thus, the result of functions which return ``None`` is typically not bound (assigned) to a variable, effectively throwing it away. Lists and Functions -------------------- Functions can take lists as arguments and return lists as values. For example, here is a function that finds the largest value in a non-empty list: .. code:: def max(vals): current_max = vals[0] for x in vals[1:]: if x > current_max: current_max = x return current_max One nice thing about Python is that it is often the case that a function can be used with multiple types. For example, what is the result of evaluating ``max([1.0, 27, -3, 4])`` versus ``max(["zzz", "xyz", "abc"])``? Here is a function that takes a list returns a new list with the squares of its elements: .. code:: def sqr_list(vals): squared = [] for x in vals: squared.append(x * x) return squared Here are some very simple uses of these functions: :: def go(): some_vals = [1.0, 3.0, 5.0, 7.0] squared_vals = sqr_list(some_vals) print(squared_vals) print("Maximum value is: {:f}".format(max(squared_vals)))