{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 01: Functions and scripts" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Why use functions?\n", "\n", "functions are a great coding tool! They allow to setup reusable bits of code that you might need over and over. Using functions, you only have the write the code once, and, if you need to change it, only change it once. \n", "\n", "We can create a function that writes the Fibonacci series to an arbitrary boundary:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "def fib(n):\n", " \"\"\"Print a Fibonacci series up to n.\"\"\"\n", " a, b = 0, 1\n", " while a < n:\n", " print(a)\n", " a, b = b, a + b" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "a, b = 0, 1" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Help on function fib in module __main__:\n", "\n", "fib(n)\n", " Print a Fibonacci series up to n.\n", "\n" ] } ], "source": [ "help(fib)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now call the function we just defined:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "1\n", "1\n", "2\n", "3\n", "5\n", "8\n", "13\n", "21\n", "34\n", "55\n", "89\n", "144\n", "233\n", "377\n", "610\n", "987\n", "1597\n" ] } ], "source": [ "fib(2000)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The keyword ```def``` introduces a function definition. It must be followed by the function name and the parenthesized list of formal parameters or arguments. The statements that form the body of the function start at the next line, and must be indented.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The first statement of the function body can optionally be a string literal; this string literal is the function’s documentation string, or docstring. (More about docstrings can be found in the section Documentation Strings.) There are tools which use docstrings to automatically produce online or printed documentation, or to let the user interactively browse through code; it’s good practice to include docstrings in code that you write, so make a habit of it.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The execution of a function introduces a new symbol table used for the local variables of the function. More precisely, all variable assignments in a function store the value in the local symbol table; whereas variable references first look in the local symbol table, then in the local symbol tables of enclosing functions, then in the global symbol table, and finally in the table of built-in names. Thus, global variables cannot be directly assigned a value within a function (unless named in a global statement), although they may be referenced. In plain english, that means the variables that are formed and used within a function can only be used within the function, unless they are ```return```-ed - they are sent back.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The actual parameters (arguments) to a function call are introduced in the local symbol table of the called function when it is called; thus, arguments are passed using call by value (where the value is always an object reference, not the value of the object). [1] When a function calls another function, a new local symbol table is created for that call.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A function definition introduces the function name in the current symbol table. The value of the function name has a type that is recognized by the interpreter as a user-defined function. This value can be assigned to another name which can then also be used as a function. This serves as a general renaming mechanism:\n" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fib" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[0;31mSignature:\u001b[0m \u001b[0mfib\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mDocstring:\u001b[0m Print a Fibonacci series up to n.\n", "\u001b[0;31mFile:\u001b[0m /var/folders/4x/bmhyjcdn3mgfdvkk_jgz6bsrlnfk3t/T/ipykernel_46966/2487284216.py\n", "\u001b[0;31mType:\u001b[0m function" ] } ], "source": [ "?fib" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "f = fib" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "1\n", "1\n", "2\n", "3\n", "5\n", "8\n", "13\n", "21\n", "34\n", "55\n", "89\n", "144\n" ] } ], "source": [ "f(200)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Coming from other languages, you might object that fib is not a function but a procedure since it doesn’t return a value. In fact, even functions without a return statement do return a value, albeit a rather boring one. This value is called ```None``` (it’s a built-in name equivalent to null or not defined). Writing the value ```None``` is normally suppressed by the interpreter if it would be the only value written. You can see it if you really want to using print:\n" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "None\n" ] } ], "source": [ "fib(0)\n", "print(fib(0))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It is simple to write a function that returns a list of the numbers of the Fibonacci series, instead of printing it:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "def fib2(n): # return Fibonacci series up to n\n", " \"\"\"Return a list containing the Fibonacci series up to n.\"\"\"\n", " result = []\n", " a, b = 0, 1\n", " while a < n:\n", " result.append(a) # see below\n", " a, b = b, a + b\n", " return result" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fib2(100)" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "f100 = fib2(100) # call it\n", "f100 # write the result" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This example, as usual, demonstrates some new Python features:\n", "\n", "The ```return``` statement returns with a value from a function. return without an expression argument returns None. Falling off the end of a function also returns None.\n", "\n", "## An object oriented preview aside: terminology alert!!!\n", "\n", "The statement ```result.append(a)``` calls a method of the ```list``` type object instance ```result```. A method is simply a function that ‘belongs’ to an object and is named obj.methodname, where obj is some object (this may be an expression), and methodname is the name of a method that is defined by the object’s type. Different types define different methods. Methods of different types may have the same name without causing ambiguity. (It is possible to define your own object types and methods, using classes, see Classes) The method append() shown in the example is defined for list objects; it adds a new element at the end of the list. In this example it is equivalent to result = result + [a], but more efficient." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## back to functions\n", "\n", "It is also possible to define functions with a variable number of arguments. In ```python```, there are three forms function arguments which can be combined. Each of these forms get used a lot.\n", "\n", "The most user-friendly form of a function argument is to specify an argument which has a default value. This creates a function that can be called with fewer arguments, but that also allows greater flexility in controlling the behavior within the function itself. \n", "\n", "For example:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "import math\n", "\n", "\n", "def thiem(T, r, Q=1000.0, R=1.0e10, h0=0.0):\n", " \"\"\"\n", " A very simple example function\n", " with a mixture of argument types.\n", " Solves the Thiem equation:\n", "\n", " h = (Q/2piT)*(ln(R/r)) + h0\n", "\n", " Parameters\n", " ----------\n", "\n", " T: transmissivity\n", " r: distance from pumping to observation\n", " Q: pumping rate\n", " R: distance to \"zero\" influence\n", " h0: initial head\n", "\n", " Returns\n", " -------\n", " h: head\n", " \"\"\"\n", " first_term = Q / (2.0 * 3.14159 * T)\n", " second_term = math.log(R / r)\n", " return (first_term * second_term) + h0" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Help on function thiem in module __main__:\n", "\n", "thiem(T, r, Q=1000.0, R=10000000000.0, h0=0.0)\n", " A very simple example function\n", " with a mixture of argument types.\n", " Solves the Thiem equation:\n", " \n", " h = (Q/2piT)*(ln(R/r)) + h0\n", " \n", " Parameters\n", " ----------\n", " \n", " T: transmissivity\n", " r: distance from pumping to observation\n", " Q: pumping rate\n", " R: distance to \"zero\" influence\n", " h0: initial head\n", " \n", " Returns\n", " -------\n", " h: head\n", "\n" ] } ], "source": [ "help(thiem)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This function *requires* both ```T``` and ```r```. All the rest use the defaults if not explicitly passed in. The default values are evaluated at the point of function definition in the defining scope.\n", "\n", "Giving only mandatory arguments: `T`=100.0, `r`=300.0" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "27.568951478843925" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "thiem(100.0, 300.0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Giving one of the optional arguments using the implied position of `Q`: `T`=100.0, `r`=300.0, `Q`=500.0" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0.2756895147884393" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "thiem(100.0, 300.0, 10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Giving one of the optional arguments using the explicit argument name: `T`=100.0, `r`=300.0, `Q`=500.0" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "13.784475739421962" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "thiem(100.0, 300.0, Q=500.0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Your turn\n", "\n", "1.) What is the thiem solution for `T`=1000.0, `r`=20.0?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "2.) What is the Thiem solution for `T`=1000.0, `r`=20.0, `h0`=40.0?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "3.) What is the Thiem solution for `T`=1000.0, `r`=20.0, `Q`=2000.0, `h0`=40.0" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "4.) What is the Thiem solution for `T`=1000.0, `r`=20.0, `Q`=2000.0, `h0`=40.0 if there is a lake 2000.0 units away?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "5.) using a loop to accumulate Thiem results for `T`=1000.0, `r`=3000.0 for `Q` values ranging from 100.0 to 2000.0 by 100.0" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "6.) redefine the Thiem function to take a list as the `Q` argument and return a list of ```h``` results. Call the new function ```thiem_list()```. Then call ```thiem_list()``` using the same Q as in 5.) above" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "def thiem_list(T, r, Q=[1000.0], R=1.0e10, h0=0.0):\n", " \"\"\"\n", " A very simple example function\n", " with a mixture of argument types.\n", " Solves the Thiem equation:\n", "\n", " h = (Q/2piT)*(ln(R/r)) + h0\n", "\n", " Parameters\n", " ----------\n", "\n", " T: transmissivity\n", " r: distance from pumping to observation\n", " Q: pumping rate\n", " R: distance to \"zero\" influence\n", " h0: initial head\n", "\n", " Returns\n", " -------\n", " h: list of head s\n", " \"\"\"\n", " h = []\n", " for q in Q:\n", " first_term = q / (2.0 * 3.14159 * T)\n", " second_term = math.log(R / r)\n", " result = (first_term * second_term) + h0\n", " h.append(result)\n", " return h" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[0.029317448718566664, 0.05863489743713333, 0.0879523461557]" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "thiem_list(1000, 100, Q=[10, 20, 30])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## More on functions: ```lambda``` functions\n", "\n", "```lambda``` functions are a special type of function known as an \"in-line\" function. They are present in virtually all modern programming languages (not Fortran, that's not modern) and are usually high-optimized. They allow you to quickly define a simple-ish function that can only accept a single, *required* argument. The only reason to introduce them is because they appear frequently when using a python library named ```pandas``` that we will cover later." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The variable ```square``` is actually a function that squares (or attempts to) the single required argument that it is given" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(x)>" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "square = lambda x: x * x\n", "square" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "10000" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "square(100)" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "can't multiply sequence by non-int of type 'str'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[22], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43msquare\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mnot gonna work\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n", "Cell \u001b[0;32mIn[20], line 1\u001b[0m, in \u001b[0;36m\u001b[0;34m(x)\u001b[0m\n\u001b[0;32m----> 1\u001b[0m square \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mlambda\u001b[39;00m x: \u001b[43mx\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mx\u001b[49m\n\u001b[1;32m 2\u001b[0m square\n", "\u001b[0;31mTypeError\u001b[0m: can't multiply sequence by non-int of type 'str'" ] } ], "source": [ "square(\"not gonna work\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Your turn\n", "\n", "*Define a* ```lambda``` *function that raises a number to the power of 3 then divides by 3*" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "power3 = lambda x: x**3 / 3" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "power3(3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*In a loop, pass you function all integers from 1 to 100*" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*Rewrite the Thiem function from above as a* ```lambda``` *function that only accepts an argument for one variable (you chose) then \"sweep\" over that variable with a* ```range``` *of values in loop*" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Scripts\n", "\n", "If you quit from the Python interpreter and enter it again, the definitions you have made (functions and variables - _e.g._ ALL YOUR HARD WORK) are lost. Therefore, if you want to write a somewhat longer program, you are better off using a text editor to prepare the input for the interpreter and running it with that file as input instead. This is known as creating a script. As your program gets longer, you may want to split it into several files for easier maintenance. You may also want to use a handy function that you’ve written in several programs without copying its definition into each program.\n", "\n", "To support this, Python has a way to put definitions in a file and use them in a script or in an interactive instance of the interpreter. Such a file is called a module; definitions from a module can be imported into other modules or into the main module (the collection of variables that you have access to in a script executed at the top level and in calculator mode).\n", "\n", "A module is a file containing Python definitions and statements. The file name is the module name with the suffix .py appended. Within a module, the module’s name (as a string) is available as the value of the global variable __name__. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Your turn\n", "\n", "*Using a text editor, put the* ```thiem()``` *and* ```thiem_list()``` *in to a file named* ```thiem_functions.py```. *Make sure you put the file in the same directory as the notebooks we are using. Test that you have everything working by typing* ```import thiem_functions```" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import thiem_functions" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from thiem_functions import thiem" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from thiem_functions import thiem as thm" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "thiem(100, 100)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "thm(100, 100)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*call both functions in the* ```thiem_function``` *module using the correct signature (a.k.a arguments)*" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "thiem_functions.thiem(100, 100)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A module can contain executable statements as well as function definitions. These statements are intended to initialize the module. They are executed only the first time the module name is encountered in an import statement. [1] (They are also run if the file is executed as a script.)\n", "\n", "Each module has its own private symbol table, which is used as the global symbol table by all functions defined in the module. Thus, the author of a module can use global variables in the module without worrying about accidental clashes with a user’s global variables. On the other hand, if you know what you are doing you can touch a module’s global variables with the same notation used to refer to its functions, modname.itemname.\n", "\n", "Modules can import other modules. It is customary but not required to place all import statements at the beginning of a module (or script, for that matter). The imported module names are placed in the importing module’s global symbol table.\n", "\n", "There is a variant of the import statement that imports names from a module directly into the importing module’s symbol table. For example:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from thiem_functions import thiem, thiem_list" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "thiem(500.0, 100.0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Your turn\n", "\n", "*Using a text editor, create a new python script that* ```import```*s the* ```thiem_functions``` *module and uses it to calculate some results. Then run your new script from the command line*" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.7" } }, "nbformat": 4, "nbformat_minor": 4 }