<jupyter_start><jupyter_text>**Note**: Click on "*Kernel*" > "*Restart Kernel and Clear All Outputs*" in [JupyterLab](https://jupyterlab.readthedocs.io/en/stable/) *before* reading this notebook to reset its output. If you cannot run this file on your machine, you may want to open it [in the cloud ](https://mybinder.org/v2/gh/webartifex/intro-to-data-science/main?urlpath=lab/tree/00_python_in_a_nutshell/05_content_functions.ipynb).  Chapter 0: Python in a Nutshell (Part 3) One big idea in software engineering is to **modularize** code. The purpose of that is manyfold. Two very important motivations are:- make a code block **re-usable** and- give it a meaningful name.The latter gets more important as the codebase in a project grows so big that we can only look at a tiny fraction of it at one point in time.  Defining Functions One syntactical construct to achieve modularization is that of a **function definition**. Just like in math, we can "define" a function as a set of parametrized instructions that provide some **output** given some **input**.A function is defined with the `def` statement: After the `def` part comes the name of the function followed by the **parameter list** within parentheses. The first couple of lines in the function's body should be a so-called **docstring** that describes what the function does in plain English. Then, comes the code that is to be made repeatable. In the example below, we simply copy & pasted the code to calculate the sum of all even numbers in a `list` into the example function `add_evens()`. Note that we exchanged the variable name `total` with `result` here to illustrate a point further below. In order for the function to provide back the output to "the outside world," we use the `return` statement (Hint: to see its effect simply re-run the couple of code cells below with and without the `return result` line).<jupyter_code>def add_evens(numbers):
    """Sum up all the even numbers in a list.

    Args:
        numbers (list of int's): numbers to be summed up

    Returns:
        total (int)
    """
    result = 0

    for number in numbers:
        if number % 2 == 0:  # if the number is even
            result = result + number

    return result<jupyter_output><empty_output><jupyter_text>After defining a function, we can **call** (i.e., "execute") it with the `()` operator.Let's execute the function with `numbers` as the input. We see the same `6` below the cell as we do above where we run the code without a function. Without the `return` statement in the function's body, we would not see any output here.To see what happens in detail, take a look at [PythonTutor ](https://pythontutor.com/visualize.htmlcode=numbers%20%3D%20%5B1,%202,%203,%204%5D%0A%0Adef%20add_evens%28numbers%29%3A%0A%20%20%20%20%22%22%22Sum%20up%20all%20the%20even%20numbers%20in%20a%20list.%22%22%22%0A%20%20%20%20result%20%3D%200%0A%0A%20%20%20%20for%20number%20in%20numbers%3A%0A%20%20%20%20%20%20%20%20if%20number%20%25%202%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20result%20%3D%20result%20%2B%20number%0A%0A%20%20%20%20return%20result%0A%0Atotal%20%3D%20add_evens%28numbers%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) again. You should notice how there are two variables by the name `numbers` in memory. Python manages the memory with a concept called **namespaces** or **scopes**, which are just fancy terms for saying that Python can tell variables from different contexts apart.<jupyter_code>numbers = [1, 2, 3, 4]
add_evens(numbers)<jupyter_output><empty_output><jupyter_text>To re-use the *same* instructions with *different* input, we call the function a second time and give it a brand-new `list` of numbers as its input.<jupyter_code>add_evens([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])<jupyter_output><empty_output><jupyter_text>Note how the variable `result` only exists "inside" the `add_evens()` function. Hence, we see the `NameError` here.<jupyter_code>result<jupyter_output><empty_output><jupyter_text>Built-in Functions The concept of re-usable functions is so important in programming that Python comes with many [built-in functions ](https://docs.python.org/3/library/functions.html).Two popular examples are the [sum() ](https://docs.python.org/3/library/functions.htmlsum) and [len() ](https://docs.python.org/3/library/functions.htmllen) functions that calculate the sum or the number of elements in a `list`.<jupyter_code>sum(numbers)
len(numbers)<jupyter_output><empty_output><jupyter_text>When working with numbers, the [round() ](https://docs.python.org/3/library/functions.htmlround) function rounds `float`ing-point numbers (i.e., real numbers in the mathematical sense) into `int`egers. `float`s are numbers containing a `.` somewhere in its digits.<jupyter_code>round(7.1)
round(7.9)<jupyter_output><empty_output><jupyter_text>The [round() ](https://docs.python.org/3/library/functions.htmlround) function takes a second input called `ndigits` that allows us to customize the rounding even further. Then, [round() ](https://docs.python.org/3/library/functions.htmlround) returns a `float`ing-point number!<jupyter_code>round(7.123, 1)
round(7.123, 2)<jupyter_output><empty_output><jupyter_text>As we saw before, the [print() ](https://docs.python.org/3/library/functions.htmlprint) function simply "prints" out its input to the screen. Below is the popular "Hello World" example that is shown in almost any introduction text on any programming language. The double quotes `"` are a delimiter that specifies anything in between them as **textual data**. The docstring above in the tripple-double quotes notation is just a special case allowing the text to span several lines.The quotes themselves are *not* part of the value. So, they are *not* printed out.<jupyter_code>print("Hello World")<jupyter_output>Hello World<jupyter_text>Single quotes `'` are synonyms for double quotes `"`.<jupyter_code>print('Hello World')<jupyter_output>Hello World<jupyter_text>The [print() ](https://docs.python.org/3/library/functions.htmlprint) function is often helpful to **debug** a code snippet (i.e., trying to figure out what it does, step by step).<jupyter_code>for number in numbers:
    square = number ** 2
    print("The square of", number, "is", square)<jupyter_output>The square of 1 is 1
The square of 2 is 4
The square of 3 is 9
The square of 4 is 16<jupyter_text>The Standard Library In the Python community, we even say that "Python comes with batteries included," meaning that a plain Python installation (like the one you are probably using to execute this notebook) offers all kinds of functionalities for a multitude of application domains. Thus, the name **general purpose** language.To "enable" most of these, however, we need to first **import** them from the so-called [standard library ](https://docs.python.org/3/library/index.html). Let's do a quick example here and look at the [random ](https://docs.python.org/3/library/random.html) module that provides functionalities to simulate and work with random numbers.<jupyter_code>import random<jupyter_output><empty_output><jupyter_text>To access a function inside the [random ](https://docs.python.org/3/library/random.html) module, for example, the [random() ](https://docs.python.org/3/library/random.htmlrandom.random) function, we use the `.` operator, formally called the attribute access operator. The [random() ](https://docs.python.org/3/library/random.htmlrandom.random) function simply returns a random decimal number between `0` and `1`.<jupyter_code>random.random()<jupyter_output><empty_output><jupyter_text>It could be used, for example, to model a fair coin toss by comparing the number it returns to `0.5` with the `<` operator: In 50% of the cases we see `True` and in the other 50% `False`.<jupyter_code>random.random() < 0.5<jupyter_output><empty_output><jupyter_text>A second example would be the [choice() ](https://docs.python.org/3/library/random.htmlrandom.choice) function, which draws a random element from a `list` with replacement. We could use it to model a fair die.<jupyter_code>random.choice([1, 2, 3, 4, 5, 6])<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10815408919674323, 'fraction_numerical': 0.03293231255316564, 'mean_word_length': 5.54733492442323, 'pattern_counts': {'":': 0, '<': 56, '<?xml version=': 0, '>': 55, 'https://': 17, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '14', 'dir': 'jupyter-structured-clean-dedup', 'id': '661055', 'license': 'MIT', 'n_tokens_mistral': 2681, 'n_tokens_neox': 2463, 'n_words': 937, 'path': '00_python_in_a_nutshell/05_content_functions.ipynb', 'repo_name': 'webartifex/cscm-workshop-intro-machine-learning', 'size': '8229'}