From 3bbcc3a04dd89c8efa8391a2755bd5ca6b845a8e Mon Sep 17 00:00:00 2001 From: Alexander Hess Date: Wed, 5 Feb 2020 18:25:39 +0100 Subject: [PATCH 1/3] Streamline text --- 01_elements_00_lecture.ipynb | 8 ++++---- 01_elements_02_exercises.ipynb | 2 +- 06_text_00_lecture.ipynb | 2 +- 08_mappings_00_lecture.ipynb | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/01_elements_00_lecture.ipynb b/01_elements_00_lecture.ipynb index b37b958..ba54aa3 100644 --- a/01_elements_00_lecture.ipynb +++ b/01_elements_00_lecture.ipynb @@ -1437,7 +1437,7 @@ } }, "source": [ - "### Value" + "### Value / \"Meaning\"" ] }, { @@ -1448,7 +1448,7 @@ } }, "source": [ - "Almost trivially, every object also has a value to which it **evaluates** when referenced. We think of the value as the **conceptual idea** of what the $0$s and $1$s in the bag mean to *humans*.\n", + "Almost trivially, every object also has a value to which it **evaluates** when referenced. We think of the value as the **conceptual idea** of what the $0$s and $1$s in the bag mean to *humans*. In other words, an object's value regards its *semantic* meaning.\n", "\n", "For built-in data types, Python prints out an object's value as a so-called **[literal](https://docs.python.org/3/reference/lexical_analysis.html#literals)**: This means that we may copy and paste the value back into a code cell and create a *new* object with the *same* value." ] @@ -2981,7 +2981,7 @@ } }, "source": [ - "Variable names may contain upper and lower case letters, numbers, and underscores (i.e., `_`) and be as long as we want them to be. However, they must not begin with a number. Also, they must not be any of Python's built-in **[keywords](https://docs.python.org/3/reference/lexical_analysis.html#keywords)**.\n", + "Variable names may contain upper and lower case letters, numbers, and underscores (i.e., `_`) and be as long as we want them to be. However, they must not begin with a number. Also, they must not be any of Python's built-in **[keywords](https://docs.python.org/3/reference/lexical_analysis.html#keywords)** like `for` or `if`.\n", "\n", "Variable names should be chosen such that they do not need any more documentation and are self-explanatory. A widespread convention is to use so-called **[snake\\_case](https://en.wikipedia.org/wiki/Snake_case)**: Keep everything lowercase and use underscores to separate words.\n", "\n", @@ -3734,7 +3734,7 @@ " - include [built-in functions](https://docs.python.org/3/library/functions.html) like [print()](https://docs.python.org/3/library/functions.html#print), [sum()](https://docs.python.org/3/library/functions.html#sum), or [len()](https://docs.python.org/3/library/functions.html#len)\n", "\n", "\n", - "- flow control (cf., [Chapter 3](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/03_conditionals_00_lecture.ipynb))\n", + "- flow control (cf., [Chapter 3](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/03_conditionals_00_lecture.ipynb) and [Chapter 4](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration_00_lecture.ipynb))\n", " - expression of **business logic** or an **algorithm**\n", " - conditional execution of parts of a program (e.g., `if` statements)\n", " - repetitive execution of parts of a program (e.g., `for`-loops)" diff --git a/01_elements_02_exercises.ipynb b/01_elements_02_exercises.ipynb index 56bf985..64fc68f 100644 --- a/01_elements_02_exercises.ipynb +++ b/01_elements_02_exercises.ipynb @@ -88,7 +88,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q3**: Lastly, what does the `end=\"\\n\"` mean in the documentation? Use it in the `for`-loop and print the numbers 1 through 10 in just *one* line!" + "**Q3**: Lastly, what does the `end=\"\\n\"` mean in the documentation? Use it in the `for`-loop and print the numbers 1 through 10 in just *one* line of output!" ] }, { diff --git a/06_text_00_lecture.ipynb b/06_text_00_lecture.ipynb index d1bbc91..fc0eeb2 100644 --- a/06_text_00_lecture.ipynb +++ b/06_text_00_lecture.ipynb @@ -124,7 +124,7 @@ } }, "source": [ - "A `str` object evaluates to itself in a literal notation with enclosing **single quotes** `'` by default. In [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements_00_lecture.ipynb#Value), we already specified the double quotes `\"` convention we stick to in this book. Yet, single quotes `'` and double quotes `\"` are *perfect* substitutes for all `str` objects that do *not* contain any of the two symbols in it. We could use the reverse convention, as well.\n", + "A `str` object evaluates to itself in a literal notation with enclosing **single quotes** `'` by default. In [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements_00_lecture.ipynb#Value-/-\"Meaning\"), we already specified the double quotes `\"` convention we stick to in this book. Yet, single quotes `'` and double quotes `\"` are *perfect* substitutes for all `str` objects that do *not* contain any of the two symbols in it. We could use the reverse convention, as well.\n", "\n", "As [this discussion](https://stackoverflow.com/questions/56011/single-quotes-vs-double-quotes-in-python) shows, many programmers have *strong* opinions about that and make up *new* conventions for their projects. Consequently, the discussion was \"closed as not constructive\" by the moderators." ] diff --git a/08_mappings_00_lecture.ipynb b/08_mappings_00_lecture.ipynb index 79483b5..69ff961 100644 --- a/08_mappings_00_lecture.ipynb +++ b/08_mappings_00_lecture.ipynb @@ -45,7 +45,7 @@ "source": [ "A *mapping* is a one-to-one correspondence from a set of **keys** to a set of **values**. In other words, a *mapping* is a *collection* of **key-value pairs**, also called **items** for short.\n", "\n", - "In the context of mappings, the term *value* has a meaning different from the general *value* that *every* object has: In the \"bag\" analogy from [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements_00_lecture.ipynb#Value), we descibe an object's value to be the concrete $0$s and $1$s it contains. Here, the terms *key* and *value* mean the *role* an object takes within a mapping. Both, *keys* and *values*, are real *objects* with a distinct *value*. So, the student should always remember the double meaning of the term *value* in this chapter!\n", + "In the context of mappings, the term *value* has a meaning different from the general *value* that *every* object has: In the \"bag\" analogy from [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements_00_lecture.ipynb#Value-/-\"Meaning\"), we descibe an object's value to be the concrete $0$s and $1$s it contains. Here, the terms *key* and *value* mean the *role* an object takes within a mapping. Both, *keys* and *values*, are real *objects* with a distinct *value*. So, the student should always remember the double meaning of the term *value* in this chapter!\n", "\n", "Let's continue with an example. To create a `dict` object, we commonly use the literal notation, `{..: .., ..: .., ...}`, and list all the items. `to_words` below maps the `int` objects `0`, `1`, and `2` to their English word equivalents, `\"zero\"`, `\"one\"`, and `\"two\"`, and `from_words` does the opposite. A stylistic side note: Pythonistas often expand `dict` or `list` definitions by writing each item or element on a line on their own. The commas `,` after the *last* items are *not* a mistake, as well, although they *may* be left out. Besides easier reading, such style has actual technical advantages (cf., [source](https://www.python.org/dev/peps/pep-0008/#when-to-use-trailing-commas)) that we do not go into detail about here." ] @@ -707,7 +707,7 @@ } }, "source": [ - "The [glossary](https://docs.python.org/3/glossary.html#term-hashable) states a second requirement for hashability, namely that \"objects which *compare equal* must have the *same* hash value.\" The purpose of this is to ensure that if we put, for example, `1` as a key in a `dict` object, we can look it up later with `1.0`. In other words, we can look up keys by their object's value (i.e., in the meaning of [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements_00_lecture.ipynb#Value)). The converse statement does *not* hold: Two objects *may* (accidentally) have the *same* hash value and *not* compare equal." + "The [glossary](https://docs.python.org/3/glossary.html#term-hashable) states a second requirement for hashability, namely that \"objects which *compare equal* must have the *same* hash value.\" The purpose of this is to ensure that if we put, for example, `1` as a key in a `dict` object, we can look it up later with `1.0`. In other words, we can look up keys by their object's value (i.e., in the meaning of [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements_00_lecture.ipynb#Value-/-\"Meaning\")). The converse statement does *not* hold: Two objects *may* (accidentally) have the *same* hash value and *not* compare equal." ] }, { From 3754297c9333df3dd3776e6a66946d92a14ad2cc Mon Sep 17 00:00:00 2001 From: Alexander Hess Date: Wed, 5 Feb 2020 18:31:52 +0100 Subject: [PATCH 2/3] Refurbish chapter 02 - streamline text - streamline example functions - move section on built-ins to beginning - add notes on the concept of a callable --- 02_functions_00_lecture.ipynb | 2275 +++++++++++++++++++++---------- 02_functions_01_review.ipynb | 26 +- 02_functions_02_exercises.ipynb | 12 +- sample_module.py | 41 +- 4 files changed, 1639 insertions(+), 715 deletions(-) diff --git a/02_functions_00_lecture.ipynb b/02_functions_00_lecture.ipynb index 6dcccec..69170d8 100644 --- a/02_functions_00_lecture.ipynb +++ b/02_functions_00_lecture.ipynb @@ -19,11 +19,9 @@ } }, "source": [ - "In [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements_00_lecture.ipynb#Example:-Averaging-Even-Numbers), we typed the code to calculate the average of the even numbers in a list into several code cells. Then, we executed them one after another. We had no way of **reusing** the code except for either executing cells multiple times or copying and pasting their contents into other cells. And, whenever we find ourselves doing repetitive manual work, we can be sure that there must be a way of automating what we are doing.\n", + "In [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements_00_lecture.ipynb#Example:-Averaging-Even-Numbers), we simply typed the code to calculate the average of the even numbers in a list of whole numbers into several code cells. Then, we executed them one after another. We had no way of *reusing* the code except for either executing cells multiple times. And, whenever we find ourselves doing repetitive manual work, we can be sure that there must be a way of automating what we are doing.\n", "\n", - "At the same time, we executed built-in functions (e.g., [print()](https://docs.python.org/3/library/functions.html#print), [sum()](https://docs.python.org/3/library/functions.html#sum), [len()](https://docs.python.org/3/library/functions.html#len), [id()](https://docs.python.org/3/library/functions.html#id), or [type()](https://docs.python.org/3/library/functions.html#type)) that obviously must be reusing the same parts inside core Python every time we use them.\n", - "\n", - "This chapter shows how Python offers language constructs that let us **define** functions ourselves that we may then **call** just like the built-in ones." + "This chapter shows how Python offers language constructs that let us **define** functions ourselves that we may then **call** just like the built-in ones. Also, we look at how we can extend our Python installation with functionalities written by other people." ] }, { @@ -34,7 +32,7 @@ } }, "source": [ - "## Function Definition" + "## [Built-in Functions](https://docs.python.org/3/library/functions.html)" ] }, { @@ -45,19 +43,9 @@ } }, "source": [ - "So-called **[user-defined functions](https://docs.python.org/3/reference/compound_stmts.html#function-definitions)** may be created with the `def` statement. To extend an already familiar example, we reuse the introductory example from [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements_00_lecture.ipynb#Best-Practices) in its final Pythonic version and transform it into the function `average_evens()` below. \n", + "Python comes with plenty of useful functions built in, some of which we have already seen before (e.g., [print()](https://docs.python.org/3/library/functions.html#print), [sum()](https://docs.python.org/3/library/functions.html#sum), [len()](https://docs.python.org/3/library/functions.html#len), or [id()](https://docs.python.org/3/library/functions.html#id)). The [documentation](https://docs.python.org/3/library/functions.html) has the full list. Just as core Python itself, they are mostly implemented in C and thus very fast.\n", "\n", - "A function's **name** must be chosen according to the same naming rules as ordinary variables as Python manages function names like variables. In this book, we further adopt the convention of ending function names with parentheses `()` in text cells for faster comprehension when reading (i.e., `average_evens()` vs. `average_evens`). These are *not* part of the name but must always be written out in the `def` statement for syntactic reasons.\n", - "\n", - "Functions may define an arbitrary number of **parameters** as inputs that can then be referenced within the indented **code block**: They are listed within the parentheses in the `def` statement (i.e., `numbers` below). \n", - "\n", - "The code block is also called a function's **body**, while the first line starting with `def` and ending with a colon is the **header**.\n", - "\n", - "Together, the name and the list of parameters are also referred to as the function's **[signature](https://en.wikipedia.org/wiki/Type_signature)** (i.e., `average_evens(numbers)` below).\n", - "\n", - "A function may come with an *explicit* **return value** (i.e., \"result\" or \"output\") specified with the `return` statement (cf., [reference](https://docs.python.org/3/reference/simple_stmts.html#the-return-statement)): Functions that have one are considered **fruitful**; otherwise, they are **void**. Functions of the latter kind are still useful because of their **side effects** as, for example, the built-in [print()](https://docs.python.org/3/library/functions.html#print) function. Strictly speaking, they also have an *implicit* return value of `None`.\n", - "\n", - "A function should define a **docstring** that describes what it does in a short subject line, what parameters it expects (i.e., their types), and what it returns (if anything). A docstring is a syntactically valid multi-line string (i.e., type `str`) defined within **triple-double quotes** `\"\"\"`. Strings are covered in depth in [Chapter 6](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/06_text_00_lecture.ipynb#The-str-Type). Widely adopted standards as to how to format a docstring are [PEP 257](https://www.python.org/dev/peps/pep-0257/) and section 3.8 in [Google's Python Style Guide](https://github.com/google/styleguide/blob/gh-pages/pyguide.md)." + "Below, [sum()](https://docs.python.org/3/library/functions.html#sum) adds up all the elements in the `numbers` list while [len()](https://docs.python.org/3/library/functions.html#len) counts the number of elements in it." ] }, { @@ -70,16 +58,841 @@ }, "outputs": [], "source": [ - "def average_evens(numbers):\n", + "numbers = [7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "78" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sum(numbers)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "12" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(numbers)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "`sum` and `len` are *no* [keywords](https://docs.python.org/3/reference/lexical_analysis.html#keywords) like `for` or `if` but variables that reference *objects* in memory. Often, we hear people say that \"everything is an object in Python\" (e.g., this [question](https://stackoverflow.com/questions/40478536/in-python-what-does-it-mean-by-everything-is-an-object)). While this phrase may sound abstract in the beginning, it simply means that the entire memory is organized with \"bags\" of $0$s and $1$s, and there are even bags for the built-in functions. That is *not* true for many other languages (e.g., C or Java) and often a source of confusion for people coming to Python from another language.\n", + "\n", + "The built-in [id()](https://docs.python.org/3/library/functions.html#id) function tells us where in memory a particular built-in function is stored." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "140413081843264" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "id(sum)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "140413081842224" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "id(len)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "[type()](https://docs.python.org/3/library/functions.html#type) reveals that built-in functions like [sum()](https://docs.python.org/3/library/functions.html#sum) or [len()](https://docs.python.org/3/library/functions.html#len) are objects of type `builtin_function_or_method`." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "builtin_function_or_method" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(sum)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "builtin_function_or_method" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(len)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Python's object-oriented nature allows us to have functions work with themselves. While seemingly not useful from a beginner's point of view, that enables a lot of powerful programming styles later on." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "140413081841824" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "id(id)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "builtin_function_or_method" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(id)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "To execute a function, we **call** it with the **call operator** `()` as shown many times in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements_00_lecture.ipynb) and above.\n", + "\n", + "If we are unsure whether a variable references a function or not, we can verify that with the built-in [callable()](https://docs.python.org/3/library/functions.html#callable) function.\n", + "\n", + "Abstractly speaking, *any* object that can be called with the call operator `()` is a so-called **callable**. And, objects of type `builtin_function_or_method` are just one kind of examples thereof. We will see another one already in the next sub-section." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "callable(sum)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "callable(len)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "`list` objects, for example, are *not* callable." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "callable(numbers)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### Constructors" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The list of [built-in functions](https://docs.python.org/3/library/functions.html) in the documentation should really be named a list of built-in *callables*.\n", + "\n", + "Besides the built-in functions, the list also features **constructors** for the built-in types. They may be used to **[cast](https://en.wikipedia.org/wiki/Type_conversion)** (i.e., \"convert\") any object as an object of a given type.\n", + "\n", + "For example, to \"convert\" a `float` or a `str` into an `int` object, we use the [int()](https://docs.python.org/3/library/functions.html#int) built-in. Below, *new* `int` objects are created from the `7.0` and `\"7\"` objects that are *newly* created themselves before being processed by [int()](https://docs.python.org/3/library/functions.html#int) right away *without* ever being referenced by a variable." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "7" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "int(7.0)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "7" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "int(\"7\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Casting an object as an `int` is different from rounding with the built-in [round()](https://docs.python.org/3/library/functions.html#round) function!" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "7" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "int(7.99)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "8" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "round(7.99)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Notice the subtle difference compared to the behavior of the `//` operator in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements_00_lecture.ipynb##%28Arithmetic#%29-Operators) that \"rounds\" towards minus infinity: [int()](https://docs.python.org/3/library/functions.html#int) always \"rounds\" towards `0`." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "-7" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "int(-7.99)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Not all conversions are valid and *runtime* errors may occur as the `ValueError` shows." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "ename": "ValueError", + "evalue": "invalid literal for int() with base 10: 'seven'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"seven\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mValueError\u001b[0m: invalid literal for int() with base 10: 'seven'" + ] + } + ], + "source": [ + "int(\"seven\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "We may also cast in the other direction with the [float()](https://docs.python.org/3/library/functions.html#float) or [str()](https://docs.python.org/3/library/functions.html#func-str) built-ins." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "7.0" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "float(7)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'7'" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "str(7)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Constructors are full-fledged objects as well." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "94916229764288" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "id(int)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "94916229768192" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "id(float)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "They are of type `type`, which is different from `builtin_function_or_method` above." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "type" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(int)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "type" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(float)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "As already noted, constructors are *callables*. In that regard, they behave the same as built-in functions. We may call them with the call operator `()`." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "callable(int)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "callable(float)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The attentive student may already have discovered that we refer to `builtin_function_or_method` objects as \"built-in functions\" and `type` objects as just \"built-ins.\" For a beginner, that difference is not so important. But, the ambitious student should already be aware that such subtleties exist.\n", + "\n", + "Next, let's look at a third kind of callables." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## Function Definitions" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "We may create so-called *user-defined* **functions** with the `def` statement (cf., [reference](https://docs.python.org/3/reference/compound_stmts.html#function-definitions)). To extend an already familiar example, we reuse the introductory example from [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements_00_lecture.ipynb#Best-Practices) in its final Pythonic version and transform it into the function `average_evens()` below. We replace the variable name `numbers` with `integers` for didactical purposes in the first couple of examples.\n", + "\n", + "A function's **name** must be chosen according to the same naming rules as ordinary variables since Python manages function names like variables. In this book, we further adopt the convention of ending function names with parentheses `()` in text cells for faster comprehension when reading (i.e., `average_evens()` vs. `average_evens`). These are *not* part of the name but must always be written out in the `def` statement for syntactic reasons.\n", + "\n", + "Functions may define an arbitrary number of **parameters** as inputs that can then be referenced within the indented **code block**: They are listed within the parentheses in the `def` statement (i.e., `integers` below). \n", + "\n", + "The code block is also called a function's **body**, while the first line starting with `def` and ending with a colon is the **header**.\n", + "\n", + "Together, the name and the list of parameters are also referred to as the function's **[signature](https://en.wikipedia.org/wiki/Type_signature)** (i.e., `average_evens(integers)` below).\n", + "\n", + "A function may specify an *explicit* **return value** (i.e., \"result\" or \"output\") with the `return` statement (cf., [reference](https://docs.python.org/3/reference/simple_stmts.html#the-return-statement)): Functions that have one are considered **fruitful**; otherwise, they are **void**. Functions of the latter kind are still useful because of their **side effects**. For example, the built-in [print()](https://docs.python.org/3/library/functions.html#print) function changes what we see on the screen. Strictly speaking, [print()](https://docs.python.org/3/library/functions.html#print) and other void functions also have an *implicit* return value, namely the `None` object.\n", + "\n", + "A function should define a **docstring** that describes what it does in a short subject line, what parameters it expects (i.e., their types), and what it returns, if anything. A docstring is a syntactically valid multi-line string (i.e., type `str`) defined within **triple-double quotes** `\"\"\"`. Strings are covered in depth in [Chapter 6](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/06_text_00_lecture.ipynb#The-str-Type). Widely adopted standards for docstrings are [PEP 257](https://www.python.org/dev/peps/pep-0257/) and section 3.8 of [Google's Python Style Guide](https://github.com/google/styleguide/blob/gh-pages/pyguide.md)." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "def average_evens(integers):\n", " \"\"\"Calculate the average of all even numbers in a list.\n", "\n", " Args:\n", - " numbers (list): a list of numbers; may be integers or floats\n", + " integers (list of int's): whole numbers to be averaged\n", "\n", " Returns:\n", " float: average\n", " \"\"\"\n", - " evens = [n for n in numbers if n % 2 == 0]\n", + " evens = [n for n in integers if n % 2 == 0]\n", " average = sum(evens) / len(evens)\n", " return average" ] @@ -92,12 +905,12 @@ } }, "source": [ - "Once defined, a function may be referenced just like any other variable by its name (i.e., *without* the parenthesis). Its value might seem awkward at first: It consists of the location where we defined the function (i.e., `__main__`, which is Python's way of saying \"in this notebook\") and the signature." + "Once defined, a function may be referenced just like any other variable by its name (i.e., *without* the parentheses)." ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 28, "metadata": { "slideshow": { "slide_type": "slide" @@ -107,10 +920,10 @@ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 2, + "execution_count": 28, "metadata": {}, "output_type": "execute_result" } @@ -127,25 +940,25 @@ } }, "source": [ - "A function is an **object** on its own with an **identity** (i.e., memory location) and a **type**, namely `function`." + "This works as functions are full-fledged *objects*. So, `average_evens` is just a name referencing an object in memory with an **identity**, a **type**, namely `function`, and a **value**. In that regard, `average_evens` is *no* different from the variable `numbers` or the built-ins' names." ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 29, "metadata": { "slideshow": { - "slide_type": "-" + "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ - "140371816700960" + "140413021591744" ] }, - "execution_count": 3, + "execution_count": 29, "metadata": {}, "output_type": "execute_result" } @@ -156,7 +969,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 30, "metadata": { "slideshow": { "slide_type": "-" @@ -169,7 +982,7 @@ "function" ] }, - "execution_count": 4, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } @@ -178,6 +991,76 @@ "type(average_evens)" ] }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Its value may seem awkward at first: It consists of a location showing where the function is defined (i.e., `__main__` here, which is Python's way of saying \"in this notebook\") and the signature wrapped inside angle brackets `<` and `>`.\n", + " \n", + "The angle brackets are a convention to indicate that the value may *not* be used as a *literal* (i.e., typed back into another code cell). Chapter 10 introduces the concept of a **text representation** of an object, which is related to the *semantic* meaning of an object's value as discussed in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements_00_lecture.ipynb#Value-/-\"Meaning\"), and the angle brackets convention is one such way to represent an object as text. When executed, the angle brackets cause a `SyntaxError` because Python expects the `<` operator to come with an operand on both sides (cf., [Chapter 3](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/03_conditionals_00_lecture.ipynb#Relational-Operators))." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "ename": "SyntaxError", + "evalue": "invalid syntax (, line 1)", + "output_type": "error", + "traceback": [ + "\u001b[0;36m File \u001b[0;32m\"\"\u001b[0;36m, line \u001b[0;32m1\u001b[0m\n\u001b[0;31m \u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid syntax\n" + ] + } + ], + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "`average_evens` is, of course, callable. So, the `function` type is the third kind of callable in this chapter." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "callable(average_evens)" + ] + }, { "cell_type": "markdown", "metadata": { @@ -193,10 +1076,10 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 33, "metadata": { "slideshow": { - "slide_type": "slide" + "slide_type": "skip" } }, "outputs": [ @@ -206,11 +1089,11 @@ "text": [ "Help on function average_evens in module __main__:\n", "\n", - "average_evens(numbers)\n", + "average_evens(integers)\n", " Calculate the average of all even numbers in a list.\n", " \n", " Args:\n", - " numbers (list): a list of numbers; may be integers or floats\n", + " integers (list of int's): whole numbers to be averaged\n", " \n", " Returns:\n", " float: average\n", @@ -230,12 +1113,12 @@ } }, "source": [ - "In a Jupyter notebook, we can just as well add a question mark to a function's name to achieve the same. Then, a small tab opens in our browser." + "In a Jupyter notebook, we can just as well add a question mark to a function's name to achieve the same." ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 34, "metadata": { "slideshow": { "slide_type": "skip" @@ -245,16 +1128,16 @@ { "data": { "text/plain": [ - "\u001b[0;31mSignature:\u001b[0m \u001b[0maverage_evens\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnumbers\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mSignature:\u001b[0m \u001b[0maverage_evens\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mintegers\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\n", "Calculate the average of all even numbers in a list.\n", "\n", "Args:\n", - " numbers (list): a list of numbers; may be integers or floats\n", + " integers (list of int's): whole numbers to be averaged\n", "\n", "Returns:\n", " float: average\n", - "\u001b[0;31mFile:\u001b[0m ~/repos/intro-to-python/\n", + "\u001b[0;31mFile:\u001b[0m ~/repos/intro-to-python/\n", "\u001b[0;31mType:\u001b[0m function\n" ] }, @@ -274,12 +1157,12 @@ } }, "source": [ - "Two question marks even show a function's source code." + "Two question marks show a function's source code." ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 35, "metadata": { "slideshow": { "slide_type": "skip" @@ -289,21 +1172,21 @@ { "data": { "text/plain": [ - "\u001b[0;31mSignature:\u001b[0m \u001b[0maverage_evens\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnumbers\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mSignature:\u001b[0m \u001b[0maverage_evens\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mintegers\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mSource:\u001b[0m \n", - "\u001b[0;32mdef\u001b[0m \u001b[0maverage_evens\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnumbers\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;32mdef\u001b[0m \u001b[0maverage_evens\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mintegers\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n", "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Calculate the average of all even numbers in a list.\u001b[0m\n", "\u001b[0;34m\u001b[0m\n", "\u001b[0;34m Args:\u001b[0m\n", - "\u001b[0;34m numbers (list): a list of numbers; may be integers or floats\u001b[0m\n", + "\u001b[0;34m integers (list of int's): whole numbers to be averaged\u001b[0m\n", "\u001b[0;34m\u001b[0m\n", "\u001b[0;34m Returns:\u001b[0m\n", "\u001b[0;34m float: average\u001b[0m\n", "\u001b[0;34m \"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mevens\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0mn\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mn\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mnumbers\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0;36m2\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mevens\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0mn\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mn\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mintegers\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0;36m2\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\n", "\u001b[0;34m\u001b[0m \u001b[0maverage\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0msum\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mevens\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m/\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mevens\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0maverage\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mFile:\u001b[0m ~/repos/intro-to-python/\n", + "\u001b[0;31mFile:\u001b[0m ~/repos/intro-to-python/\n", "\u001b[0;31mType:\u001b[0m function\n" ] }, @@ -315,6 +1198,46 @@ "average_evens??" ] }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "[help()](https://docs.python.org/3/library/functions.html#help) and the `?`s also work for built-ins." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Help on built-in function sum in module builtins:\n", + "\n", + "sum(iterable, start=0, /)\n", + " Return the sum of a 'start' value (default: 0) plus an iterable of numbers\n", + " \n", + " When the iterable is empty, return the start value.\n", + " This function is intended specifically for use with numeric values and may\n", + " reject non-numeric types.\n", + "\n" + ] + } + ], + "source": [ + "help(sum)" + ] + }, { "cell_type": "markdown", "metadata": { @@ -334,30 +1257,17 @@ } }, "source": [ - "We **call** (i.e., \"execute\") a function with the **call operator** `()` as often as we wish. The formal parameters are filled in by passing variables or expressions as **arguments** to the function within the parentheses." + "Once defined, we may call a function with the call operator `()` as often as we wish. The formal parameters are then filled in by **passing** *expressions* (e.g., literals or variables) as **arguments** to the function within the parentheses." ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 37, "metadata": { "slideshow": { "slide_type": "slide" } }, - "outputs": [], - "source": [ - "nums = [7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "slideshow": { - "slide_type": "-" - } - }, "outputs": [ { "data": { @@ -365,13 +1275,37 @@ "7.0" ] }, - "execution_count": 9, + "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "average_evens(nums)" + "average_evens([7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4])" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "7.0" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "average_evens(numbers)" ] }, { @@ -382,71 +1316,12 @@ } }, "source": [ - "If we are ever unsure if a variable references a `function` object that may be called, we can verify that with the built-in [callable()](https://docs.python.org/3/library/functions.html#callable) function that takes any object as its only argument and tells us `True` if that is **callable** and `False` otherwise." + "A function call's return value is commonly assigned to a variable for subsequent use. Otherwise, we lose access to the returned object right away." ] }, { "cell_type": "code", - "execution_count": 10, - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "callable(average_evens)" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "False" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "callable(nums)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, - "source": [ - "The return value is commonly assigned to a variable for later reference. Otherwise, we would lose access to it in memory right away." - ] - }, - { - "cell_type": "code", - "execution_count": 12, + "execution_count": 39, "metadata": { "slideshow": { "slide_type": "fragment" @@ -454,12 +1329,12 @@ }, "outputs": [], "source": [ - "avg = average_evens(nums)" + "result = average_evens(numbers)" ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 40, "metadata": { "slideshow": { "slide_type": "-" @@ -472,13 +1347,13 @@ "7.0" ] }, - "execution_count": 13, + "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "avg" + "result" ] }, { @@ -511,12 +1386,12 @@ } }, "source": [ - "Notice how the parameters listed in a function's definition (i.e., `numbers`) and variables created inside it during execution (i.e., `evens` and `average`) are **local** to that function. That means they only reference an object in memory *while* the function is being executed and de-referenced immediately when the function returns. We say they **go out of scope** once the function terminates." + "The parameters listed in a function's definition (i.e., `integers` in the example) and variables created *inside* it during execution (i.e., `evens` and `average`) are **local** to that function. That means they only reference an object in memory *while* the function is being executed and are dereferenced immediately when the function call returns. We say they go out of **scope**. That is why we see the `NameError`s below." ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 41, "metadata": { "slideshow": { "slide_type": "slide" @@ -525,23 +1400,23 @@ "outputs": [ { "ename": "NameError", - "evalue": "name 'numbers' is not defined", + "evalue": "name 'integers' is not defined", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mnumbers\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;31mNameError\u001b[0m: name 'numbers' is not defined" + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mintegers\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mNameError\u001b[0m: name 'integers' is not defined" ] } ], "source": [ - "numbers" + "integers" ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 42, "metadata": { "slideshow": { "slide_type": "-" @@ -555,7 +1430,7 @@ "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mevens\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mevens\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mNameError\u001b[0m: name 'evens' is not defined" ] } @@ -566,7 +1441,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 43, "metadata": { "slideshow": { "slide_type": "skip" @@ -580,7 +1455,7 @@ "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0maverage\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0maverage\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mNameError\u001b[0m: name 'average' is not defined" ] } @@ -597,7 +1472,7 @@ } }, "source": [ - "[PythonTutor](http://pythontutor.com/visualize.html#code=nums%20%3D%20%5B1,%202,%203,%204,%205,%206,%207,%208,%209,%2010,%2011,%2012%5D%0A%0Adef%20average_evens%28numbers%29%3A%0A%20%20%20%20evens%20%3D%20%5Bn%20for%20n%20in%20numbers%20if%20n%20%25%202%20%3D%3D%200%5D%0A%20%20%20%20average%20%3D%20sum%28evens%29%20/%20len%28evens%29%0A%20%20%20%20return%20average%0A%0Arv%20%3D%20average_evens%28nums%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) visualizes what happens in memory: To be precise, in the exact moment when the function call is initiated and `nums` passed in as the `numbers` argument, there are *two* references to the *same* `list` object (cf., steps 4-5 in the visualization). We also see how Python creates a *new* **frame** that holds the function's local scope (i.e., \"internal names\") in addition to the **global** frame. Frames are nothing but [namespaces](https://en.wikipedia.org/wiki/Namespace) to *isolate* the names of different **scopes** from each other. The list comprehension `[n for n in numbers if n % 2 == 0]` constitutes yet another frame that is in scope as the `list` object assigned to `evens` is *being* created (cf., steps 6-20). When the function returns, only the global frame is left (cf., last step)." + "[PythonTutor](http://pythontutor.com/visualize.html#code=numbers%20%3D%20%5B7,%2011,%208,%205,%203,%2012,%202,%206,%209,%2010,%201,%204%5D%0A%0Adef%20average_evens%28integers%29%3A%0A%20%20%20%20evens%20%3D%20%5Bn%20for%20n%20in%20integers%20if%20n%20%25%202%20%3D%3D%200%5D%0A%20%20%20%20average%20%3D%20sum%28evens%29%20/%20len%28evens%29%0A%20%20%20%20return%20average%0A%0Aresult%20%3D%20average_evens%28numbers%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) visualizes what happens in memory: To be precise, in the exact moment when the function call is initiated and `numbers` passed in as the `integers` argument, there are *two* references to the *same* `list` object (cf., steps 4-5 in the visualization). We also see how Python creates a *new* **frame** that holds the function's local scope (i.e., \"internal names\") in addition to the **global** frame. Frames are nothing but [namespaces](https://en.wikipedia.org/wiki/Namespace) to *isolate* the names of different **scopes** from each other. The list comprehension `[n for n in integers if n % 2 == 0]` constitutes yet another frame that is in scope as the `list` object assigned to `evens` is *being* created (cf., steps 6-20). When the function returns, only the global frame is left (cf., last step)." ] }, { @@ -619,12 +1494,12 @@ } }, "source": [ - "On the contrary, while a function is *being* executed, it can reference the variables of **enclosing scopes** (i.e., \"outside\" of it). This is a common source of *semantic* errors. Consider the following stylized (and incorrect) example `average_wrong()`. The error is hard to spot with eyes: The function never references the `numbers` parameter but the `nums` variable in the **global scope** instead." + "On the contrary, while a function is *being* executed, it may reference the variables of **enclosing scopes** (i.e., \"outside\" of it). This is a common source of *semantic* errors. Consider the following stylized and incorrect example `average_wrong()`. The error is hard to spot with eyes: The function never references the `integers` parameter but the `numbers` variable in the **global scope** instead." ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 44, "metadata": { "slideshow": { "slide_type": "slide" @@ -632,16 +1507,16 @@ }, "outputs": [], "source": [ - "def average_wrong(numbers):\n", + "def average_wrong(integers):\n", " \"\"\"Calculate the average of all even numbers in a list.\n", "\n", " Args:\n", - " numbers (list): a list of numbers; may be integers or floats\n", + " integers (list of int's): whole numbers to be averaged\n", "\n", " Returns:\n", " float: average\n", " \"\"\"\n", - " evens = [n for n in nums if n % 2 == 0] # should reference numbers not nums\n", + " evens = [n for n in numbers if n % 2 == 0]\n", " average = sum(evens) / len(evens)\n", " return average" ] @@ -654,12 +1529,12 @@ } }, "source": [ - "`nums` in the global scope is, of course, unchanged." + "`numbers` in the global scope is, of course, *not* changed by merely defining `average_wrong()`." ] }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 45, "metadata": { "slideshow": { "slide_type": "slide" @@ -672,13 +1547,13 @@ "[7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]" ] }, - "execution_count": 18, + "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "nums" + "numbers" ] }, { @@ -689,15 +1564,15 @@ } }, "source": [ - "Sometimes a function might return a correct solution for *some* inputs ..." + "Sometimes a function may return a correct solution for *some* inputs ..." ] }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 46, "metadata": { "slideshow": { - "slide_type": "-" + "slide_type": "fragment" } }, "outputs": [ @@ -707,13 +1582,13 @@ "7.0" ] }, - "execution_count": 19, + "execution_count": 46, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "average_wrong(nums) # this is correct by accident" + "average_wrong(numbers) # correct by accident" ] }, { @@ -729,10 +1604,10 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 47, "metadata": { "slideshow": { - "slide_type": "-" + "slide_type": "fragment" } }, "outputs": [ @@ -742,7 +1617,7 @@ "7.0" ] }, - "execution_count": 20, + "execution_count": 47, "metadata": {}, "output_type": "execute_result" } @@ -759,9 +1634,9 @@ } }, "source": [ - "[PythonTutor](http://pythontutor.com/visualize.html#code=nums%20%3D%20%5B1,%202,%203,%204,%205,%206,%207,%208,%209,%2010,%2011,%2012%5D%0A%0Adef%20average_wrong%28numbers%29%3A%0A%20%20%20%20evens%20%3D%20%5Bn%20for%20n%20in%20nums%20if%20n%20%25%202%20%3D%3D%200%5D%0A%20%20%20%20average%20%3D%20sum%28evens%29%20/%20len%28evens%29%0A%20%20%20%20return%20average%0A%0Arv%20%3D%20average_wrong%28%5B123,%20456,%20789%5D%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) is again helpful at visualizing the error interactively: Creating the `list` object `evens` eventually references takes *16* computational steps, namely two for managing the list comprehension, one for setting up an empty `list` object, *twelve* for filling it with elements derived from `nums` in the global scope (i.e., that is the error), and one to make `evens` reference it (cf., steps 6-21).\n", + "[PythonTutor](http://pythontutor.com/visualize.html#code=numbers%20%3D%20%5B7,%2011,%208,%205,%203,%2012,%202,%206,%209,%2010,%201,%204%5D%0A%0Adef%20average_wrong%28integers%29%3A%0A%20%20%20%20evens%20%3D%20%5Bn%20for%20n%20in%20numbers%20if%20n%20%25%202%20%3D%3D%200%5D%0A%20%20%20%20average%20%3D%20sum%28evens%29%20/%20len%28evens%29%0A%20%20%20%20return%20average%0A%0Aresult%20%3D%20average_wrong%28%5B123,%20456,%20789%5D%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) is again helpful at visualizing the error interactively: Creating the `list` object `evens` eventually references takes *16* computational steps, namely two for managing the list comprehension, one for setting up an empty `list` object, *twelve* for filling it with elements derived from `numbers` in the global scope (i.e., that is the error), and one to make `evens` reference it (cf., steps 6-21).\n", "\n", - "The frames logic shown by PythonTutor is the mechanism by which Python not only manages the names inside *one* function call but also for *many* potentially *simultaneous* calls, as revealed in [Chapter 4](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration_00_lecture.ipynb#Trivial-Example:-Countdown). It is the reason why we may reuse the same names for the parameters and variables inside both `average_evens()` and `average_wrong()` without Python mixing them up. So, as we already read in the [Zen of Python](https://www.python.org/dev/peps/pep-0020/), \"namespaces are one honking great idea\" (cf., `import this`), and a frame is just a special kind of namespace." + "The frames logic shown by PythonTutor is the mechanism with which Python not only manages the names inside *one* function call but also for *many* potentially *simultaneous* calls, as revealed in [Chapter 4](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration_00_lecture.ipynb#Trivial-Example:-Countdown). It is the reason why we may reuse the same names for the parameters and variables inside both `average_evens()` and `average_wrong()` without Python mixing them up. So, as we already read in the [Zen of Python](https://www.python.org/dev/peps/pep-0020/), \"namespaces are one honking great idea\" (cf., `import this`), and a frame is just a special kind of namespace." ] }, { @@ -783,14 +1658,14 @@ } }, "source": [ - "Code gets even more confusing when variables by the *same* name from *different* scopes collide. In particular, what should we expect to happen if a function \"changes\" a globally defined variable internally?\n", + "Code gets even more confusing when variables by the *same* name from *different* scopes collide. In particular, what should we expect to happen if a function \"changes\" a globally defined variable in its body?\n", "\n", - "`average_odds()` below works like `average_evens()` above except that it **[casts](https://en.wikipedia.org/wiki/Type_conversion)** (i.e., \"converts\") the elements of `numbers` as objects of type `int` with the [int()](https://docs.python.org/3/library/functions.html#int) built-in first before filtering and averaging them. In doing so, it introduces an *internal* variable `nums` whose name collides with the one in the global scope. To filter for odd numbers, we use the **inequality operator** `!=` that is just the **reversed** version of `==`." + "`average_evens()` below works like `average_evens()` above except that it rounds the numbers in `integers` with the built-in [round()](https://docs.python.org/3/library/functions.html#round) function before filtering and averaging them. [round()](https://docs.python.org/3/library/functions.html#round) returns `int` objects independent of its argument being an `int` or a `float` object. On the first line in its body, `average_evens()` introduces a *local* variable `numbers` whose name collides with the one defined in the global scope." ] }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 48, "metadata": { "slideshow": { "slide_type": "slide" @@ -798,18 +1673,19 @@ }, "outputs": [], "source": [ - "def average_odds(numbers):\n", - " \"\"\"Calculate the average of all odd numbers in a list.\n", + "def average_evens(integers):\n", + " \"\"\"Calculate the average of all even numbers in a list.\n", "\n", " Args:\n", - " numbers (list): a list of numbers; must be integer-like\n", + " integers (list of int's/float's): numbers to be averaged;\n", + " if non-whole numbers are provided, they are rounded\n", "\n", " Returns:\n", " float: average\n", " \"\"\"\n", - " nums = [int(n) for n in numbers] # cast all numbers as integers first\n", - " odds = [n for n in nums if n % 2 != 0] # before filtering for odd numbers\n", - " average = sum(odds) / len(odds)\n", + " numbers = [round(n) for n in integers]\n", + " evens = [n for n in numbers if n % 2 == 0]\n", + " average = sum(evens) / len(evens)\n", " return average" ] }, @@ -821,12 +1697,12 @@ } }, "source": [ - "`nums` in the global scope is still unchanged." + "As a good practice, let's first \"verify\" that `average_evens()` is \"correct\" by calling it with inputs for which we can calculate the answer in our heads. Treating a function as a \"black box\" (i.e., input-output specification) when testing is also called [unit testing](https://en.wikipedia.org/wiki/Unit_testing) and plays an important role in modern software engineering." ] }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 49, "metadata": { "slideshow": { "slide_type": "slide" @@ -836,16 +1712,16 @@ { "data": { "text/plain": [ - "[7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]" + "42.0" ] }, - "execution_count": 22, + "execution_count": 49, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "nums" + "average_evens([40.0, 41.1, 42.2, 43.3, 44.4]) # inputs with predictable result" ] }, { @@ -856,31 +1732,56 @@ } }, "source": [ - "As good practice, let's first use inputs for which we can calculate the answer in our heads to \"verify\" that `average_odds()` is \"correct.\"" + "Such tests are often and conveniently expressed with the `assert` statement (cf., [reference](https://docs.python.org/3/reference/simple_stmts.html#the-assert-statement)): If the expression following `assert` evaluates to `True`, nothing happens." ] }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 50, "metadata": { "slideshow": { - "slide_type": "-" + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "assert average_evens([40.0, 41.1, 42.2, 43.3, 44.4]) == 42.0" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "However, if the expression evaluates to `False`, an `AssertionError` is raised." + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "metadata": { + "slideshow": { + "slide_type": "skip" } }, "outputs": [ { - "data": { - "text/plain": [ - "3.0" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" + "ename": "AssertionError", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32massert\u001b[0m \u001b[0maverage_evens\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m40.0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m41.1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m42.2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m43.3\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m44.4\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m87.0\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mAssertionError\u001b[0m: " + ] } ], "source": [ - "average_odds([1.0, 10.0, 3.0, 10.0, 5.0]) # verify correctness with predictable inputs" + "assert average_evens([40.0, 41.1, 42.2, 43.3, 44.4]) == 87.0" ] }, { @@ -891,47 +1792,12 @@ } }, "source": [ - "To add to the confusion, let's also pass the global `nums` as an argument to `average_odds()`." + "Calling `average_evens()` leaves `numbers` in the global scope unchanged." ] }, { "cell_type": "code", - "execution_count": 24, - "metadata": { - "slideshow": { - "slide_type": "-" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "6.0" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "average_odds(nums)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, - "source": [ - "Python, however, is again smart enough to keep all the involved `nums` variables apart. So, the global `nums` is still referencing the *same* `list` object as before." - ] - }, - { - "cell_type": "code", - "execution_count": 25, + "execution_count": 52, "metadata": { "slideshow": { "slide_type": "-" @@ -944,13 +1810,13 @@ "[7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]" ] }, - "execution_count": 25, + "execution_count": 52, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "nums" + "numbers" ] }, { @@ -961,101 +1827,114 @@ } }, "source": [ - "The reason why everything works is that *every time* we (re-)assign an object to a variable *inside* a function with the `=` statement, this is done in the *local* scope by default. There are ways to change variables existing in an outer scope from within a function, but this is a rather advanced topic on its own.\n", + "To add to the confusion, let's also pass the global `numbers` list as an argument to `average_evens()`. The return value is the same as before." + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "7.0" + ] + }, + "execution_count": 53, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "average_evens(numbers)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "In summary, Python is smart enough to keep all the involved `numbers` variables apart. So, the global `numbers` variable is still referencing the *same* `list` object as before." + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]" + ] + }, + "execution_count": 54, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "numbers" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The reason why everything works is that *every* time we (re-)assign an object to a variable *inside* a function's body with the `=` statement, this is done in the *local* scope by default. There are ways to change variables existing in an outer scope from within a function, but this is a rather advanced topic.\n", "\n", - "[PythonTutor](http://pythontutor.com/visualize.html#code=nums%20%3D%20%5B1,%202,%203,%204,%205,%206,%207,%208,%209,%2010,%2011,%2012%5D%0A%0Adef%20average_odds%28numbers%29%3A%0A%20%20%20%20nums%20%3D%20%5Bint%28n%29%20for%20n%20in%20numbers%5D%0A%20%20%20%20odds%20%3D%20%5Bn%20for%20n%20in%20nums%20if%20n%20%25%202%20!%3D%200%5D%0A%20%20%20%20average%20%3D%20sum%28odds%29%20/%20len%28odds%29%0A%20%20%20%20return%20average%0A%0Arv%20%3D%20average_odds%28%5B1.0,%2010.0,%203.0,%2010.0,%205.0%5D%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) shows how *two* `nums` variables exist in *different* scopes referencing *different* objects (cf., steps 14-25) when we execute `average_odds([1.0, 10.0, 3.0, 10.0, 5.0])`.\n", + "[PythonTutor](http://pythontutor.com/visualize.html#code=numbers%20%3D%20%5B7,%2011,%208,%205,%203,%2012,%202,%206,%209,%2010,%201,%204%5D%0A%0Adef%20average_evens%28integers%29%3A%0A%20%20%20%20numbers%20%3D%20%5Bround%28n%29%20for%20n%20in%20integers%5D%0A%20%20%20%20evens%20%3D%20%5Bn%20for%20n%20in%20numbers%20if%20n%20%25%202%20%3D%3D%200%5D%0A%20%20%20%20average%20%3D%20sum%28evens%29%20/%20len%28evens%29%0A%20%20%20%20return%20average%0A%0Aresult%20%3D%20average_evens%28%5B40.0,%2041.1,%2042.2,%2043.3,%2044.4%5D%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) shows how *two* `numbers` variables exist in *different* scopes referencing *different* objects (cf., steps 14-25) when we execute `average_evens([40.0, 41.1, 42.2, 43.3, 44.4])`.\n", "\n", "Variables whose names collide with the ones of variables in enclosing scopes - and the global scope is just the most enclosing scope - are said to **shadow** them.\n", "\n", - "While this is not a problem for Python, it may lead to less readable code for us humans and should be avoided if possible. But, as we have also heard, \"[naming things](https://skeptics.stackexchange.com/questions/19836/has-phil-karlton-ever-said-there-are-only-two-hard-things-in-computer-science)\" is often considered hard as well, and we have to be prepared to encounter shadowing variables." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "## Built-in Functions" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, - "source": [ - "Python comes with plenty of useful functions built in, some of which we have already seen before. The [documentation](https://docs.python.org/3/library/functions.html) has the full list. Just as core Python itself, they are implemented in C and thus very fast.\n", + "While this is not a problem for Python, it may lead to less readable code for humans and should be avoided if possible. But, as the software engineering wisdom goes, \"[naming things](https://skeptics.stackexchange.com/questions/19836/has-phil-karlton-ever-said-there-are-only-two-hard-things-in-computer-science)\" is often considered a hard problem as well, and we have to be prepared to encounter shadowing variables.\n", "\n", - "[len()](https://docs.python.org/3/library/functions.html#len) counts the number of elements in a container object while [sum()](https://docs.python.org/3/library/functions.html#sum) adds up all the elements." + "Shadowing also occurs if a parameter in the function definition goes by the same name as a variable in an outer scope. Below, `average_evens()` is identical to the first version in this chapter except that the parameter `integers` is now called `numbers` as well." ] }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 55, "metadata": { "slideshow": { "slide_type": "slide" } }, - "outputs": [ - { - "data": { - "text/plain": [ - "12" - ] - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "len(nums)" + "def average_evens(numbers):\n", + " \"\"\"Calculate the average of all even numbers in a list.\n", + "\n", + " Args:\n", + " numbers (list of int's): whole numbers to be averaged\n", + "\n", + " Returns:\n", + " float: average\n", + " \"\"\"\n", + " evens = [n for n in numbers if n % 2 == 0]\n", + " average = sum(evens) / len(evens)\n", + " return average" ] }, { "cell_type": "code", - "execution_count": 27, - "metadata": { - "slideshow": { - "slide_type": "-" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "78" - ] - }, - "execution_count": 27, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "sum(nums)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, - "source": [ - "We may cast objects as a different type. For example, to \"convert\" a float or a text into an integer, we use the [int()](https://docs.python.org/3/library/functions.html#int) built-in. It creates a *new* object of type `int` from the provided `avg` or `\"7\"` objects that continue to exist in memory unchanged." - ] - }, - { - "cell_type": "code", - "execution_count": 28, + "execution_count": 56, "metadata": { "slideshow": { "slide_type": "slide" @@ -1068,18 +1947,18 @@ "7.0" ] }, - "execution_count": 28, + "execution_count": 56, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "avg" + "average_evens(numbers)" ] }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 57, "metadata": { "slideshow": { "slide_type": "-" @@ -1089,40 +1968,16 @@ { "data": { "text/plain": [ - "7" + "[7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]" ] }, - "execution_count": 29, + "execution_count": 57, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "int(avg)" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "metadata": { - "slideshow": { - "slide_type": "fragment" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "7" - ] - }, - "execution_count": 30, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "int(\"7\")" + "numbers" ] }, { @@ -1133,126 +1988,7 @@ } }, "source": [ - "Casting as an `int` object is different from rounding with the built-in [round()](https://docs.python.org/3/library/functions.html#round) function!" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "7" - ] - }, - "execution_count": 31, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "int(7.99)" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "metadata": { - "slideshow": { - "slide_type": "-" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "8" - ] - }, - "execution_count": 32, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "round(7.99)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, - "source": [ - "Not all conversions are valid and *runtime* errors may occur as the `ValueError` shows." - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "outputs": [ - { - "ename": "ValueError", - "evalue": "invalid literal for int() with base 10: 'seven'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"seven\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;31mValueError\u001b[0m: invalid literal for int() with base 10: 'seven'" - ] - } - ], - "source": [ - "int(\"seven\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, - "source": [ - "We may also go in the other direction with the [float()](https://docs.python.org/3/library/functions.html#float) built-in." - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "42.0" - ] - }, - "execution_count": 34, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "float(42)" + "[PythonTutor](http://pythontutor.com/visualize.html#code=numbers%20%3D%20%5B7,%2011,%208,%205,%203,%2012,%202,%206,%209,%2010,%201,%204%5D%0A%0Adef%20average_evens%28numbers%29%3A%0A%20%20%20%20evens%20%3D%20%5Bn%20for%20n%20in%20numbers%20if%20n%20%25%202%20%3D%3D%200%5D%0A%20%20%20%20average%20%3D%20sum%28evens%29%20/%20len%28evens%29%0A%20%20%20%20return%20average%0A%0Aresult%20%3D%20average_evens%28numbers%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) reveals that in this example there are *two* `numbers` variables in *different* scope referencing the *same* `list` object in memory (cf., steps 4-23)." ] }, { @@ -1274,12 +2010,12 @@ } }, "source": [ - "So far, we have only specified one parameter in each of our user-defined functions. In [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements_00_lecture.ipynb#%28Arithmetic%29-Operators), however, we saw the built-in function [divmod()](https://docs.python.org/3/library/functions.html#divmod) take two arguments. And, the order of the numbers passed in mattered! Whenever we call a function and list its arguments in a comma separated manner, we say that we pass in the arguments by position or refer to them as **positional arguments**." + "So far, we have specified only one parameter in each of our user-defined functions. In [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements_00_lecture.ipynb#%28Arithmetic%29-Operators), however, we saw the built-in [divmod()](https://docs.python.org/3/library/functions.html#divmod) function take two arguments. And, the order in which they are passed in matters! Whenever we call a function and list its arguments in a comma separated manner, we say that we pass in the arguments *by position* or refer to them as **positional arguments**." ] }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 58, "metadata": { "slideshow": { "slide_type": "slide" @@ -1292,7 +2028,7 @@ "(4, 2)" ] }, - "execution_count": 35, + "execution_count": 58, "metadata": {}, "output_type": "execute_result" } @@ -1303,7 +2039,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 59, "metadata": { "slideshow": { "slide_type": "-" @@ -1316,7 +2052,7 @@ "(0, 10)" ] }, - "execution_count": 36, + "execution_count": 59, "metadata": {}, "output_type": "execute_result" } @@ -1333,12 +2069,12 @@ } }, "source": [ - "For many functions, there is a natural order to the arguments. But what if this is not the case? For example, let's create a close relative of the above `average_evens()` function that also scales the resulting average by a factor. What is more natural? Passing in `numbers` first? Or `scalar`? There is no obvious way and we continue with the first alternative for no concrete reason." + "For many functions, there is a natural order to the arguments: For example, for any kind of division passing the dividend first and the divisor second seems intuitive. But what if that is not the case in another setting? For example, let's create a close relative of the above `average_evens()` function that also scales the resulting average by a factor. What is more natural? Passing in `numbers` first? Or `scalar`? There is no obvious way and we continue with the first alternative for no concrete reason." ] }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 60, "metadata": { "slideshow": { "slide_type": "slide" @@ -1347,16 +2083,17 @@ "outputs": [], "source": [ "def scaled_average_evens(numbers, scalar):\n", - " \"\"\"Calculate a scaled average of all even numbers in a list.\n", + " \"\"\"Calculate the scaled average of all even numbers in a list.\n", "\n", " Args:\n", - " numbers (list): a list of numbers; may be integers or floats\n", - " scalar (float): the scalar that multiplies the average\n", - " of the even numbers\n", + " numbers (list of int's/float's): numbers to be averaged;\n", + " if non-whole numbers are provided, they are rounded\n", + " scalar (float): multiplies the average\n", "\n", " Returns:\n", " float: scaled average\n", " \"\"\"\n", + " numbers = [round(n) for n in numbers]\n", " evens = [n for n in numbers if n % 2 == 0]\n", " average = sum(evens) / len(evens)\n", " return scalar * average" @@ -1370,12 +2107,12 @@ } }, "source": [ - "As with [divmod()](https://docs.python.org/3/library/functions.html#divmod), we pass in the arguments by position." + "As with [divmod()](https://docs.python.org/3/library/functions.html#divmod), we may pass in the arguments by position." ] }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 61, "metadata": { "slideshow": { "slide_type": "slide" @@ -1388,13 +2125,13 @@ "14.0" ] }, - "execution_count": 38, + "execution_count": 61, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "scaled_average_evens(nums, 2)" + "scaled_average_evens(numbers, 2)" ] }, { @@ -1405,14 +2142,14 @@ } }, "source": [ - "However, now the function call is a bit harder to comprehend as we always need to remember what the `2` means. This becomes harder the more parameters we specify.\n", + "Now, this function call is a bit harder to understand as we always need to remember what the `2` means. This becomes even harder with more parameters.\n", "\n", - "Luckily, we may also reference the formal parameter names as **keyword arguments**. We can even combine positional and keyword arguments in the same function call. Each of the following does the *same*." + "Luckily, we may also pass in arguments *by name*. Then, we refer to them as **keyword arguments**." ] }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 62, "metadata": { "slideshow": { "slide_type": "fragment" @@ -1425,18 +2162,29 @@ "14.0" ] }, - "execution_count": 39, + "execution_count": 62, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "scaled_average_evens(nums, scalar=2)" + "scaled_average_evens(numbers=numbers, scalar=2)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "When passing all arguments by name, we may do so in any order." ] }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 63, "metadata": { "slideshow": { "slide_type": "fragment" @@ -1449,18 +2197,29 @@ "14.0" ] }, - "execution_count": 40, + "execution_count": 63, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "scaled_average_evens(numbers=nums, scalar=2)" + "scaled_average_evens(scalar=2, numbers=numbers)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "We may even combine positional and keyword arguments in the same function call." ] }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 64, "metadata": { "slideshow": { "slide_type": "fragment" @@ -1473,13 +2232,13 @@ "14.0" ] }, - "execution_count": 41, + "execution_count": 64, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "scaled_average_evens(scalar=2, numbers=nums)" + "scaled_average_evens(numbers, scalar=2)" ] }, { @@ -1495,35 +2254,24 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 65, "metadata": { "slideshow": { - "slide_type": "slide" + "slide_type": "skip" } }, "outputs": [ { "ename": "SyntaxError", - "evalue": "positional argument follows keyword argument (, line 1)", + "evalue": "positional argument follows keyword argument (, line 1)", "output_type": "error", "traceback": [ - "\u001b[0;36m File \u001b[0;32m\"\"\u001b[0;36m, line \u001b[0;32m1\u001b[0m\n\u001b[0;31m scaled_average_evens(numbers=nums, 2)\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m positional argument follows keyword argument\n" + "\u001b[0;36m File \u001b[0;32m\"\"\u001b[0;36m, line \u001b[0;32m1\u001b[0m\n\u001b[0;31m scaled_average_evens(numbers=numbers, 2)\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m positional argument follows keyword argument\n" ] } ], "source": [ - "scaled_average_evens(numbers=nums, 2)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "### Default Argument Values" + "scaled_average_evens(numbers=numbers, 2)" ] }, { @@ -1534,16 +2282,86 @@ } }, "source": [ - "Defining both `average_evens()` and `scaled_average_evens()` is also kind of repetitive as most of their code is the same. Such a redundancy makes a codebase harder to maintain in the long run as whenever we change the logic in one function, we must *not* forget to do so for the other function as well.\n", - "\n", - "A better way is to design related functions in a **modular** fashion such that they reuse each other's logic.\n", - "\n", - "For example, as not scaling an average is just a special case of scaling it with `1`, we could redefine the two functions like below: In this setting, the function resembling the *special* case (i.e., `average_evens()`) **forwards** the call to the more *general* function (i.e., `scaled_average_evens()`) using a `scalar=1` argument." + "Similarly, we must always pass in the right number of arguments. Otherwise, a `TypeError` is raised." ] }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 66, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "ename": "TypeError", + "evalue": "scaled_average_evens() missing 1 required positional argument: 'scalar'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mscaled_average_evens\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnumbers\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m: scaled_average_evens() missing 1 required positional argument: 'scalar'" + ] + } + ], + "source": [ + "scaled_average_evens(numbers)" + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "ename": "TypeError", + "evalue": "scaled_average_evens() takes 2 positional arguments but 3 were given", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mscaled_average_evens\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnumbers\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m3\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m: scaled_average_evens() takes 2 positional arguments but 3 were given" + ] + } + ], + "source": [ + "scaled_average_evens(numbers, 2, 3)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### Default Arguments" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Defining `average_evens()` and `scaled_average_evens()` as above leads to a repetition of most of their code. That is *not* good as such a redundancy makes a code base hard to maintain in the long run: Whenever we change the logic in one function, we must *not* forget to do so for the other function as well. And, most likely, we forget about such issues in larger projects.\n", + "\n", + "Below, three of four lines in the functions' bodies are identical!" + ] + }, + { + "cell_type": "code", + "execution_count": 68, "metadata": { "slideshow": { "slide_type": "slide" @@ -1551,42 +2369,56 @@ }, "outputs": [], "source": [ + "def average_evens(numbers):\n", + " \"\"\" ... ... ... \"\"\"\n", + " numbers = [round(n) for n in numbers]\n", + " evens = [n for n in numbers if n % 2 == 0]\n", + " average = sum(evens) / len(evens)\n", + " return average\n", + "\n", + "\n", "def scaled_average_evens(numbers, scalar):\n", - " \"\"\"Calculate a scaled average of all even numbers in a list.\n", - "\n", - " Args:\n", - " numbers (list): a list of numbers; may be integers or floats\n", - " scalar (float): the scalar that multiplies the average\n", - " of the even numbers\n", - "\n", - " Returns:\n", - " float: scaled average\n", - " \"\"\"\n", + " \"\"\" ... ... ... \"\"\"\n", + " numbers = [round(n) for n in numbers]\n", " evens = [n for n in numbers if n % 2 == 0]\n", " average = sum(evens) / len(evens)\n", " return scalar * average" ] }, { - "cell_type": "code", - "execution_count": 44, + "cell_type": "markdown", "metadata": { "slideshow": { - "slide_type": "-" + "slide_type": "skip" + } + }, + "source": [ + "A better way is to design related functions in a **modular** fashion such that they reuse each other's code.\n", + "\n", + "For example, as not scaling an average is just a special case of scaling it with `1`, we could redefine the two functions like below: In this version, the function resembling the *special* case, `average_evens()`, **forwards** the call to the more *general* function, `scaled_average_evens()`, passing a `scalar` argument of `1`. As the name `scaled_average_evens` within the body of `average_evens()` is looked up each time the function is *being* executed, we may define `average_evens()` before `scaled_average_evens()`." + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "metadata": { + "slideshow": { + "slide_type": "slide" } }, "outputs": [], "source": [ "def average_evens(numbers):\n", - " \"\"\"Calculate the average of all even numbers in a list.\n", + " \"\"\" ... ... ... \"\"\"\n", + " return scaled_average_evens(numbers, scalar=1)\n", "\n", - " Args:\n", - " numbers (list): a list of numbers; may be integers or floats\n", "\n", - " Returns:\n", - " float: average\n", - " \"\"\"\n", - " return scaled_average_evens(numbers, scalar=1) # refactored to use the logic in scaled_average_evens()" + "def scaled_average_evens(numbers, scalar):\n", + " \"\"\" ... ... ... \"\"\"\n", + " numbers = [round(n) for n in numbers]\n", + " evens = [n for n in numbers if n % 2 == 0]\n", + " average = sum(evens) / len(evens)\n", + " return scalar * average" ] }, { @@ -1597,31 +2429,33 @@ } }, "source": [ - "The outcome of `average_evens(nums)` is, of course, still `7.0`." + "After **refactoring** the functions, it is a good idea to test them again." ] }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 70, "metadata": { "slideshow": { - "slide_type": "-" + "slide_type": "skip" } }, - "outputs": [ - { - "data": { - "text/plain": [ - "7.0" - ] - }, - "execution_count": 45, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "average_evens(nums)" + "assert average_evens(numbers) == 7.0" + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "assert scaled_average_evens(numbers, 2) == 14.0" ] }, { @@ -1632,12 +2466,12 @@ } }, "source": [ - "If we *assume* that scaling the average occurs rarely, we could also handle both cases in *one* function definition by providing a **default value** for the `scalar` parameter." + "*Assuming* that scaling the average occurs rarely, it may be a good idea to handle both cases in *one* function definition by providing a **default argument** of `1` for the `scalar` parameter." ] }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 72, "metadata": { "slideshow": { "slide_type": "slide" @@ -1649,13 +2483,14 @@ " \"\"\"Calculate the average of all even numbers in a list.\n", "\n", " Args:\n", - " numbers (list): list of numbers; may be integers or floats\n", - " scalar (float, optional): the scalar that multiplies the\n", - " average of the even numbers\n", + " numbers (list of int's/float's): numbers to be averaged;\n", + " if non-whole numbers are provided, they are rounded\n", + " scalar (float, optional): multiplies the average; defaults to 1\n", "\n", " Returns:\n", " float: (scaled) average\n", " \"\"\"\n", + " numbers = [round(n) for n in numbers]\n", " evens = [n for n in numbers if n % 2 == 0]\n", " average = sum(evens) / len(evens)\n", " return scalar * average" @@ -1669,14 +2504,16 @@ } }, "source": [ - "Now, we call the function either with or without the `scalar` argument.\n", + "Now, we call the function with or without passing a `scalar` argument.\n", "\n", - "If `scalar` is to be passed in, this may be done as either a positional or a keyword argument. Which of the two versions where `scalar` is `2` is more \"natural\" and faster to comprehend in a larger program?" + "If `scalar` is *not* passed in, it automatically takes the value `1`.\n", + "\n", + "If `scalar` is passed in, this may be done as either a positional or a keyword argument. Which of the two calls where `scalar` is `2` is faster to understand in a larger program?" ] }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 73, "metadata": { "slideshow": { "slide_type": "slide" @@ -1689,18 +2526,18 @@ "7.0" ] }, - "execution_count": 47, + "execution_count": 73, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "average_evens(nums)" + "average_evens(numbers)" ] }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 74, "metadata": { "slideshow": { "slide_type": "-" @@ -1713,18 +2550,18 @@ "14.0" ] }, - "execution_count": 48, + "execution_count": 74, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "average_evens(nums, 2)" + "average_evens(numbers, 2)" ] }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 75, "metadata": { "slideshow": { "slide_type": "-" @@ -1737,13 +2574,13 @@ "14.0" ] }, - "execution_count": 49, + "execution_count": 75, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "average_evens(nums, scalar=2)" + "average_evens(numbers, scalar=2)" ] }, { @@ -1765,14 +2602,14 @@ } }, "source": [ - "Since we *assumed* that scaling occurs *rarely*, we'd prefer that our new version of `average_evens()` be called with a *keyword argument* whenever `scalar` is to be passed in explicitly. Then, the second argument is never ambiguous as we could always read its name.\n", + "Because we *assumed* that scaling occurs rarely, we would prefer that our new version of `average_evens()` be called with a *keyword argument* whenever `scalar` is passed in. Then, a function call is never ambiguous when reading the source code.\n", "\n", - "Luckily, Python offers a **keyword-only** syntax, where all we need to do is to place the arguments for which we require *explicit* keyword use after an asterisk `*`." + "Python offers a **keyword-only** syntax when defining a function that *forces* a caller to pass the `scalar` argument *by name* if it is passed in at all: To do so, we place an asterisk `*` before the arguments that may only be passed in by name. Note that the keyword-only syntax also works *without* a default argument." ] }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 76, "metadata": { "slideshow": { "slide_type": "slide" @@ -1784,13 +2621,14 @@ " \"\"\"Calculate the average of all even numbers in a list.\n", "\n", " Args:\n", - " numbers (list): list of numbers; may be integers or floats\n", - " scalar (float, optional): the scalar that multiplies the\n", - " average of the even numbers\n", + " numbers (list of int's/float's): numbers to be averaged;\n", + " if non-whole numbers are provided, they are rounded\n", + " scalar (float, optional): multiplies the average; defaults to 1\n", "\n", " Returns:\n", " float: (scaled) average\n", " \"\"\"\n", + " numbers = [round(n) for n in numbers]\n", " evens = [n for n in numbers if n % 2 == 0]\n", " average = sum(evens) / len(evens)\n", " return scalar * average" @@ -1804,12 +2642,12 @@ } }, "source": [ - "If we now call the function with a `scalar` argument passed in, we *must* use keyword notation." + "As before, we may call `average_evens()` without passing in an argument for the `scalar` parameter." ] }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 77, "metadata": { "slideshow": { "slide_type": "slide" @@ -1822,18 +2660,29 @@ "7.0" ] }, - "execution_count": 51, + "execution_count": 77, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "average_evens(nums)" + "average_evens(numbers)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "If we call `average_evens()` with a `scalar` argument, we *must* use keyword notation." ] }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 78, "metadata": { "slideshow": { "slide_type": "-" @@ -1846,13 +2695,13 @@ "14.0" ] }, - "execution_count": 52, + "execution_count": 78, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "average_evens(nums, scalar=2)" + "average_evens(numbers, scalar=2)" ] }, { @@ -1863,12 +2712,12 @@ } }, "source": [ - "If we pass in `scalar` as a positional argument instead, we get a `TypeError`." + "If instead we pass in `scalar` as a positional argument, we get a `TypeError`." ] }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 79, "metadata": { "slideshow": { "slide_type": "fragment" @@ -1882,13 +2731,13 @@ "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0maverage_evens\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnums\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0maverage_evens\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnumbers\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mTypeError\u001b[0m: average_evens() takes 1 positional argument but 2 were given" ] } ], "source": [ - "average_evens(nums, 2)" + "average_evens(numbers, 2)" ] }, { @@ -1910,22 +2759,20 @@ } }, "source": [ - "The `def` statement is a statement because of its side effect of creating a *new* name that references a *new* `function` object in memory.\n", + "The `def` statement is a statement because of its *side effect* of creating a *new* name that references a *new* `function` object in memory.\n", "\n", - "We can thus think of it as doing *two* things at once (i.e., either both of them happen or none). First, a `function` object is created that contains the concrete $0$s and $1$s that resemble the instructions we put into the function's body. In the context of a function, these $0$s and $1$s are also called **[byte code](https://en.wikipedia.org/wiki/Bytecode)**. Then, a name referencing the new `function` object is created.\n", + "We can thus think of it as doing *two* things **atomically** (i.e., either both of them happen or none). First, a `function` object is created that contains the concrete $0$s and $1$s that resemble the instructions we put into the function's body. In the context of a function, these $0$s and $1$s are also called **[byte code](https://en.wikipedia.org/wiki/Bytecode)**. Then, a name referencing the new `function` object is created.\n", "\n", "Only this second aspect makes `def` a statement: Merely creating a new object in memory without making it accessible for later reference does *not* constitute a side effect because the state the program is *not* changed. After all, if we cannot reference an object, how do we know it exists in the first place?\n", "\n", - "Python provides a so-called **[lambda expression](https://docs.python.org/3/reference/expressions.html#lambda)** syntax that allows us to *only* create a `function` object in memory *without* making a name reference it.\n", + "Python provides a `lambda` expression syntax that allows us to *only* create a `function` object in memory *without* making a name reference it (cf., [reference](https://docs.python.org/3/reference/expressions.html#lambda)). It starts with the keyword `lambda` followed by an optional listing of comma separated parameters, a mandatory colon, and *one* expression that serves as the return value of the resulting `function` object. Because it does *not* create a name referencing the object, we effectively create \"anonymous\" functions with it.\n", "\n", - "It starts with the keyword `lambda` followed by an optional comma separated enumeration of parameters, a mandatory colon, and *one* expression that also is the resulting `function` object's return value.\n", - "\n", - "Because it does not create a name referencing the object, we effectively create \"anonymous\" functions with it. In the example, we create a `function` object that adds `3` to the only argument passed in as the parameter `x` and returns that sum." + "In the example, we create a `function` object that adds `3` to the only argument passed in as the parameter `x` and returns that sum." ] }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 80, "metadata": { "slideshow": { "slide_type": "slide" @@ -1938,7 +2785,7 @@ "(x)>" ] }, - "execution_count": 54, + "execution_count": 80, "metadata": {}, "output_type": "execute_result" } @@ -1959,12 +2806,12 @@ "\n", "We created a `function` object, dit *not* call it, and Python immediately forgot about it. So what's the point?\n", "\n", - "To show that a `lambda` expression creates a callable `function` object, we use the simple `=` statement to assign it to the variable `add_three`, which is really `add_three()` as per our convention from above." + "To inspect the object created by a `lambda` expression, we use the simple `=` statement and assign it to the variable `add_three`, which is really `add_three()` as per our convention from above." ] }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 81, "metadata": { "slideshow": { "slide_type": "fragment" @@ -1983,12 +2830,36 @@ } }, "source": [ - "Now we call `add_three()` as if we defined it with the `def` statement in the first place." + "[type()](https://docs.python.org/3/library/functions.html#type) and [callable()](https://docs.python.org/3/library/functions.html#callable) confirm that `add_three` is indeed a callable `function` object." ] }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 82, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "function" + ] + }, + "execution_count": 82, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(add_three)" + ] + }, + { + "cell_type": "code", + "execution_count": 83, "metadata": { "slideshow": { "slide_type": "-" @@ -1998,16 +2869,16 @@ { "data": { "text/plain": [ - "13" + "True" ] }, - "execution_count": 56, + "execution_count": 83, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "add_three(10)" + "callable(add_three)" ] }, { @@ -2018,12 +2889,12 @@ } }, "source": [ - "Alternatively, we could call an anonymous `function` object created with a `lambda` expression right away (i.e., without assigning it to a variable), which looks quite weird for now as we need *two* pairs of parentheses: The first is just a delimiter whereas the second the call operator." + "Now we may call `add_three()` as if we defined it with the `def` statement." ] }, { "cell_type": "code", - "execution_count": 57, + "execution_count": 84, "metadata": { "slideshow": { "slide_type": "fragment" @@ -2036,7 +2907,42 @@ "42" ] }, - "execution_count": 57, + "execution_count": 84, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "add_three(39)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Alternatively, we could call an `function` object created with a `lambda` expression right away (i.e., without assigning it to a variable), which looks quite weird for now as we need *two* pairs of parentheses: The first one serves as a delimiter whereas the second represents the call operator." + ] + }, + { + "cell_type": "code", + "execution_count": 85, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "42" + ] + }, + "execution_count": 85, "metadata": {}, "output_type": "execute_result" } @@ -2053,9 +2959,9 @@ } }, "source": [ - "The main point of having functions without a name is to use them in a situation where we know ahead of time that we use the function only *once*.\n", + "The main point of having functions without a reference to them is to use them in a situation where we know ahead of time that we use the function only *once*.\n", "\n", - "Popular applications of lambda expressions occur in combination with the **map-filter-reduce** paradigm or when we do \"number crunching\" with **arrays** and **data frames**. We look at both in detail in [Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences_00_lecture.ipynb)." + "Popular applications of lambda expressions occur in combination with the **map-filter-reduce** paradigm (cf., [Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences_00_lecture.ipynb#Lambda-Expressions)) or when we do \"number crunching\" with **arrays** and **data frames** (cf., Chapter 9)." ] }, { @@ -2094,7 +3000,7 @@ } }, "source": [ - "### Standard Library" + "### The Standard Library" ] }, { @@ -2122,7 +3028,7 @@ } }, "source": [ - "#### The [math](https://docs.python.org/3/library/math.html) Module" + "#### [math](https://docs.python.org/3/library/math.html) Module" ] }, { @@ -2140,7 +3046,7 @@ }, { "cell_type": "code", - "execution_count": 58, + "execution_count": 86, "metadata": { "slideshow": { "slide_type": "slide" @@ -2164,7 +3070,7 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": 87, "metadata": { "slideshow": { "slide_type": "fragment" @@ -2177,7 +3083,7 @@ "" ] }, - "execution_count": 59, + "execution_count": 87, "metadata": {}, "output_type": "execute_result" } @@ -2188,7 +3094,7 @@ }, { "cell_type": "code", - "execution_count": 60, + "execution_count": 88, "metadata": { "slideshow": { "slide_type": "fragment" @@ -2198,10 +3104,10 @@ { "data": { "text/plain": [ - "140371899337520" + "140413079419024" ] }, - "execution_count": 60, + "execution_count": 88, "metadata": {}, "output_type": "execute_result" } @@ -2212,7 +3118,7 @@ }, { "cell_type": "code", - "execution_count": 61, + "execution_count": 89, "metadata": { "slideshow": { "slide_type": "-" @@ -2225,7 +3131,7 @@ "module" ] }, - "execution_count": 61, + "execution_count": 89, "metadata": {}, "output_type": "execute_result" } @@ -2251,7 +3157,7 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": 90, "metadata": { "slideshow": { "slide_type": "slide" @@ -2319,7 +3225,7 @@ " 'trunc']" ] }, - "execution_count": 62, + "execution_count": 90, "metadata": {}, "output_type": "execute_result" } @@ -2341,7 +3247,7 @@ }, { "cell_type": "code", - "execution_count": 63, + "execution_count": 91, "metadata": { "slideshow": { "slide_type": "slide" @@ -2354,7 +3260,7 @@ "3.141592653589793" ] }, - "execution_count": 63, + "execution_count": 91, "metadata": {}, "output_type": "execute_result" } @@ -2365,7 +3271,7 @@ }, { "cell_type": "code", - "execution_count": 64, + "execution_count": 92, "metadata": { "slideshow": { "slide_type": "-" @@ -2378,7 +3284,7 @@ "2.718281828459045" ] }, - "execution_count": 64, + "execution_count": 92, "metadata": {}, "output_type": "execute_result" } @@ -2389,7 +3295,7 @@ }, { "cell_type": "code", - "execution_count": 65, + "execution_count": 93, "metadata": { "slideshow": { "slide_type": "slide" @@ -2402,7 +3308,7 @@ "" ] }, - "execution_count": 65, + "execution_count": 93, "metadata": {}, "output_type": "execute_result" } @@ -2413,7 +3319,7 @@ }, { "cell_type": "code", - "execution_count": 66, + "execution_count": 94, "metadata": { "slideshow": { "slide_type": "-" @@ -2438,7 +3344,7 @@ }, { "cell_type": "code", - "execution_count": 67, + "execution_count": 95, "metadata": { "slideshow": { "slide_type": "-" @@ -2451,7 +3357,7 @@ "1.4142135623730951" ] }, - "execution_count": 67, + "execution_count": 95, "metadata": {}, "output_type": "execute_result" } @@ -2477,7 +3383,7 @@ }, { "cell_type": "code", - "execution_count": 68, + "execution_count": 96, "metadata": { "slideshow": { "slide_type": "-" @@ -2490,7 +3396,7 @@ "2.0" ] }, - "execution_count": 68, + "execution_count": 96, "metadata": {}, "output_type": "execute_result" } @@ -2512,7 +3418,7 @@ }, { "cell_type": "code", - "execution_count": 69, + "execution_count": 97, "metadata": { "slideshow": { "slide_type": "-" @@ -2525,7 +3431,7 @@ "10.0" ] }, - "execution_count": 69, + "execution_count": 97, "metadata": {}, "output_type": "execute_result" } @@ -2549,7 +3455,7 @@ }, { "cell_type": "code", - "execution_count": 70, + "execution_count": 98, "metadata": { "slideshow": { "slide_type": "skip" @@ -2562,7 +3468,7 @@ }, { "cell_type": "code", - "execution_count": 71, + "execution_count": 99, "metadata": { "slideshow": { "slide_type": "skip" @@ -2575,7 +3481,7 @@ "4.0" ] }, - "execution_count": 71, + "execution_count": 99, "metadata": {}, "output_type": "execute_result" } @@ -2592,7 +3498,7 @@ } }, "source": [ - "#### The [random](https://docs.python.org/3/library/random.html) Module" + "#### [random](https://docs.python.org/3/library/random.html) Module" ] }, { @@ -2608,7 +3514,7 @@ }, { "cell_type": "code", - "execution_count": 72, + "execution_count": 100, "metadata": { "slideshow": { "slide_type": "slide" @@ -2621,7 +3527,7 @@ }, { "cell_type": "code", - "execution_count": 73, + "execution_count": 101, "metadata": { "slideshow": { "slide_type": "-" @@ -2634,7 +3540,7 @@ "" ] }, - "execution_count": 73, + "execution_count": 101, "metadata": {}, "output_type": "execute_result" } @@ -2656,7 +3562,7 @@ }, { "cell_type": "code", - "execution_count": 74, + "execution_count": 102, "metadata": { "slideshow": { "slide_type": "slide" @@ -2730,7 +3636,7 @@ " 'weibullvariate']" ] }, - "execution_count": 74, + "execution_count": 102, "metadata": {}, "output_type": "execute_result" } @@ -2752,7 +3658,7 @@ }, { "cell_type": "code", - "execution_count": 75, + "execution_count": 103, "metadata": { "slideshow": { "slide_type": "slide" @@ -2765,7 +3671,7 @@ "" ] }, - "execution_count": 75, + "execution_count": 103, "metadata": {}, "output_type": "execute_result" } @@ -2776,7 +3682,7 @@ }, { "cell_type": "code", - "execution_count": 76, + "execution_count": 104, "metadata": { "slideshow": { "slide_type": "-" @@ -2801,7 +3707,7 @@ }, { "cell_type": "code", - "execution_count": 77, + "execution_count": 105, "metadata": { "slideshow": { "slide_type": "-" @@ -2811,10 +3717,10 @@ { "data": { "text/plain": [ - "0.64575874518665" + "0.8837282454584094" ] }, - "execution_count": 77, + "execution_count": 105, "metadata": {}, "output_type": "execute_result" } @@ -2831,12 +3737,12 @@ } }, "source": [ - "While we could build some conditional logic with an `if` statement to map the number generated by [random.random()](https://docs.python.org/3/library/random.html#random.random) to a finite set of elements manually, the [random.choice()](https://docs.python.org/3/library/random.html#random.choice) function provides a lot more **convenience** for us. We call it with, for example, the `nums` list and it draws one element out of it with equal chance." + "While we could build some conditional logic with an `if` statement to map the number generated by [random.random()](https://docs.python.org/3/library/random.html#random.random) to a finite set of elements manually, the [random.choice()](https://docs.python.org/3/library/random.html#random.choice) function provides a lot more **convenience** for us. We call it with, for example, the `numbers` list and it draws one element out of it with equal chance." ] }, { "cell_type": "code", - "execution_count": 78, + "execution_count": 106, "metadata": { "slideshow": { "slide_type": "slide" @@ -2846,10 +3752,10 @@ { "data": { "text/plain": [ - ">" + ">" ] }, - "execution_count": 78, + "execution_count": 106, "metadata": {}, "output_type": "execute_result" } @@ -2860,7 +3766,7 @@ }, { "cell_type": "code", - "execution_count": 79, + "execution_count": 107, "metadata": { "slideshow": { "slide_type": "-" @@ -2885,7 +3791,7 @@ }, { "cell_type": "code", - "execution_count": 80, + "execution_count": 108, "metadata": { "slideshow": { "slide_type": "-" @@ -2895,16 +3801,16 @@ { "data": { "text/plain": [ - "12" + "9" ] }, - "execution_count": 80, + "execution_count": 108, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "random.choice(nums)" + "random.choice(numbers)" ] }, { @@ -2922,7 +3828,7 @@ }, { "cell_type": "code", - "execution_count": 81, + "execution_count": 109, "metadata": { "slideshow": { "slide_type": "slide" @@ -2935,7 +3841,7 @@ }, { "cell_type": "code", - "execution_count": 82, + "execution_count": 110, "metadata": { "slideshow": { "slide_type": "fragment" @@ -2948,7 +3854,7 @@ "0.6394267984578837" ] }, - "execution_count": 82, + "execution_count": 110, "metadata": {}, "output_type": "execute_result" } @@ -2959,7 +3865,7 @@ }, { "cell_type": "code", - "execution_count": 83, + "execution_count": 111, "metadata": { "slideshow": { "slide_type": "-" @@ -2972,7 +3878,7 @@ }, { "cell_type": "code", - "execution_count": 84, + "execution_count": 112, "metadata": { "slideshow": { "slide_type": "-" @@ -2985,7 +3891,7 @@ "0.6394267984578837" ] }, - "execution_count": 84, + "execution_count": 112, "metadata": {}, "output_type": "execute_result" } @@ -3030,7 +3936,7 @@ } }, "source": [ - "#### The [numpy](http://www.numpy.org/) Library" + "#### [numpy](http://www.numpy.org/) Library" ] }, { @@ -3050,7 +3956,7 @@ }, { "cell_type": "code", - "execution_count": 85, + "execution_count": 113, "metadata": { "slideshow": { "slide_type": "slide" @@ -3082,7 +3988,7 @@ }, { "cell_type": "code", - "execution_count": 86, + "execution_count": 114, "metadata": { "slideshow": { "slide_type": "slide" @@ -3106,7 +4012,7 @@ }, { "cell_type": "code", - "execution_count": 87, + "execution_count": 115, "metadata": { "slideshow": { "slide_type": "fragment" @@ -3119,7 +4025,7 @@ "" ] }, - "execution_count": 87, + "execution_count": 115, "metadata": {}, "output_type": "execute_result" } @@ -3136,12 +4042,12 @@ } }, "source": [ - "Let's convert the above `nums` list into a vector-like object of type `numpy.ndarray`." + "Let's convert the above `numbers` list into a vector-like object of type `numpy.ndarray`." ] }, { "cell_type": "code", - "execution_count": 88, + "execution_count": 116, "metadata": { "slideshow": { "slide_type": "slide" @@ -3149,12 +4055,12 @@ }, "outputs": [], "source": [ - "vec = np.array(nums)" + "vec = np.array(numbers)" ] }, { "cell_type": "code", - "execution_count": 89, + "execution_count": 117, "metadata": { "slideshow": { "slide_type": "fragment" @@ -3167,7 +4073,7 @@ "array([ 7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4])" ] }, - "execution_count": 89, + "execution_count": 117, "metadata": {}, "output_type": "execute_result" } @@ -3178,7 +4084,7 @@ }, { "cell_type": "code", - "execution_count": 90, + "execution_count": 118, "metadata": { "slideshow": { "slide_type": "-" @@ -3191,7 +4097,7 @@ "numpy.ndarray" ] }, - "execution_count": 90, + "execution_count": 118, "metadata": {}, "output_type": "execute_result" } @@ -3215,7 +4121,7 @@ }, { "cell_type": "code", - "execution_count": 91, + "execution_count": 119, "metadata": { "slideshow": { "slide_type": "slide" @@ -3228,7 +4134,7 @@ "array([14, 22, 16, 10, 6, 24, 4, 12, 18, 20, 2, 8])" ] }, - "execution_count": 91, + "execution_count": 119, "metadata": {}, "output_type": "execute_result" } @@ -3245,12 +4151,12 @@ } }, "source": [ - "This scalar multiplication would \"fail\" if we used a plain `list` object like `nums` instead of an `numpy.ndarray` object like `vec`. The two types exhibit different **behavior** when used with the same operator, another example of **operator overloading**." + "This scalar multiplication would \"fail\" if we used a plain `list` object like `numbers` instead of an `numpy.ndarray` object like `vec`. The two types exhibit different **behavior** when used with the same operator, another example of **operator overloading**." ] }, { "cell_type": "code", - "execution_count": 92, + "execution_count": 120, "metadata": { "slideshow": { "slide_type": "fragment" @@ -3263,13 +4169,13 @@ "[7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4, 7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]" ] }, - "execution_count": 92, + "execution_count": 120, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "2 * nums # surprise, surprise" + "2 * numbers # surprise, surprise" ] }, { @@ -3285,7 +4191,7 @@ }, { "cell_type": "code", - "execution_count": 93, + "execution_count": 121, "metadata": { "slideshow": { "slide_type": "fragment" @@ -3298,7 +4204,7 @@ "78" ] }, - "execution_count": 93, + "execution_count": 121, "metadata": {}, "output_type": "execute_result" } @@ -3309,7 +4215,7 @@ }, { "cell_type": "code", - "execution_count": 94, + "execution_count": 122, "metadata": { "slideshow": { "slide_type": "-" @@ -3322,7 +4228,7 @@ "7" ] }, - "execution_count": 94, + "execution_count": 122, "metadata": {}, "output_type": "execute_result" } @@ -3361,7 +4267,7 @@ }, { "cell_type": "code", - "execution_count": 95, + "execution_count": 123, "metadata": { "slideshow": { "slide_type": "slide" @@ -3374,7 +4280,7 @@ }, { "cell_type": "code", - "execution_count": 96, + "execution_count": 124, "metadata": { "slideshow": { "slide_type": "fragment" @@ -3387,7 +4293,7 @@ "" ] }, - "execution_count": 96, + "execution_count": 124, "metadata": {}, "output_type": "execute_result" } @@ -3413,7 +4319,7 @@ }, { "cell_type": "code", - "execution_count": 97, + "execution_count": 125, "metadata": { "slideshow": { "slide_type": "fragment" @@ -3431,14 +4337,14 @@ " '__name__',\n", " '__package__',\n", " '__spec__',\n", - " '_default_scalar',\n", + " '_round_all',\n", " '_scaled_average',\n", " 'average',\n", " 'average_evens',\n", " 'average_odds']" ] }, - "execution_count": 97, + "execution_count": 125, "metadata": {}, "output_type": "execute_result" } @@ -3460,7 +4366,7 @@ }, { "cell_type": "code", - "execution_count": 98, + "execution_count": 126, "metadata": { "slideshow": { "slide_type": "slide" @@ -3477,9 +4383,9 @@ " Calculate the average of all even numbers in a list.\n", " \n", " Args:\n", - " numbers (list): list of numbers; may be integers or floats\n", - " scalar (float, optional): the scalar that multiplies the\n", - " average of the even numbers\n", + " numbers (list of int's/float's): numbers to be averaged;\n", + " if non-whole numbers are provided, they are rounded\n", + " scalar (float, optional): multiplies the average; defaults to 1\n", " \n", " Returns:\n", " float: (scaled) average\n", @@ -3493,7 +4399,7 @@ }, { "cell_type": "code", - "execution_count": 99, + "execution_count": 127, "metadata": { "slideshow": { "slide_type": "slide" @@ -3506,18 +4412,18 @@ "7.0" ] }, - "execution_count": 99, + "execution_count": 127, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "mod.average_evens(nums)" + "mod.average_evens(numbers)" ] }, { "cell_type": "code", - "execution_count": 100, + "execution_count": 128, "metadata": { "slideshow": { "slide_type": "-" @@ -3530,13 +4436,13 @@ "14.0" ] }, - "execution_count": 100, + "execution_count": 128, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "mod.average_evens(nums, scalar=2)" + "mod.average_evens(numbers, scalar=2)" ] }, { @@ -3571,11 +4477,11 @@ } }, "source": [ - "A **function** is a **named sequence** of statements that perform a computation.\n", + "A user-defined **function** is a **named sequence** of statements that perform a computation.\n", "\n", - "Functions provide benefits as they:\n", + "Functions provide benefits as they\n", "\n", - "- make programs easier to comprehend and debug for humans as they give names to the smaller parts of a larger program (i.e., they **modularize** a codebase), and\n", + "- make programs easier to comprehend and debug for humans as they give names to the smaller parts of a larger program (i.e., they **modularize** a code base), and\n", "- eliminate redundancies by allowing **reuse of code**.\n", "\n", "Functions are **defined** once with the `def` statement. Then, they may be **called** many times with the call operator `()`.\n", @@ -3586,6 +4492,8 @@ "\n", "**Lambda expressions** create anonymous functions.\n", "\n", + "Functions are a special kind of **callables**. Any object that may be **called** with the call operator `()` is a callable. Built-in functions and **constructors** are other kinds of callables.\n", + "\n", "Core Python can be extended with code from either the **standard library** or **third-party** libraries.\n", "\n", "Outside Jupyter notebooks, Python code is put into **modules** that are grouped in **packages**." @@ -3593,6 +4501,7 @@ } ], "metadata": { + "celltoolbar": "Slideshow", "kernelspec": { "display_name": "Python 3", "language": "python", diff --git a/02_functions_01_review.ipynb b/02_functions_01_review.ipynb index f0e4772..f081ec6 100644 --- a/02_functions_01_review.ipynb +++ b/02_functions_01_review.ipynb @@ -67,7 +67,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q3**: What does it mean for a variable to **go out of scope**?" + "**Q3**: What does it mean for a variable to go out of **scope**?" ] }, { @@ -95,7 +95,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q5**: Explain the concept of **forwarding** a function **call**." + "**Q5**: Explain the concept of **forwarding** a **function call**." ] }, { @@ -119,6 +119,20 @@ " " ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q7**: What are **callables**? How do they relate to `function` objects?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -137,7 +151,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q7**: A mere function **call** is just an **expression**." + "**Q8**: A mere function **call** is just an **expression**." ] }, { @@ -151,7 +165,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q8**: When using the `import` statement, we need to ensure that the imported attributes do **not** overwrite any already defined variables and functions." + "**Q9**: When using the `import` statement, we need to ensure that the imported attributes do *not* overwrite any already defined variables and functions." ] }, { @@ -165,7 +179,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q9:** Functions always have a name by which we can call them." + "**Q10:** Functions always have a name by which we can call them." ] }, { @@ -179,7 +193,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q10**: The [standard library](https://docs.python.org/3/library/index.html) is a collection of numerical tools often used in scientific computing, for example, advanced mathematical functions or utilities for simulation." + "**Q11**: The [standard library](https://docs.python.org/3/library/index.html) is a collection of numerical tools often used in scientific computing, for example, advanced mathematical functions or utilities for simulation." ] }, { diff --git a/02_functions_02_exercises.ipynb b/02_functions_02_exercises.ipynb index a17b45b..00cd198 100644 --- a/02_functions_02_exercises.ipynb +++ b/02_functions_02_exercises.ipynb @@ -32,7 +32,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q1.1**: The [volume of a sphere](https://en.wikipedia.org/wiki/Sphere) is defined as $\\frac{4}{3} * \\pi * r^3$. Calculate this value for $r=10.0$ and round it to 10 digits after the comma. Use the [standard library](https://docs.python.org/3/library/index.html) to obtain a good approximation of $\\pi$." + "**Q1**: The [volume of a sphere](https://en.wikipedia.org/wiki/Sphere) is defined as $\\frac{4}{3} * \\pi * r^3$. Calculate this value for $r=10.0$ and round it to 10 digits after the comma. Use the [standard library](https://docs.python.org/3/library/index.html) to obtain a good approximation of $\\pi$." ] }, { @@ -66,7 +66,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q1.2**: Encapsulate the logic into a function `sphere_volume()` that takes one *positional* argument `radius` and one *keyword-only* argument `digits` defaulting to `5`. The volume should be returned as a `float` object under *all* circumstances. Document your work appropriately in a docstring according to [Google's Python Style Guide](https://github.com/google/styleguide/blob/gh-pages/pyguide.md)." + "**Q2**: Encapsulate the logic into a function `sphere_volume()` that takes one *positional* argument `radius` and one *keyword-only* argument `digits` defaulting to `5`. The volume should be returned as a `float` object under *all* circumstances. Document your work appropriately in a docstring according to [Google's Python Style Guide](https://github.com/google/styleguide/blob/gh-pages/pyguide.md)." ] }, { @@ -83,7 +83,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q1.3**: Evaluate the function with `radius = 100.0` and 1, 5, 10, 15, and 20 digits respectively." + "**Q3**: Evaluate the function with `radius = 100.0` and 1, 5, 10, 15, and 20 digits respectively." ] }, { @@ -144,7 +144,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q1.4**: What observation do you make?" + "**Q4**: What observation do you make?" ] }, { @@ -158,7 +158,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q1.5**: Using the [range()](https://docs.python.org/3/library/functions.html#func-range) built-in, write a `for`-loop and calculate the volume of a sphere with `radius = 42.0` for all `digits` from `1` through `20`. Print out each volume on a separate line.\n", + "**Q5**: Using the [range()](https://docs.python.org/3/library/functions.html#func-range) built-in, write a `for`-loop and calculate the volume of a sphere with `radius = 42.0` for all `digits` from `1` through `20`. Print out each volume on a separate line.\n", "\n", "Note: This is the first task where you need to use the built-in [print()](https://docs.python.org/3/library/functions.html#print) function." ] @@ -186,7 +186,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q1.6**: What lesson do you learn about the `float` type?" + "**Q6**: What lesson do you learn about the `float` type?" ] }, { diff --git a/sample_module.py b/sample_module.py index 2efcda4..b16e7b2 100644 --- a/sample_module.py +++ b/sample_module.py @@ -15,16 +15,17 @@ be used outside the module with a single underscore "_". This way, we can design the code within a module in a modular fashion and only "export" what we want. -Here, all three functions internally forward the computation to an internal -utility function _scaled_average() that contains all the logic common to the -three functions. Also, we define one _default_scalar variable that is used as -the default for the scalar parameter in each of the functions. +Here, all three functions internally forward parts of their computations +to the utility functions _round_all() and _scaled_average() that contain all +the logic common to the three functions. While this example is stylized, it shows how Python modules are often designed. """ -_default_scalar = 1 +def _round_all(numbers): + """Internal utility function to round all numbers in a list.""" + return [round(n) for n in numbers] def _scaled_average(numbers, scalar): @@ -33,43 +34,43 @@ def _scaled_average(numbers, scalar): return scalar * average -def average(numbers, *, scalar=_default_scalar): +def average(numbers, *, scalar=1): """Calculate the average of all numbers in a list. Args: - numbers (list): list of numbers; may be integers or floats - scalar (float, optional): the scalar that multiplies the - average of the even numbers + numbers (list of int's/float's): numbers to be averaged; + if non-whole numbers are provided, they are rounded + scalar (float, optional): multiplies the average; defaults to 1 Returns: float: (scaled) average """ - return _scaled_average(numbers, scalar) + return _scaled_average(_round_all(numbers), scalar) -def average_evens(numbers, *, scalar=_default_scalar): +def average_evens(numbers, *, scalar=1): """Calculate the average of all even numbers in a list. Args: - numbers (list): list of numbers; may be integers or floats - scalar (float, optional): the scalar that multiplies the - average of the even numbers + numbers (list of int's/float's): numbers to be averaged; + if non-whole numbers are provided, they are rounded + scalar (float, optional): multiplies the average; defaults to 1 Returns: float: (scaled) average """ - return _scaled_average([n for n in numbers if n % 2 == 0], scalar) + return _scaled_average([n for n in _round_all(numbers) if n % 2 == 0], scalar) -def average_odds(numbers, *, scalar=_default_scalar): +def average_odds(numbers, *, scalar=1): """Calculate the average of all odd numbers in a list. Args: - numbers (list): list of numbers; may be integers or floats - scalar (float, optional): the scalar that multiplies the - average of the even numbers + numbers (list of int's/float's): numbers to be averaged; + if non-whole numbers are provided, they are rounded + scalar (float, optional): multiplies the average; defaults to 1 Returns: float: (scaled) average """ - return _scaled_average([n for n in numbers if n % 2 != 0], scalar) + return _scaled_average([n for n in _round_all(numbers) if n % 2 != 0], scalar) From 431d2aa31a10b30a556fa4214a448a62dadc8111 Mon Sep 17 00:00:00 2001 From: Alexander Hess Date: Wed, 5 Feb 2020 18:33:32 +0100 Subject: [PATCH 3/3] Bump version number --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b77fd3a..9cc1c12 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "intro-to-python" -version = "0.6.2" +version = "0.6.3" description = "An introduction to Python and programming for wanna-be data scientists" authors = ["Alexander Hess "] license = "MIT"