intro-to-python/07_sequences_00_content.ipynb

6951 lines
201 KiB
Text

{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"**Important**: Click on \"*Kernel*\" > \"*Restart Kernel and Clear All Outputs*\" *before* reading this chapter in [JupyterLab <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_jp.png\">](https://jupyterlab.readthedocs.io/en/stable/) (e.g., in the cloud on [MyBinder <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_mb.png\">](https://mybinder.org/v2/gh/webartifex/intro-to-python/master?urlpath=lab/tree/07_sequences_00_content.ipynb))"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"# Chapter 7: Sequential Data"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"We studied numbers (cf., [Chapter 5 <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/05_numbers_00_content.ipynb)) and textual data (cf., [Chapter 6 <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/06_text_00_content.ipynb)) first mainly because objects of the presented data types are \"simple.\" That is so for two reasons: First, they are *immutable*, and, as we saw in the \"*Who am I? And how many?*\" section in [Chapter 1 <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements_00_content.ipynb#Who-am-I?-And-how-many?), mutable objects can quickly become hard to reason about. Second, they are \"flat\" in the sense that they are *not* composed of other objects.\n",
"\n",
"The `str` type is a bit of a corner case in this regard. While one could argue that a longer `str` object, for example, `\"text\"`, is composed of individual characters, this is *not* the case in memory as the literal `\"text\"` only creates *one* object (i.e., one \"bag\" of $0$s and $1$s modeling all characters).\n",
"\n",
"This chapter, [Chapter 8 <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/08_mfr_00_content.ipynb), and [Chapter 9 <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/09_mappings_00_content.ipynb) introduce various \"complex\" data types. While some are mutable and others are not, they all share that they are primarily used to \"manage,\" or structure, the memory in a program (i.e., they provide references to other objects). Unsurprisingly, computer scientists refer to the ideas behind these data types as **[data structures <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_wiki.png\">](https://en.wikipedia.org/wiki/Data_structure)**.\n",
"\n",
"In this chapter, we focus on data types that model all kinds of sequential data. Examples of such data are [spreadsheets <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_wiki.png\">](https://en.wikipedia.org/wiki/Spreadsheet) or [matrices <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_wiki.png\">](https://en.wikipedia.org/wiki/Matrix_%28mathematics%29) and [vectors <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_wiki.png\">](https://en.wikipedia.org/wiki/Vector_%28mathematics_and_physics%29). These formats share the property that they are composed of smaller units that come in a sequence of, for example, rows/columns/cells or elements/entries."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Collections vs. Sequences"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"[Chapter 6 <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/06_text_00_content.ipynb#A-\"String\"-of-Characters) already describes the **sequence** properties of `str` objects. In this section, we take a step back and study these properties one by one.\n",
"\n",
"The [collections.abc <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/collections.abc.html) module in the [standard library <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/index.html) defines a variety of **abstract base classes** (ABCs). We saw ABCs already in [Chapter 5 <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/05_numbers_00_content.ipynb#The-Numerical-Tower), where we use the ones from the [numbers <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/numbers.html) module in the [standard library <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/index.html) to classify Python's numeric data types according to mathematical ideas. Now, we take the ABCs from the [collections.abc <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/collections.abc.html) module to classify the data types in this chapter according to their behavior in various contexts.\n",
"\n",
"As an illustration, consider `numbers` and `text` below, two objects of *different* types."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"numbers = [7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]\n",
"text = \"Lorem ipsum dolor sit amet.\""
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Among others, one commonality between the two is that we may loop over them with the `for` statement. So, in the context of iteration, both exhibit the *same* behavior."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"7 11 8 5 3 12 2 6 9 10 1 4 "
]
}
],
"source": [
"for number in numbers:\n",
" print(number, end=\" \")"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"L o r e m i p s u m d o l o r s i t a m e t . "
]
}
],
"source": [
"for character in text:\n",
" print(character, end=\" \")"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"In [Chapter 4 <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration_00_content.ipynb#Containers-vs.-Iterables), we referred to such types as *iterables*. That is *not* a proper [English](https://dictionary.cambridge.org/spellcheck/english-german/?q=iterable) word, even if it may sound like one at first sight. Yet, it is an official term in the Python world formalized with the `Iterable` ABC in the [collections.abc <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/collections.abc.html) module.\n",
"\n",
"For the data science practitioner, it is worthwhile to know such terms as, for example, the documentation on the [built-ins <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html) uses them extensively: In simple words, any built-in that takes an argument called \"*iterable*\" may be called with *any* object that supports being looped over. Already familiar [built-ins <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html) include [enumerate() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#enumerate), [sum() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#sum), or [zip() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#zip). So, they do *not* require the argument to be of a certain data type (e.g., `list`); instead, any *iterable* type works."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"import collections.abc as abc"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"collections.abc.Iterable"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"abc.Iterable"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"As seen in [Chapter 5 <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/05_numbers_00_content.ipynb#Goose-Typing), we can use ABCs with the built-in [isinstance() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#isinstance) function to check if an object supports a behavior.\n",
"\n",
"So, let's \"ask\" Python if it can loop over `numbers` or `text`."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"isinstance(numbers, abc.Iterable)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"isinstance(text, abc.Iterable)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Contrary to `list` or `str` objects, numeric objects are *not* iterable."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"isinstance(999, abc.Iterable)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Instead of asking, we could try to loop over `999`, but this results in a `TypeError`."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"ename": "TypeError",
"evalue": "'int' object is not iterable",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-9-8249bf25b5b7>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mfor\u001b[0m \u001b[0mdigit\u001b[0m \u001b[0;32min\u001b[0m \u001b[0;36m999\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdigit\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mTypeError\u001b[0m: 'int' object is not iterable"
]
}
],
"source": [
"for digit in 999:\n",
" print(digit)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Most of the data types in this and the next chapter exhibit three [orthogonal <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_wiki.png\">](https://en.wikipedia.org/wiki/Orthogonality) (i.e., \"independent\") behaviors, formalized by ABCs in the [collections.abc <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/collections.abc.html) module as:\n",
"- `Iterable`: An object may be looped over.\n",
"- `Container`: An object \"contains\" references to other objects; a \"whole\" is composed of many \"parts.\"\n",
"- `Sized`: The number of references to other objects, the \"parts,\" is *finite*.\n",
"\n",
"The characteristical operation supported by `Container` types is the `in` operator for membership testing."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"0 in numbers"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"\"l\" in text"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Alternatively, we could also check if `numbers` and `text` are `Container` types with [isinstance() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#isinstance)."
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"isinstance(numbers, abc.Container)"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"isinstance(text, abc.Container)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Numeric objects do *not* \"contain\" references to other objects, and that is why they are considered \"flat\" data types. The `in` operator raises a `TypeError`. Conceptually speaking, Python views numeric types as \"wholes\" without any \"parts.\""
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"isinstance(999, abc.Container)"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"ename": "TypeError",
"evalue": "argument of type 'int' is not iterable",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-15-b3e0b3c32aec>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;36m9\u001b[0m \u001b[0;32min\u001b[0m \u001b[0;36m999\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mTypeError\u001b[0m: argument of type 'int' is not iterable"
]
}
],
"source": [
"9 in 999"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Analogously, being `Sized` types, we can pass `numbers` and `text` as the argument to the built-in [len() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#len) function and obtain \"meaningful\" results. The exact meaning depends on the data type: For `numbers`, [len() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#len) tells us how many elements are in the `list` object; for `text`, it tells us how many [Unicode characters <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_wiki.png\">](https://en.wikipedia.org/wiki/Unicode) make up the `str` object. *Abstractly* speaking, both data types exhibit the *same* behavior of *finiteness*."
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"12"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"len(numbers)"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"27"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"len(text)"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"isinstance(numbers, abc.Sized)"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"isinstance(text, abc.Sized)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"On the contrary, even though `999` consists of three digits for humans, numeric objects in Python have no concept of a \"size\" or \"length,\" and the [len() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#len) function raises a `TypeError`."
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"isinstance(999, abc.Sized)"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"ename": "TypeError",
"evalue": "object of type 'int' has no len()",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-21-ae09c1aa5305>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m999\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: object of type 'int' has no len()"
]
}
],
"source": [
"len(999)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"These three behaviors are so essential that whenever they coincide for a data type, it is called a **collection**, formalized with the `Collection` ABC. That is where the [collections.abc <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/collections.abc.html) module got its name from: It summarizes all ABCs related to collections; in particular, it defines a hierarchy of specialized kinds of collections.\n",
"\n",
"Without going into too much detail, one way to read the summary table at the beginning of the [collections.abc <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/collections.abc.html) module's documention is as follows: The first column, titled \"ABC\", lists all collection-related ABCs in Python. The second column, titled \"Inherits from,\" indicates if the idea behind the ABC is *original* (e.g., the first row with the `Container` ABC has an empty \"Inherits from\" column) or a *combination* (e.g., the row with the `Collection` ABC has `Sized`, `Iterable`, and `Container` in the \"Inherits from\" column). The third and fourth columns list the methods that come with a data type following an ABC. We keep ignoring the methods named in the dunder style for now.\n",
"\n",
"So, let's confirm that both `numbers` and `text` are collections."
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 22,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"isinstance(numbers, abc.Collection)"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"isinstance(text, abc.Collection)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"They share one more common behavior: When looping over them, we can *predict* the *order* of the elements or characters. The ABC in the [collections.abc <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/collections.abc.html) module corresponding to this behavior is `Reversible`. While sounding unintuitive at first, it is evident that if something is reversible, it must have a forward order, to begin with.\n",
"\n",
"The [reversed() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#reversed) built-in allows us to loop over the elements or characters in reverse order."
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"4 1 10 9 6 2 12 3 5 8 11 7 "
]
}
],
"source": [
"for number in reversed(numbers):\n",
" print(number, end=\" \")"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
". t e m a t i s r o l o d m u s p i m e r o L "
]
}
],
"source": [
"for character in reversed(text):\n",
" print(character, end=\" \")"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"isinstance(numbers, abc.Reversible)"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 27,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"isinstance(text, abc.Reversible)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Collections that exhibit this fourth behavior are referred to as **sequences**, formalized with the `Sequence` ABC in the [collections.abc <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/collections.abc.html) module."
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"isinstance(numbers, abc.Sequence)"
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 29,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"isinstance(text, abc.Sequence)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Most of the data types introduced in the remainder of this chapter are sequences. Nevertheless, we also look at some data types that are neither collections nor sequences but still useful to model sequential data in practice.\n",
"\n",
"In Python-related documentations, the terms collection and sequence are heavily used, and the data science practitioner should always think of them in terms of the three or four behaviors they exhibit.\n",
"\n",
"Data types that are collections but not sequences are covered in [Chapter 9 <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/09_mappings_00_content.ipynb)."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## The `list` Type"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"As already seen multiple times, to create a `list` object, we use the *literal notation* and list all elements within brackets `[` and `]`."
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"empty = []"
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"simple = [40, 50]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The elements do *not* need to be of the *same* type, and `list` objects may also be **nested**."
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"nested = [empty, 10, 20.0, \"Thirty\", simple]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"[PythonTutor <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](http://pythontutor.com/visualize.html#code=empty%20%3D%20%5B%5D%0Asimple%20%3D%20%5B40,%2050%5D%0Anested%20%3D%20%5Bempty,%2010,%2020.0,%20%22Thirty%22,%20simple%5D&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) shows how `nested` holds references to the `empty` and `simple` objects. Technically, it holds three more references to the `10`, `20.0`, and `\"Thirty\"` objects as well. However, to simplify the visualization, these three objects are shown right inside the `nested` object. That may be done because they are immutable and \"flat\" data types. In general, the $0$s and $1$s inside a `list` object in memory always constitute references to other objects only."
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[[], 10, 20.0, 'Thirty', [40, 50]]"
]
},
"execution_count": 33,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nested"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Let's not forget that `nested` is an object on its own with an *identity* and *data type*."
]
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"140483818052016"
]
},
"execution_count": 34,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"id(nested)"
]
},
{
"cell_type": "code",
"execution_count": 35,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"list"
]
},
"execution_count": 35,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"type(nested)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Alternatively, we use the built-in [list() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#func-list) constructor to create a `list` object out of any (finite) *iterable* we pass to it as the argument.\n",
"\n",
"For example, we can wrap the [range() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#func-range) built-in with [list() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#func-list): As described in [Chapter 4 <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration_00_content.ipynb#Containers-vs.-Iterables), `range` objects, like `range(1, 13)` below, are iterable and generate `int` objects \"on the fly\" (i.e., one by one). The [list() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#func-list) around it acts like a `for`-loop and **materializes** twelve `int` objects in memory that then become the elements of the newly created `list` object. [PythonTutor <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](http://pythontutor.com/visualize.html#code=r%20%3D%20range%281,%2013%29%0Al%20%3D%20list%28range%281,%2013%29%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) shows this difference visually."
]
},
{
"cell_type": "code",
"execution_count": 36,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]"
]
},
"execution_count": 36,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"list(range(1, 13))"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Beware of passing a `range` object over a \"big\" horizon as the argument to [list() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#func-list) as that may lead to a `MemoryError` and the computer crashing."
]
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"ename": "MemoryError",
"evalue": "",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mMemoryError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-37-dbb397cbb3b9>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mlist\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mrange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m999_999_999_999\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mMemoryError\u001b[0m: "
]
}
],
"source": [
"list(range(999_999_999_999))"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"As another example, we create a `list` object from a `str` object, which is iterable, as well. Then, the individual characters become the elements of the new `list` object!"
]
},
{
"cell_type": "code",
"execution_count": 38,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"['i', 't', 'e', 'r', 'a', 'b', 'l', 'e']"
]
},
"execution_count": 38,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"list(\"iterable\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"### Sequence Behaviors"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"`list` objects are *sequences*. To reiterate that, we briefly summarize the *four* behaviors of a sequence and provide some more `list`-specific details below:"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"- `Container`:\n",
" - holds references to other objects in memory (with their own *identity* and *type*)\n",
" - implements membership testing via the `in` operator\n",
"- `Iterable`:\n",
" - supports being looped over\n",
" - works with the `for` or `while` statements\n",
"- `Reversible`:\n",
" - the elements come in a *predictable* order that we may loop over in a forward or backward fashion\n",
" - works with the [reversed() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#reversed) built-in\n",
"- `Sized`:\n",
" - the number of elements is finite *and* known in advance\n",
" - works with the built-in [len() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#len) function"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The \"length\" of `nested` is *five* because `empty` and `simple` count as *one* element each. In other words, `nested` holds five references to other objects, two of which are `list` objects."
]
},
{
"cell_type": "code",
"execution_count": 39,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"5"
]
},
"execution_count": 39,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"len(nested)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"With a `for`-loop, we can iterate over all elements in a *predictable* order, forward or backward. As `list` objects hold *references* to other *objects*, these have an *indentity* and may even be of *different* types; however, the latter observation is rarely, if ever, useful in practice."
]
},
{
"cell_type": "code",
"execution_count": 40,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[] 140483817280848 <class 'list'>\n",
"10 94627844305984 <class 'int'>\n",
"20.0 140483817093232 <class 'float'>\n",
"Thirty 140483698711280 <class 'str'>\n",
"[40, 50] 140483698705104 <class 'list'>\n"
]
}
],
"source": [
"for element in nested:\n",
" print(str(element).ljust(10), str(id(element)).ljust(18), type(element))"
]
},
{
"cell_type": "code",
"execution_count": 41,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[40, 50] Thirty 20.0 10 [] "
]
}
],
"source": [
"for element in reversed(nested):\n",
" print(element, end=\" \")"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The `in` operator checks if a given object is \"contained\" in a `list` object. It uses the `==` operator behind the scenes (i.e., *not* the `is` operator) conducting a **[linear search <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_wiki.png\">](https://en.wikipedia.org/wiki/Linear_search)**: So, Python implicitly loops over *all* elements and only stops prematurely if an element evaluates equal to the searched object. A linear search may, therefore, be relatively *slow* for big `list` objects."
]
},
{
"cell_type": "code",
"execution_count": 42,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 42,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"10 in nested"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"`20` compares equal to the `20.0` in `nested`."
]
},
{
"cell_type": "code",
"execution_count": 43,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 43,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"20 in nested"
]
},
{
"cell_type": "code",
"execution_count": 44,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 44,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"30 in nested"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"### Indexing"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Because of the *predictable* order and the *finiteness*, each element in a sequence can be labeled with a unique *index*, an `int` object in the range $0 \\leq \\text{index} < \\lvert \\text{sequence} \\rvert$.\n",
"\n",
"Brackets, `[` and `]`, are the literal syntax for accessing individual elements of any sequence type. In this book, we also call them the *indexing operator* in this context."
]
},
{
"cell_type": "code",
"execution_count": 45,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[]"
]
},
"execution_count": 45,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nested[0]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The last index is one less than `len(nested)`, and Python raises an `IndexError` if we look up an index that is not in the range."
]
},
{
"cell_type": "code",
"execution_count": 46,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"ename": "IndexError",
"evalue": "list index out of range",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mIndexError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-46-cb216f4a067e>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mnested\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m5\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mIndexError\u001b[0m: list index out of range"
]
}
],
"source": [
"nested[5]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Negative indices are used to count in reverse order from the end of a sequence, and brackets may be chained to access nested objects. So, to access the `50` inside `simple` via the `nested` object, we write `nested[-1][1]`."
]
},
{
"cell_type": "code",
"execution_count": 47,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[40, 50]"
]
},
"execution_count": 47,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nested[-1]"
]
},
{
"cell_type": "code",
"execution_count": 48,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"50"
]
},
"execution_count": 48,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nested[-1][1]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"### Slicing"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Slicing `list` objects works analogously to slicing `str` objects: We use the literal syntax with either one or two colons `:` inside the brackets `[]` to separate the *start*, *stop*, and *step* values. Slicing creates a *new* `list` object with the elements chosen from the original one.\n",
"\n",
"For example, to obtain the three elements in the \"middle\" of `nested`, we slice from `1` (including) to `4` (excluding)."
]
},
{
"cell_type": "code",
"execution_count": 49,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[10, 20.0, 'Thirty']"
]
},
"execution_count": 49,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nested[1:4]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"To obtain \"every other\" element, we slice from beginning to end, defaulting to `0` and `len(nested)` when omitted, in steps of `2`."
]
},
{
"cell_type": "code",
"execution_count": 50,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[[], 20.0, [40, 50]]"
]
},
"execution_count": 50,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nested[::2]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The literal notation with the colons `:` is *syntactic sugar*. It saves us from using the [slice() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#slice) built-in to create `slice` objects. [slice() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#slice) takes *start*, *stop*, and *step* arguments in the same way as the familiar [range() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#func-range), and the `slice` objects it creates are used just as *indexes* above.\n",
"\n",
"In most cases, the literal notation is more convenient to use; however, with `slice` objects, we can give names to slices and reuse them across several sequences."
]
},
{
"cell_type": "code",
"execution_count": 51,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"middle = slice(1, 4)"
]
},
{
"cell_type": "code",
"execution_count": 52,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"slice"
]
},
"execution_count": 52,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"type(middle)"
]
},
{
"cell_type": "code",
"execution_count": 53,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[10, 20.0, 'Thirty']"
]
},
"execution_count": 53,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nested[middle]"
]
},
{
"cell_type": "code",
"execution_count": 54,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[11, 8, 5]"
]
},
"execution_count": 54,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"numbers[middle]"
]
},
{
"cell_type": "code",
"execution_count": 55,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"'ore'"
]
},
"execution_count": 55,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"text[middle]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"`slice` objects come with three read-only attributes `start`, `stop`, and `step` on them."
]
},
{
"cell_type": "code",
"execution_count": 56,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"1"
]
},
"execution_count": 56,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"middle.start"
]
},
{
"cell_type": "code",
"execution_count": 57,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"4"
]
},
"execution_count": 57,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"middle.stop"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"If not passed to [slice() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#slice), these attributes default to `None`. That is why the cell below has no output."
]
},
{
"cell_type": "code",
"execution_count": 58,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"middle.step"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"A good trick to know is taking a \"full\" slice: This copies *all* elements of a `list` object into a *new* `list` object."
]
},
{
"cell_type": "code",
"execution_count": 59,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"nested_copy = nested[:]"
]
},
{
"cell_type": "code",
"execution_count": 60,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[[], 10, 20.0, 'Thirty', [40, 50]]"
]
},
"execution_count": 60,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nested_copy"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"At first glance, `nested` and `nested_copy` seem to cause no pain. For `list` objects, the comparison operator `==` goes over the elements in both operands in a pairwise fashion and checks if they all evaluate equal (cf., the \"*List Comparison*\" section below for more details).\n",
"\n",
"We confirm that `nested` and `nested_copy` compare equal as could be expected but also that they are *different* objects."
]
},
{
"cell_type": "code",
"execution_count": 61,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 61,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nested == nested_copy"
]
},
{
"cell_type": "code",
"execution_count": 62,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 62,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nested is nested_copy"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"However, as [PythonTutor <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](http://pythontutor.com/visualize.html#code=nested%20%3D%20%5B%5B%5D,%2010,%2020.0,%20%22Thirty%22,%20%5B40,%2050%5D%5D%0Anested_copy%20%3D%20nested%5B%3A%5D&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) reveals, only the *references* to the elements are copied, and not the objects in `nested` themselves! Because of that, `nested_copy` is a so-called **[shallow copy <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_wiki.png\">](https://en.wikipedia.org/wiki/Object_copying#Shallow_copy)** of `nested`.\n",
"\n",
"We could also see this with the [id() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#id) function: The respective first elements in both `nested` and `nested_copy` are the *same* object, namely `empty`. So, we have three ways of accessing the *same* address in memory. Also, we say that `nested` and `nested_copy` partially share the *same* state."
]
},
{
"cell_type": "code",
"execution_count": 63,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 63,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nested[0] is nested_copy[0]"
]
},
{
"cell_type": "code",
"execution_count": 64,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"140483817280848"
]
},
"execution_count": 64,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"id(nested[0])"
]
},
{
"cell_type": "code",
"execution_count": 65,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"140483817280848"
]
},
"execution_count": 65,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"id(nested_copy[0])"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Knowing this becomes critical if the elements in a `list` object are mutable objects (i.e., we can change them *in place*), and this is the case with `nested` and `nested_copy`, as we see in the next section on \"*Mutability*\".\n",
"\n",
"As both the original `nested` object and its copy reference the *same* `list` objects in memory, any changes made to them are visible to both! Because of that, working with shallow copies can easily become confusing.\n",
"\n",
"Instead of a shallow copy, we could also create a so-called **[deep copy <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_wiki.png\">](https://en.wikipedia.org/wiki/Object_copying#Deep_copy)** of `nested`: Then, the copying process recursively follows every reference in a nested data structure and creates copies of *every* object found.\n",
"\n",
"To explicitly create shallow or deep copies, the [copy <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/copy.html) module in the [standard library <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/index.html) provides two functions, [copy() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/copy.html#copy.copy) and [deepcopy() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/copy.html#copy.deepcopy). We must always remember that slicing creates *shallow* copies only."
]
},
{
"cell_type": "code",
"execution_count": 66,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"import copy"
]
},
{
"cell_type": "code",
"execution_count": 67,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"nested_deep_copy = copy.deepcopy(nested)"
]
},
{
"cell_type": "code",
"execution_count": 68,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 68,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nested == nested_deep_copy"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Now, the first elements of `nested` and `nested_deep_copy` are *different* objects, and [PythonTutor <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](http://pythontutor.com/visualize.html#code=import%20copy%0Anested%20%3D%20%5B%5B%5D,%2010,%2020.0,%20%22Thirty%22,%20%5B40,%2050%5D%5D%0Anested_deep_copy%20%3D%20copy.deepcopy%28nested%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) shows that there are *six* `list` objects in memory."
]
},
{
"cell_type": "code",
"execution_count": 69,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 69,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nested[0] is nested_deep_copy[0]"
]
},
{
"cell_type": "code",
"execution_count": 70,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"140483817280848"
]
},
"execution_count": 70,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"id(nested[0])"
]
},
{
"cell_type": "code",
"execution_count": 71,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"140483818059328"
]
},
"execution_count": 71,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"id(nested_deep_copy[0])"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"As this [StackOverflow question <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_so.png\">](https://stackoverflow.com/questions/184710/what-is-the-difference-between-a-deep-copy-and-a-shallow-copy) shows, understanding shallow and deep copies is a common source of confusion independent of the programming language."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"### Mutability"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"In contrast to `str` objects, `list` objects are *mutable*: We may assign new elements to indices or slices and also remove elements. That changes the *references* in a `list` object. In general, if an object is *mutable*, we say that it may be changed *in place*."
]
},
{
"cell_type": "code",
"execution_count": 72,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"nested[0] = 0"
]
},
{
"cell_type": "code",
"execution_count": 73,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[0, 10, 20.0, 'Thirty', [40, 50]]"
]
},
"execution_count": 73,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nested"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"When we re-assign a slice, we can even change the size of the `list` object."
]
},
{
"cell_type": "code",
"execution_count": 74,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"nested[:4] = [100, 100, 100]"
]
},
{
"cell_type": "code",
"execution_count": 75,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[100, 100, 100, [40, 50]]"
]
},
"execution_count": 75,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nested"
]
},
{
"cell_type": "code",
"execution_count": 76,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"4"
]
},
"execution_count": 76,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"len(nested)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The `list` object's identity does *not* change. That is the main point behind mutable objects."
]
},
{
"cell_type": "code",
"execution_count": 77,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"140483818052016"
]
},
"execution_count": 77,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"id(nested) # same memory location as before"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"`nested_copy` is unchanged!"
]
},
{
"cell_type": "code",
"execution_count": 78,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[[], 10, 20.0, 'Thirty', [40, 50]]"
]
},
"execution_count": 78,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nested_copy"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Let's change the nested `[40, 50]` via `nested_copy` into `[1, 2, 3]` by replacing all its elements."
]
},
{
"cell_type": "code",
"execution_count": 79,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"nested_copy[-1][:] = [1, 2, 3]"
]
},
{
"cell_type": "code",
"execution_count": 80,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[[], 10, 20.0, 'Thirty', [1, 2, 3]]"
]
},
"execution_count": 80,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nested_copy"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"That has a surprising side effect on `nested`."
]
},
{
"cell_type": "code",
"execution_count": 81,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[100, 100, 100, [1, 2, 3]]"
]
},
"execution_count": 81,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nested"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"That is precisely the confusion we talked about above when we said that `nested_copy` is a *shallow* copy of `nested`. [PythonTutor <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](http://pythontutor.com/visualize.html#code=nested%20%3D%20%5B%5B%5D,%2010,%2020.0,%20%22Thirty%22,%20%5B40,%2050%5D%5D%0Anested_copy%20%3D%20nested%5B%3A%5D%0Anested%5B%3A4%5D%20%3D%20%5B100,%20100,%20100%5D%0Anested_copy%5B-1%5D%5B%3A%5D%20%3D%20%5B1,%202,%203%5D&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) shows how both reference the *same* nested `list` object that is changed *in place* from `[40, 50]` into `[1, 2, 3]`.\n",
"\n",
"Lastly, we use the `del` statement to remove an element."
]
},
{
"cell_type": "code",
"execution_count": 82,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"del nested[-1]"
]
},
{
"cell_type": "code",
"execution_count": 83,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[100, 100, 100]"
]
},
"execution_count": 83,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nested"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The `del` statement also works for slices. Here, we remove all references `nested` holds."
]
},
{
"cell_type": "code",
"execution_count": 84,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"del nested[:]"
]
},
{
"cell_type": "code",
"execution_count": 85,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[]"
]
},
"execution_count": 85,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nested"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Mutability for sequences is formalized by the `MutableSequence` ABC in the [collections.abc <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/collections.abc.html) module."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"### List Methods"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The `list` type is an essential data structure in any real-world Python application, and many typical `list`-related algorithms from computer science theory are already built into it at the C level (cf., the [documentation <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types) or the [tutorial <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/tutorial/datastructures.html#more-on-lists) for a full overview; unfortunately, not all methods have direct links). So, understanding and applying the built-in methods of the `list` type not only speeds up the development process but also makes programs significantly faster.\n",
"\n",
"In contrast to the `str` type's methods in [Chapter 6 <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/06_text_00_content.ipynb#String-Methods) (e.g., [upper() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/stdtypes.html#str.upper) or [lower() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/stdtypes.html#str.lower)), the `list` type's methods that mutate an object do so *in place*. That means they *never* create *new* `list` objects and return `None` to indicate that. So, we must *never* assign the return value of `list` methods to the variable holding the list!\n",
"\n",
"Let's look at the following `names` example."
]
},
{
"cell_type": "code",
"execution_count": 86,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"names = [\"Carl\", \"Peter\"]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"To add an object to the end of `names`, we use the `append()` method. The code cell shows no output indicating that `None` must be the return value."
]
},
{
"cell_type": "code",
"execution_count": 87,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"names.append(\"Eckardt\")"
]
},
{
"cell_type": "code",
"execution_count": 88,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"['Carl', 'Peter', 'Eckardt']"
]
},
"execution_count": 88,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"names"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"With the `extend()` method, we may also append multiple elements provided by an iterable. Here, the iterable is a `list` object itself holding two `str` objects."
]
},
{
"cell_type": "code",
"execution_count": 89,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"names.extend([\"Karl\", \"Oliver\"])"
]
},
{
"cell_type": "code",
"execution_count": 90,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"['Carl', 'Peter', 'Eckardt', 'Karl', 'Oliver']"
]
},
"execution_count": 90,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"names"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Similar to `append()`, we may add a new element at an arbitrary position with the `insert()` method. `insert()` takes two arguments, an *index* and the element to be inserted."
]
},
{
"cell_type": "code",
"execution_count": 91,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"names.insert(1, \"Berthold\")"
]
},
{
"cell_type": "code",
"execution_count": 92,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"['Carl', 'Berthold', 'Peter', 'Eckardt', 'Karl', 'Oliver']"
]
},
"execution_count": 92,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"names"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"`list` objects may be sorted *in place* with the [sort() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/stdtypes.html#list.sort) method. That is different from the built-in [sorted() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#sorted) function that takes any *finite* and *iterable* object and returns a *new* `list` object with the iterable's elements sorted!"
]
},
{
"cell_type": "code",
"execution_count": 93,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"['Berthold', 'Carl', 'Eckardt', 'Karl', 'Oliver', 'Peter']"
]
},
"execution_count": 93,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sorted(names)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"As the previous code cell created a *new* `list` object, `names` is still unsorted."
]
},
{
"cell_type": "code",
"execution_count": 94,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"['Carl', 'Berthold', 'Peter', 'Eckardt', 'Karl', 'Oliver']"
]
},
"execution_count": 94,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"names"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Let's sort the elements in `names` instead."
]
},
{
"cell_type": "code",
"execution_count": 95,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"names.sort()"
]
},
{
"cell_type": "code",
"execution_count": 96,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"['Berthold', 'Carl', 'Eckardt', 'Karl', 'Oliver', 'Peter']"
]
},
"execution_count": 96,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"names"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"To sort in reverse order, we pass a keyword-only `reverse=True` argument to either the [sort() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/stdtypes.html#list.sort) method or the [sorted() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#sorted) function."
]
},
{
"cell_type": "code",
"execution_count": 97,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"names.sort(reverse=True)"
]
},
{
"cell_type": "code",
"execution_count": 98,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"['Peter', 'Oliver', 'Karl', 'Eckardt', 'Carl', 'Berthold']"
]
},
"execution_count": 98,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"names"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The [sort() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/stdtypes.html#list.sort) method and the [sorted() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#sorted) function sort the elements in `names` in alphabetical order, forward or backward. However, that does *not* hold in general.\n",
"\n",
"We mention above that `list` objects may contain objects of *any* type and even of *mixed* types. Because of that, the sorting is **[delegated <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_wiki.png\">](https://en.wikipedia.org/wiki/Delegation_(object-oriented_programming))** to the elements in a `list` object. In a way, Python \"asks\" the elements in a `list` object to sort themselves. As `names` contains only `str` objects, they are sorted according the the comparison rules explained in [Chapter 6 <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/06_text_00_content.ipynb#String-Comparison).\n",
"\n",
"To customize the sorting, we pass a keyword-only `key` argument to [sort() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/stdtypes.html#list.sort) or [sorted() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#sorted), which must be a `function` object accepting *one* positional argument. Then, the elements in the `list` object are passed to that one by one, and the return values are used as the **sort keys**. The `key` argument is also a popular use case for `lambda` expressions.\n",
"\n",
"For example, to sort `names` not by alphabet but by the names' lengths, we pass in a reference to the built-in [len() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#len) function as `key=len`. Note that there are *no* parentheses after `len`!"
]
},
{
"cell_type": "code",
"execution_count": 99,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"names.sort(key=len)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"If two names have the same length, their relative order is kept as is. That is why `\"Karl\"` comes before `\"Carl\" ` below. A [sorting algorithm <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_wiki.png\">](https://en.wikipedia.org/wiki/Sorting_algorithm) with that property is called **[stable <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_wiki.png\">](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability)**.\n",
"\n",
"Sorting is an important topic in programming, and we refer to the official [HOWTO <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/howto/sorting.html) for a more comprehensive introduction."
]
},
{
"cell_type": "code",
"execution_count": 100,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"['Karl', 'Carl', 'Peter', 'Oliver', 'Eckardt', 'Berthold']"
]
},
"execution_count": 100,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"names"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"`sort(reverse=True)` is different from the `reverse()` method. Whereas the former applies some sorting rule in reverse order, the latter simply reverses the elements in a `list` object."
]
},
{
"cell_type": "code",
"execution_count": 101,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"names.reverse()"
]
},
{
"cell_type": "code",
"execution_count": 102,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"['Berthold', 'Eckardt', 'Oliver', 'Peter', 'Carl', 'Karl']"
]
},
"execution_count": 102,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"names"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The `pop()` method removes the *last* element from a `list` object *and* returns it. Below we **capture** the `removed` element to show that the return value is not `None` as with all the methods introduced so far."
]
},
{
"cell_type": "code",
"execution_count": 103,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"removed = names.pop()"
]
},
{
"cell_type": "code",
"execution_count": 104,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"'Karl'"
]
},
"execution_count": 104,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"removed"
]
},
{
"cell_type": "code",
"execution_count": 105,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"['Berthold', 'Eckardt', 'Oliver', 'Peter', 'Carl']"
]
},
"execution_count": 105,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"names"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"`pop()` takes an optional *index* argument and removes that instead.\n",
"\n",
"So, to remove the second element, `\"Eckhardt\"`, from `names`, we write this."
]
},
{
"cell_type": "code",
"execution_count": 106,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"removed = names.pop(1)"
]
},
{
"cell_type": "code",
"execution_count": 107,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"'Eckardt'"
]
},
"execution_count": 107,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"removed"
]
},
{
"cell_type": "code",
"execution_count": 108,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"['Berthold', 'Oliver', 'Peter', 'Carl']"
]
},
"execution_count": 108,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"names"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Instead of removing an element by its index, we can also remove it by its value with the `remove()` method. Behind the scenes, Python then compares the object to be removed, `\"Peter\"` in the example, sequentially to each element with the `==` operator and removes the *first* one that evaluates equal to it. `remove()` does *not* return the removed element."
]
},
{
"cell_type": "code",
"execution_count": 109,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"names.remove(\"Peter\")"
]
},
{
"cell_type": "code",
"execution_count": 110,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"['Berthold', 'Oliver', 'Carl']"
]
},
"execution_count": 110,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"names"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Also, `remove()` raises a `ValueError` if the value is not found."
]
},
{
"cell_type": "code",
"execution_count": 111,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"ename": "ValueError",
"evalue": "list.remove(x): x not in list",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-111-6bb75d50cf06>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mnames\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mremove\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Peter\"\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: list.remove(x): x not in list"
]
}
],
"source": [
"names.remove(\"Peter\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"`list` objects implement an `index()` method that returns the position of the first element that compares equal to its argument. It fails *loudly* with a `ValueError` if no element compares equal."
]
},
{
"cell_type": "code",
"execution_count": 112,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"['Berthold', 'Oliver', 'Carl']"
]
},
"execution_count": 112,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"names"
]
},
{
"cell_type": "code",
"execution_count": 113,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"1"
]
},
"execution_count": 113,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"names.index(\"Oliver\")"
]
},
{
"cell_type": "code",
"execution_count": 114,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"ename": "ValueError",
"evalue": "'Karl' is not in list",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-114-8c741a6680be>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mnames\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Karl\"\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: 'Karl' is not in list"
]
}
],
"source": [
"names.index(\"Karl\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The `count()` method returns the number of elements that compare equal to its argument."
]
},
{
"cell_type": "code",
"execution_count": 115,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"1"
]
},
"execution_count": 115,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"names.count(\"Carl\")"
]
},
{
"cell_type": "code",
"execution_count": 116,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"0"
]
},
"execution_count": 116,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"names.count(\"Karl\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Two more methods, `copy()` and `clear()`, are *syntactic sugar* and replace working with slices.\n",
"\n",
"`copy()` creates a *shallow* copy. So, `names.copy()` below does the same as taking a full slice with `names[:]`, and the caveats from above apply, too."
]
},
{
"cell_type": "code",
"execution_count": 117,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"names_copy = names.copy()"
]
},
{
"cell_type": "code",
"execution_count": 118,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"['Berthold', 'Oliver', 'Carl']"
]
},
"execution_count": 118,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"names_copy"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"`clear()` removes all references from a `list` object. So, `names_copy.clear()` is the same as `del names_copy[:]`."
]
},
{
"cell_type": "code",
"execution_count": 119,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"names_copy.clear()"
]
},
{
"cell_type": "code",
"execution_count": 120,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[]"
]
},
"execution_count": 120,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"names_copy"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Many methods introduced in this section are mentioned in the [collections.abc <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/collections.abc.html) module's documentation as well: While the `index()` and `count()` methods come with any data type that is a `Sequence`, the `append()`, `extend()`, `insert()`, `reverse()`, `pop()`, and `remove()` methods are part of any `MutableSequence` type. The `sort()`, `copy()`, and `clear()` methods are `list`-specific.\n",
"\n",
"So, being a sequence does not only imply the four *behaviors* specified above, but also means that a data type comes with certain standardized methods."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"### List Operations"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"As with `str` objects, the `+` and `*` operators are overloaded for concatenation and always return a *new* `list` object. The references in this newly created `list` object reference the *same* objects as the two original `list` objects. So, the same caveat as with *shallow* copies from above applies!"
]
},
{
"cell_type": "code",
"execution_count": 121,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"['Berthold', 'Oliver', 'Carl']"
]
},
"execution_count": 121,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"names"
]
},
{
"cell_type": "code",
"execution_count": 122,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"['Berthold', 'Oliver', 'Carl', 'Diedrich', 'Yves']"
]
},
"execution_count": 122,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"names + [\"Diedrich\", \"Yves\"]"
]
},
{
"cell_type": "code",
"execution_count": 123,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"['Berthold', 'Oliver', 'Carl', 'Berthold', 'Oliver', 'Carl']"
]
},
"execution_count": 123,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"2 * names"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Besides being an operator, the `*` symbol has a second syntactical use, as explained in [PEP 3132 <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://www.python.org/dev/peps/pep-3132/) and [PEP 448 <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://www.python.org/dev/peps/pep-0448/): It implements what is called **iterable unpacking**. It is *not* an operator syntactically but a notation that Python reads as a literal.\n",
"\n",
"In the example, Python interprets the expression as if the elements of the iterable `names` were placed between `\"Achim\"` and `\"Xavier\"` one by one. So, we do not obtain a nested but a *flat* list."
]
},
{
"cell_type": "code",
"execution_count": 124,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"['Achim', 'Berthold', 'Oliver', 'Carl', 'Xavier']"
]
},
"execution_count": 124,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"[\"Achim\", *names, \"Xavier\"]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Effectively, Python reads that as if we wrote the following."
]
},
{
"cell_type": "code",
"execution_count": 125,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"['Achim', 'Berthold', 'Oliver', 'Carl', 'Xavier']"
]
},
"execution_count": 125,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"[\"Achim\", names[0], names[1], names[2], \"Xavier\"]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"#### List Comparison"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The relational operators also work with `list` objects; yet another example of operator overloading.\n",
"\n",
"Comparison is made in a pairwise fashion until the first pair of elements does not evaluate equal or one of the `list` objects ends. The exact comparison rules depend on the elements and not the `list` objects. As with [sort() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/stdtypes.html#list.sort) or [sorted() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#sorted) above, comparison is *delegated* to the objects to be compared, and Python \"asks\" the elements in the two `list` objects to compare themselves. Usually, all elements are of the *same* type, and comparison is straightforward."
]
},
{
"cell_type": "code",
"execution_count": 126,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"['Berthold', 'Oliver', 'Carl']"
]
},
"execution_count": 126,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"names"
]
},
{
"cell_type": "code",
"execution_count": 127,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 127,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"names == [\"Berthold\", \"Oliver\", \"Carl\"]"
]
},
{
"cell_type": "code",
"execution_count": 128,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 128,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"names != [\"Berthold\", \"Oliver\", \"Karl\"]"
]
},
{
"cell_type": "code",
"execution_count": 129,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 129,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"names < [\"Berthold\", \"Oliver\", \"Karl\"]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"If two `list` objects have a different number of elements and all overlapping elements compare equal, the shorter `list` object is considered \"smaller.\""
]
},
{
"cell_type": "code",
"execution_count": 130,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 130,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"[\"Berthold\", \"Oliver\"] < names < [\"Berthold\", \"Oliver\", \"Carl\", \"Xavier\"]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"### Modifiers vs. Pure Functions"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"As `list` objects are mutable, the caller of a function can see the changes made to a `list` object passed to the function as an argument. That is often a surprising *side effect* and should be avoided.\n",
"\n",
"As an example, consider the `add_xyz()` function."
]
},
{
"cell_type": "code",
"execution_count": 131,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"letters = [\"a\", \"b\", \"c\"]"
]
},
{
"cell_type": "code",
"execution_count": 132,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"def add_xyz(arg):\n",
" \"\"\"Append letters to a list.\"\"\"\n",
" arg.extend([\"x\", \"y\", \"z\"])\n",
" return arg"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"While this function is being executed, two variables, namely `letters` in the global scope and `arg` inside the function's local scope, reference the *same* `list` object in memory. Furthermore, the passed in `arg` is also the return value.\n",
"\n",
"So, after the function call, `letters_with_xyz` and `letters` are **aliases** as well, referencing the *same* object. We can also visualize that with [PythonTutor <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](http://pythontutor.com/visualize.html#code=letters%20%3D%20%5B%22a%22,%20%22b%22,%20%22c%22%5D%0A%0Adef%20add_xyz%28arg%29%3A%0A%20%20%20%20arg.extend%28%5B%22x%22,%20%22y%22,%20%22z%22%5D%29%0A%20%20%20%20return%20arg%0A%0Aletters_with_xyz%20%3D%20add_xyz%28letters%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false)."
]
},
{
"cell_type": "code",
"execution_count": 133,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"letters_with_xyz = add_xyz(letters)"
]
},
{
"cell_type": "code",
"execution_count": 134,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"['a', 'b', 'c', 'x', 'y', 'z']"
]
},
"execution_count": 134,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"letters_with_xyz"
]
},
{
"cell_type": "code",
"execution_count": 135,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"['a', 'b', 'c', 'x', 'y', 'z']"
]
},
"execution_count": 135,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"letters"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"A better practice is to first create a copy of `arg` within the function that is then modified and returned. If we are sure that `arg` contains immutable elements only, we get away with a shallow copy. The downside of this approach is the higher amount of memory necessary.\n",
"\n",
"The revised `add_xyz()` function below is more natural to reason about as it does *not* modify the passed in `arg` internally. [PythonTutor <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](http://pythontutor.com/visualize.html#code=letters%20%3D%20%5B%22a%22,%20%22b%22,%20%22c%22%5D%0A%0Adef%20add_xyz%28arg%29%3A%0A%20%20%20%20new_arg%20%3D%20arg%5B%3A%5D%0A%20%20%20%20new_arg.extend%28%5B%22x%22,%20%22y%22,%20%22z%22%5D%29%0A%20%20%20%20return%20new_arg%0A%0Aletters_with_xyz%20%3D%20add_xyz%28letters%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) shows that as well. This approach is following the **[functional programming <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_wiki.png\">](https://en.wikipedia.org/wiki/Functional_programming)** paradigm that is going through a \"renaissance\" currently. Two essential characteristics of functional programming are that a function *never* changes its inputs and *always* returns the same output given the same inputs.\n",
"\n",
"For a beginner, it is probably better to stick to this idea and not change any arguments as the original `add_xyz()` above. However, functions that modify and return the argument passed in are an important aspect of object-oriented programming, as explained in [Chapter 10 <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/10_classes_00_content.ipynb)."
]
},
{
"cell_type": "code",
"execution_count": 136,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"letters = [\"a\", \"b\", \"c\"]"
]
},
{
"cell_type": "code",
"execution_count": 137,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"def add_xyz(arg):\n",
" \"\"\"Create a new list from an existing one.\"\"\"\n",
" new_arg = arg[:]\n",
" new_arg.extend([\"x\", \"y\", \"z\"])\n",
" return new_arg"
]
},
{
"cell_type": "code",
"execution_count": 138,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"letters_with_xyz = add_xyz(letters)"
]
},
{
"cell_type": "code",
"execution_count": 139,
"metadata": {
"scrolled": true,
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"['a', 'b', 'c', 'x', 'y', 'z']"
]
},
"execution_count": 139,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"letters_with_xyz"
]
},
{
"cell_type": "code",
"execution_count": 140,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"['a', 'b', 'c']"
]
},
"execution_count": 140,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"letters"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"If we want to modify the argument passed in, it is best to return `None` and not `arg`, as does the final version of `add_xyz()` below. Then, the user of our function cannot accidentally create two aliases to the same object. That is also why the list methods above all return `None`. [PythonTutor <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](http://pythontutor.com/visualize.html#code=letters%20%3D%20%5B%22a%22,%20%22b%22,%20%22c%22%5D%0A%0Adef%20add_xyz%28arg%29%3A%0A%20%20%20%20arg.extend%28%5B%22x%22,%20%22y%22,%20%22z%22%5D%29%0A%20%20%20%20return%0A%0Aadd_xyz%28letters%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) shows how there is only *one* reference to `letters` after the function call."
]
},
{
"cell_type": "code",
"execution_count": 141,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"letters = [\"a\", \"b\", \"c\"]"
]
},
{
"cell_type": "code",
"execution_count": 142,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"def add_xyz(arg):\n",
" \"\"\"Append letters to a list.\"\"\"\n",
" arg.extend([\"x\", \"y\", \"z\"])\n",
" return # None"
]
},
{
"cell_type": "code",
"execution_count": 143,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"add_xyz(letters)"
]
},
{
"cell_type": "code",
"execution_count": 144,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"['a', 'b', 'c', 'x', 'y', 'z']"
]
},
"execution_count": 144,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"letters"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"If we call `add_xyz()` with `letters` as the argument again, we end up with an even longer `list` object."
]
},
{
"cell_type": "code",
"execution_count": 145,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"add_xyz(letters)"
]
},
{
"cell_type": "code",
"execution_count": 146,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"['a', 'b', 'c', 'x', 'y', 'z', 'x', 'y', 'z']"
]
},
"execution_count": 146,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"letters"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Functions that only work on the argument passed in are called **modifiers**. Their primary purpose is to change the **state** of the argument. On the contrary, functions that have *no* side effects on the arguments are said to be **pure**."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## The `tuple` Type"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"To create a `tuple` object, we can use the same literal notation as for `list` objects *without* the brackets and list all elements."
]
},
{
"cell_type": "code",
"execution_count": 147,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"numbers = 7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4"
]
},
{
"cell_type": "code",
"execution_count": 148,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"(7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4)"
]
},
"execution_count": 148,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"numbers"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"However, to be clearer, many Pythonistas write out the optional parentheses `(` and `)`."
]
},
{
"cell_type": "code",
"execution_count": 149,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"numbers = (7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4)"
]
},
{
"cell_type": "code",
"execution_count": 150,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"(7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4)"
]
},
"execution_count": 150,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"numbers"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"As before, `numbers` is an object on its own."
]
},
{
"cell_type": "code",
"execution_count": 151,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"140483698604784"
]
},
"execution_count": 151,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"id(numbers)"
]
},
{
"cell_type": "code",
"execution_count": 152,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"tuple"
]
},
"execution_count": 152,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"type(numbers)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"While we could use empty parentheses `()` to create an empty `tuple` object ..."
]
},
{
"cell_type": "code",
"execution_count": 153,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"empty_tuple = ()"
]
},
{
"cell_type": "code",
"execution_count": 154,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"()"
]
},
"execution_count": 154,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"empty_tuple"
]
},
{
"cell_type": "code",
"execution_count": 155,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"tuple"
]
},
"execution_count": 155,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"type(empty_tuple)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"... we must use a *trailing comma* to create a `tuple` object holding one element. If we forget the comma, the parentheses are interpreted as the grouping operator and effectively useless!"
]
},
{
"cell_type": "code",
"execution_count": 156,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"one_tuple = (1,) # we could ommit the parentheses but not the comma"
]
},
{
"cell_type": "code",
"execution_count": 157,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"(1,)"
]
},
"execution_count": 157,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"one_tuple"
]
},
{
"cell_type": "code",
"execution_count": 158,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"tuple"
]
},
"execution_count": 158,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"type(one_tuple)"
]
},
{
"cell_type": "code",
"execution_count": 159,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"no_tuple = (1)"
]
},
{
"cell_type": "code",
"execution_count": 160,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"1"
]
},
"execution_count": 160,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"no_tuple"
]
},
{
"cell_type": "code",
"execution_count": 161,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"int"
]
},
"execution_count": 161,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"type(no_tuple)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Alternatively, we may use the [tuple() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#func-tuple) built-in that takes any iterable as its argument and creates a new `tuple` from its elements."
]
},
{
"cell_type": "code",
"execution_count": 162,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"(1,)"
]
},
"execution_count": 162,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"tuple([1])"
]
},
{
"cell_type": "code",
"execution_count": 163,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"('i', 't', 'e', 'r', 'a', 'b', 'l', 'e')"
]
},
"execution_count": 163,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"tuple(\"iterable\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"### Tuples are like \"Immutable Lists\""
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Most operations involving `tuple` objects work in the same way as with `list` objects. The main difference is that `tuple` objects are *immutable*. So, if our program does not depend on mutability, we may and should use `tuple` and not `list` objects to model sequential data. That way, we avoid the pitfalls seen above.\n",
"\n",
"`tuple` objects are *sequences* exhibiting the familiar *four* behaviors. So, `numbers` holds a *finite* number of elements ..."
]
},
{
"cell_type": "code",
"execution_count": 164,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"12"
]
},
"execution_count": 164,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"len(numbers)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"... that we can obtain individually by looping over it in a predictable *forward* or *reverse* order."
]
},
{
"cell_type": "code",
"execution_count": 165,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"7 11 8 5 3 12 2 6 9 10 1 4 "
]
}
],
"source": [
"for number in numbers:\n",
" print(number, end=\" \")"
]
},
{
"cell_type": "code",
"execution_count": 166,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"4 1 10 9 6 2 12 3 5 8 11 7 "
]
}
],
"source": [
"for number in reversed(numbers):\n",
" print(number, end=\" \")"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"To check if a given object is *contained* in `numbers`, we use the `in` operator and conduct a linear search."
]
},
{
"cell_type": "code",
"execution_count": 167,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 167,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"0 in numbers"
]
},
{
"cell_type": "code",
"execution_count": 168,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 168,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"1 in numbers"
]
},
{
"cell_type": "code",
"execution_count": 169,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 169,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"1.0 in numbers # in relies on == behind the scenes"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"We may index and slice with the `[]` operator. The latter returns *new* `tuple` objects."
]
},
{
"cell_type": "code",
"execution_count": 170,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"7"
]
},
"execution_count": 170,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"numbers[0]"
]
},
{
"cell_type": "code",
"execution_count": 171,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"4"
]
},
"execution_count": 171,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"numbers[-1]"
]
},
{
"cell_type": "code",
"execution_count": 172,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"(2, 6, 9, 10, 1, 4)"
]
},
"execution_count": 172,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"numbers[6:]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Index assignment does *not* work as tuples are *immutable* and results in a `TypeError`."
]
},
{
"cell_type": "code",
"execution_count": 173,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"ename": "TypeError",
"evalue": "'tuple' object does not support item assignment",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-173-80fddf1304f0>\u001b[0m in \u001b[0;36m<module>\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[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m99\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mTypeError\u001b[0m: 'tuple' object does not support item assignment"
]
}
],
"source": [
"numbers[-1] = 99"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The `+` and `*` operators work with `tuple` objects as well: They always create *new* `tuple` objects."
]
},
{
"cell_type": "code",
"execution_count": 174,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"(7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4, 99)"
]
},
"execution_count": 174,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"numbers + (99,) "
]
},
{
"cell_type": "code",
"execution_count": 175,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"(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": 175,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"2 * numbers"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Being immutable, `tuple` objects only provide the `count()` and `index()` methods of `Sequence` types. The `append()`, `extend()`, `insert()`, `reverse()`, `pop()`, and `remove()` methods of `MutableSequence` types are *not* available. The same holds for the `list`-specific methods `sort()`, `copy()`, and `clear()`."
]
},
{
"cell_type": "code",
"execution_count": 176,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"0"
]
},
"execution_count": 176,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"numbers.count(0)"
]
},
{
"cell_type": "code",
"execution_count": 177,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"10"
]
},
"execution_count": 177,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"numbers.index(1)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The relational operators work in the *same* way as for `list` objects."
]
},
{
"cell_type": "code",
"execution_count": 178,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"(7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4)"
]
},
"execution_count": 178,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"numbers"
]
},
{
"cell_type": "code",
"execution_count": 179,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 179,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"numbers == (7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4)"
]
},
{
"cell_type": "code",
"execution_count": 180,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 180,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"numbers != (99, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4)"
]
},
{
"cell_type": "code",
"execution_count": 181,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 181,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"numbers < (99, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"While `tuple` objects are immutable, this only relates to the references they hold. If a `tuple` object contains references to mutable objects, the entire nested structure is *not* immutable as a whole!\n",
"\n",
"Consider the following stylized example `not_immutable`: It contains *three* elements, `1`, `[2, ..., 11]`, and `12`, and the elements of the nested `list` object may be changed. While it is not practical to mix data types in a `tuple` object that is used as an \"immutable list,\" we want to make the point that the mere usage of the `tuple` type does *not* guarantee a nested object to be immutable as a whole."
]
},
{
"cell_type": "code",
"execution_count": 182,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"not_immutable = (1, [2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 12)"
]
},
{
"cell_type": "code",
"execution_count": 183,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"(1, [2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 12)"
]
},
"execution_count": 183,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"not_immutable"
]
},
{
"cell_type": "code",
"execution_count": 184,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"not_immutable[1][:] = [99, 99, 99]"
]
},
{
"cell_type": "code",
"execution_count": 185,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"(1, [99, 99, 99], 12)"
]
},
"execution_count": 185,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"not_immutable"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"### Packing & Unpacking"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"In the \"*List Operations*\" section above, the `*` symbol **unpacks** the elements of a `list` object into another one. This idea of *iterable unpacking* is built into Python at various places, even *without* the `*` symbol.\n",
"\n",
"For example, we may write variables on the left-hand side of a `=` statement in a `tuple` style. Then, any *finite* iterable on the right-hand side is unpacked. So, `numbers` is unpacked into *twelve* variables below."
]
},
{
"cell_type": "code",
"execution_count": 186,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12 = numbers"
]
},
{
"cell_type": "code",
"execution_count": 187,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"7"
]
},
"execution_count": 187,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"n1"
]
},
{
"cell_type": "code",
"execution_count": 188,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"11"
]
},
"execution_count": 188,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"n2"
]
},
{
"cell_type": "code",
"execution_count": 189,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"8"
]
},
"execution_count": 189,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"n3"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Having to type twelve variables on the left is already tedious. Furthermore, if the iterable on the right yields a number of elements *different* from the number of variables, we get a `ValueError`."
]
},
{
"cell_type": "code",
"execution_count": 190,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"ename": "ValueError",
"evalue": "too many values to unpack (expected 11)",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-190-c5e79943681f>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mn1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn3\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn4\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn5\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn6\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn7\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn8\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn9\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn10\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn11\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnumbers\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mValueError\u001b[0m: too many values to unpack (expected 11)"
]
}
],
"source": [
"n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11 = numbers"
]
},
{
"cell_type": "code",
"execution_count": 191,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"ename": "ValueError",
"evalue": "not enough values to unpack (expected 13, got 12)",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-191-214f2de70baf>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mn1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn3\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn4\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn5\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn6\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn7\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn8\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn9\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn10\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn11\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn12\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn13\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnumbers\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mValueError\u001b[0m: not enough values to unpack (expected 13, got 12)"
]
}
],
"source": [
"n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13 = numbers"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"So, to make iterable unpacking useful, we prepend the `*` symbol to *one* of the variables on the left: That variable then becomes a `list` object holding the elements not captured by the other variables. We say that the excess elements from the iterable are **packed** into this variable.\n",
"\n",
"For example, let's get the `first` and `last` element of `numbers` and collect the rest in `middle`."
]
},
{
"cell_type": "code",
"execution_count": 192,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"first, *middle, last = numbers"
]
},
{
"cell_type": "code",
"execution_count": 193,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"7"
]
},
"execution_count": 193,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"first"
]
},
{
"cell_type": "code",
"execution_count": 194,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[11, 8, 5, 3, 12, 2, 6, 9, 10, 1]"
]
},
"execution_count": 194,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"middle # always a list!"
]
},
{
"cell_type": "code",
"execution_count": 195,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"4"
]
},
"execution_count": 195,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"last"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"We already used unpacking before this section without knowing it. Whenever we write a `for`-loop over the [zip() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#zip) built-in, that generates a new `tuple` object in each iteration, which we unpack by listing several loop variables.\n",
"\n",
"So, the `name, position` acts like a left-hand side of an `=` statement and unpacks the `tuple` objects generated from \"zipping\" the `names` list and the `positions` tuple together."
]
},
{
"cell_type": "code",
"execution_count": 196,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"positions = (\"goalkeeper\", \"defender\", \"midfielder\", \"striker\", \"coach\")"
]
},
{
"cell_type": "code",
"execution_count": 197,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Berthold is a goalkeeper\n",
"Oliver is a defender\n",
"Carl is a midfielder\n"
]
}
],
"source": [
"for name, position in zip(names, positions):\n",
" print(name, \"is a\", position)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Without unpacking, [zip() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#zip) generates a series of `tuple` objects."
]
},
{
"cell_type": "code",
"execution_count": 198,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"<class 'tuple'> ('Berthold', 'goalkeeper')\n",
"<class 'tuple'> ('Oliver', 'defender')\n",
"<class 'tuple'> ('Carl', 'midfielder')\n"
]
}
],
"source": [
"for pair in zip(names, positions):\n",
" print(type(pair), pair, sep=\" \")"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Unpacking also works for nested objects. Below, we wrap [zip() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#zip) with the [enumerate() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#enumerate) built-in to have an index variable `number` inside the `for`-loop. In each iteration, a `tuple` object consisting of `number` and another `tuple` object is created. The inner one then holds the `name` and `position`."
]
},
{
"cell_type": "code",
"execution_count": 199,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Berthold (jersey #1) is a goalkeeper\n",
"Oliver (jersey #2) is a defender\n",
"Carl (jersey #3) is a midfielder\n"
]
}
],
"source": [
"for number, (name, position) in enumerate(zip(names, positions), start=1):\n",
" print(f\"{name} (jersey #{number}) is a {position}\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"#### Swapping Variables"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"A popular use case of unpacking is **swapping** two variables.\n",
"\n",
"Consider `a` and `b` below."
]
},
{
"cell_type": "code",
"execution_count": 200,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"a = 0\n",
"b = 1"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Without unpacking, we must use a temporary variable `temp` to swap `a` and `b`."
]
},
{
"cell_type": "code",
"execution_count": 201,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"temp = a\n",
"a = b\n",
"b = temp\n",
"\n",
"del temp"
]
},
{
"cell_type": "code",
"execution_count": 202,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"1"
]
},
"execution_count": 202,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"a"
]
},
{
"cell_type": "code",
"execution_count": 203,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"0"
]
},
"execution_count": 203,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"With unpacking, the solution is more elegant, and also a bit faster as well. *All* expressions on the right-hand side are evaluated *before* any assignment takes place."
]
},
{
"cell_type": "code",
"execution_count": 204,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"a, b = 0, 1"
]
},
{
"cell_type": "code",
"execution_count": 205,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"a, b = b, a"
]
},
{
"cell_type": "code",
"execution_count": 206,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"(1, 0)"
]
},
"execution_count": 206,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"a, b"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"##### Example: [Fibonacci Numbers <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_wiki.png\">](https://en.wikipedia.org/wiki/Fibonacci_number) (revisited)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Unpacking allows us to rewrite the iterative `fibonacci()` function from [Chapter 4 <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration_00_content.ipynb#\"Hard-at-first-Glance\"-Example:-Fibonacci-Numbers-%28revisited%29) in a concise way."
]
},
{
"cell_type": "code",
"execution_count": 207,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"def fibonacci(i):\n",
" \"\"\"Calculate the ith Fibonacci number.\n",
"\n",
" Args:\n",
" i (int): index of the Fibonacci number to calculate\n",
"\n",
" Returns:\n",
" ith_fibonacci (int)\n",
" \"\"\"\n",
" a, b = 0, 1\n",
"\n",
" for _ in range(i - 1):\n",
" a, b = b, a + b\n",
"\n",
" return b"
]
},
{
"cell_type": "code",
"execution_count": 208,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"144"
]
},
"execution_count": 208,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"fibonacci(12)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"#### Function Definitions & Calls"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The concepts of packing and unpacking are also helpful when writing and using functions.\n",
"\n",
"For example, let's look at the `product()` function below. Its implementation suggests that `args` must be a sequence type. Otherwise, it would not make sense to index into it with `[0]` or take a slice with `[1:]`. In line with the function's name, the `for`-loop multiplies all elements of the `args` sequence. So, what does the `*` do in the header line, and what is the exact data type of `args`?\n",
"\n",
"The `*` is again *not* an operator in this context but a special syntax that makes Python *pack* all *positional* arguments passed to `product()` into a single `tuple` object called `args`."
]
},
{
"cell_type": "code",
"execution_count": 209,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"def product(*args):\n",
" \"\"\"Multiply all arguments.\"\"\"\n",
" result = args[0]\n",
"\n",
" for arg in args[1:]:\n",
" result *= arg\n",
"\n",
" return result"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"So, we can pass an *arbitrary* (i.e., also none) number of *positional* arguments to `product()`.\n",
"\n",
"The product of just one number is the number itself."
]
},
{
"cell_type": "code",
"execution_count": 210,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"42"
]
},
"execution_count": 210,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"product(42)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Passing in several numbers works as expected."
]
},
{
"cell_type": "code",
"execution_count": 211,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"100"
]
},
"execution_count": 211,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"product(2, 5, 10)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"However, this implementation of `product()` needs *at least* one argument passed in due to the expression `args[0]` used internally. Otherwise, we see a *runtime* error, namely an `IndexError`. We emphasize that this error is *not* caused in the header line."
]
},
{
"cell_type": "code",
"execution_count": 212,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"ename": "IndexError",
"evalue": "tuple index out of range",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mIndexError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-212-640e0c632b8d>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mproduct\u001b[0m\u001b[0;34m(\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<ipython-input-209-02b0db9fbeab>\u001b[0m in \u001b[0;36mproduct\u001b[0;34m(*args)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mproduct\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;34m\"\"\"Multiply all arguments.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0margs\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 4\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0marg\u001b[0m \u001b[0;32min\u001b[0m \u001b[0margs\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mIndexError\u001b[0m: tuple index out of range"
]
}
],
"source": [
"product()"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Another downside of this implementation is that we can easily generate *semantic* errors: For example, if we pass in an iterable object like the `one_hundred` list, *no* exception is raised. However, the return value is also not a numeric object as we expect. The reason for this is that during the function call, `args` becomes a `tuple` object holding *one* element, which is `one_hundred`, a `list` object. So, we created a nested structure by accident."
]
},
{
"cell_type": "code",
"execution_count": 213,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"one_hundred = [2, 5, 10]"
]
},
{
"cell_type": "code",
"execution_count": 214,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[2, 5, 10]"
]
},
"execution_count": 214,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"product(one_hundred) # a semantic error!"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"This error does not occur if we unpack `one_hundred` upon passing it as the argument."
]
},
{
"cell_type": "code",
"execution_count": 215,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"100"
]
},
"execution_count": 215,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"product(*one_hundred)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"That is the equivalent of writing out the following tedious expression. Yet, that does *not* scale for iterables with many elements in them."
]
},
{
"cell_type": "code",
"execution_count": 216,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"100"
]
},
"execution_count": 216,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"product(one_hundred[0], one_hundred[1], one_hundred[2])"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"In the \"*Packing & Unpacking with Functions*\" [exercise <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences_02_exercises.ipynb#Packing-&-Unpacking-with-Functions) at the end of this chapter, we look at `product()` in more detail.\n",
"\n",
"While we needed to unpack `one_hundred` above to avoid the semantic error, unpacking an argument in a function call may also be a convenience in general. For example, to print the elements of `one_hundred` in one line, we need to use a `for` statement, until now. With unpacking, we get away *without* a loop."
]
},
{
"cell_type": "code",
"execution_count": 217,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[2, 5, 10]\n"
]
}
],
"source": [
"print(one_hundred) # prints the tuple; we do not want that"
]
},
{
"cell_type": "code",
"execution_count": 218,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2 5 10 "
]
}
],
"source": [
"for number in one_hundred:\n",
" print(number, end=\" \")"
]
},
{
"cell_type": "code",
"execution_count": 219,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2 5 10\n"
]
}
],
"source": [
"print(*one_hundred) # replaces the for-loop"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"### The `namedtuple` Type"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Above, we proposed the idea that `tuple` objects are like \"immutable lists.\" Often, however, we use `tuple` objects to represent a **record** of related **fields**. Then, each element has a *semantic* meaning (i.e., a descriptive name).\n",
"\n",
"As an example, think of a spreadsheet with information on students in a course. Each row represents a record and holds all the data associated with an individual student. The columns (e.g., matriculation number, first name, last name) are the fields that may come as *different* data types (e.g., `int` for the matriculation number, `str` for the names).\n",
"\n",
"A simple way of modeling a single student is as a `tuple` object, for example, `(123456, \"John\", \"Doe\")`. A disadvantage of this approach is that we must remember the order and meaning of the elements/fields in the `tuple` object.\n",
"\n",
"An example from a different domain is the representation of $(x, y)$-points in the $x$-$y$-plane. Again, we could use a `tuple` object like `current_position` below to model the point $(4, 2)$."
]
},
{
"cell_type": "code",
"execution_count": 220,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"current_position = (4, 2)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"We implicitly assume that the first element represents the $x$ and the second the $y$ coordinate. While that follows intuitively from convention in math, we should at least add comments somewhere in the code to document this assumption.\n",
"\n",
"A better way is to create a *custom* data type. While that is covered in depth in [Chapter 10 <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/10_classes_00_content.ipynb), the [collections <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/collections.html) module in the [standard library <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/index.html) provides a [namedtuple() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/collections.html#collections.namedtuple) **factory function** that creates \"simple\" custom data types on top of the standard `tuple` type."
]
},
{
"cell_type": "code",
"execution_count": 221,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"from collections import namedtuple"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"[namedtuple() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/collections.html#collections.namedtuple) takes two arguments. The first argument is the name of the data type. That could be different from the variable `Point` we use to refer to the new type, but in most cases it is best to keep them in sync. The second argument is a sequence with the field names as `str` objects. The names' order corresponds to the one assumed in `current_position`."
]
},
{
"cell_type": "code",
"execution_count": 222,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"Point = namedtuple(\"Point\", [\"x\", \"y\"])"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The `Point` object is a so-called **class**. That is what it means if an object is of type `type`. It can be used as a **factory** to create *new* `tuple`-like objects of type `Point`. In a way, [namedtuple() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/collections.html#collections.namedtuple) gives us a way to create our own custom **constructors**."
]
},
{
"cell_type": "code",
"execution_count": 223,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"140483213000128"
]
},
"execution_count": 223,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"id(Point)"
]
},
{
"cell_type": "code",
"execution_count": 224,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"type"
]
},
"execution_count": 224,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"type(Point)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The value of `Point` is just itself in a *literal notation*."
]
},
{
"cell_type": "code",
"execution_count": 225,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"__main__.Point"
]
},
"execution_count": 225,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Point"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"We write `Point(4, 2)` to create a *new* object of type `Point`."
]
},
{
"cell_type": "code",
"execution_count": 226,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"current_position = Point(4, 2)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Now, `current_position` has a somewhat nicer representation. In particular, the coordinates are named `x` and `y`."
]
},
{
"cell_type": "code",
"execution_count": 227,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"Point(x=4, y=2)"
]
},
"execution_count": 227,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"current_position"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"It is *not* a `tuple` any more but an object of type `Point`."
]
},
{
"cell_type": "code",
"execution_count": 228,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"140483698359504"
]
},
"execution_count": 228,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"id(current_position)"
]
},
{
"cell_type": "code",
"execution_count": 229,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"__main__.Point"
]
},
"execution_count": 229,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"type(current_position)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"We use the dot operator `.` to access the defined attributes."
]
},
{
"cell_type": "code",
"execution_count": 230,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"4"
]
},
"execution_count": 230,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"current_position.x"
]
},
{
"cell_type": "code",
"execution_count": 231,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"2"
]
},
"execution_count": 231,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"current_position.y"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"As before, we get an `AttributeError` if we try to access an undefined attribute."
]
},
{
"cell_type": "code",
"execution_count": 232,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"ename": "AttributeError",
"evalue": "'Point' object has no attribute 'z'",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-232-b30d0b930bac>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mcurrent_position\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mz\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mAttributeError\u001b[0m: 'Point' object has no attribute 'z'"
]
}
],
"source": [
"current_position.z"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"`current_position` continues to work like a `tuple` object! That is why we can use `namedtuple` as a replacement for `tuple`. The underlying implementations exhibit the *same* computational efficiencies and memory usages.\n",
"\n",
"For example, we can index into or loop over `current_position` as it is still a sequence with the familiar four properties."
]
},
{
"cell_type": "code",
"execution_count": 233,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"4"
]
},
"execution_count": 233,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"current_position[0]"
]
},
{
"cell_type": "code",
"execution_count": 234,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"2"
]
},
"execution_count": 234,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"current_position[1]"
]
},
{
"cell_type": "code",
"execution_count": 235,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"4\n",
"2\n"
]
}
],
"source": [
"for number in current_position:\n",
" print(number)"
]
},
{
"cell_type": "code",
"execution_count": 236,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2\n",
"4\n"
]
}
],
"source": [
"for number in reversed(current_position):\n",
" print(number)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Because of that it has \"length.\""
]
},
{
"cell_type": "code",
"execution_count": 237,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"2"
]
},
"execution_count": 237,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"len(current_position)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"## TL;DR"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"**Sequences** are an *abstract* concept that summarizes *four* behaviors an object may or may not exhibit. Sequences are\n",
"- **finite** and\n",
"- **ordered**\n",
"- **containers** that we may\n",
"- **loop over**.\n",
"\n",
"Examples are the `list`, `tuple`, but also the `str` types.\n",
"\n",
"Objects that exhibit all behaviors *except* being ordered are referred to as **collections**.\n",
"\n",
"The objects inside a sequence are called its **elements** and may be labeled with a unique **index**, an `int` object in the range $0 \\leq \\text{index} < \\lvert \\text{sequence} \\rvert$.\n",
"\n",
"`list` objects are **mutable**. That means we can change the references to the other objects it contains, and, in particular, re-assign them.\n",
"\n",
"On the contrary, `tuple` objects are like **immutable** lists: We can use them in place of any `list` object as long as we do *not* need to mutate it. Often, `tuple` objects are also used to model **records** of related **fields**."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"## Further Resources"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"A lecture-style **video presentation** of this chapter is integrated below (cf., the [video <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_yt.png\">](https://www.youtube.com/watch?v=nx2sCDoeC3I&list=PL-2JV1G3J10lQ2xokyQowcRJI5jjNfW7f) or the entire [playlist <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_yt.png\">](https://www.youtube.com/playlist?list=PL-2JV1G3J10lQ2xokyQowcRJI5jjNfW7f))."
]
},
{
"cell_type": "code",
"execution_count": 238,
"metadata": {
"scrolled": true,
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAUDBAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChALCAgOCggIDRYNDhERExMTCAsWGBYSGBASExIBBQUFCAcIDwkJDxgVERUWFxcYExMYGBgVFRgWFRYWGBcVGxIaEhMXFRoYGBISFRcVFRUVFRUVFRUVGBUSFxIVFf/AABEIAWgB4AMBIgACEQEDEQH/xAAdAAEAAgIDAQEAAAAAAAAAAAAABgcFCAIDBAEJ/8QAURAAAQQBAgMBCQgRAgUEAgMAAQACAwQRBRIGEyExBxQYIjJBVZTVCBUXUVJhk9QjMzVCU1RxcnN0dYGSsbKz05G0FiQ2YrU0gqGiY3YlQ0X/xAAcAQEAAgMBAQEAAAAAAAAAAAAAAgQBAwUHBgj/xAA9EQABAwIDBQUGBAYBBQEAAAABAAIRAyEEEjEFQVFhcRMVU4GRBiIyocHwFjRysRQ1QlLR4fEjM0NigpL/2gAMAwEAAhEDEQA/ANMkREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREWzPgVcVfj/D/AK1qPs5PAq4q/H+H/WtR9nIi1mRbM+BVxV+P8P8ArWo+zk8Crir8f4f9a1H2ciLWZFsz4FXFX4/w/wCtaj7OTwKuKvx/h/1rUfZyItZkWzPgVcVfj/D/AK1qPs5PAq4q/H+H/WtR9nIi1mRbM+BVxV+P8P8ArWo+zk8Crir8f4f9a1H2ciLWZFsz4FXFX4/w/wCtaj7OTwKuKvx/h/1rUfZyItZkWzPgVcVfj/D/AK1qPs5PAq4q/H+H/WtR9nIi1mRbM+BVxV+P8P8ArWo+zk8Crir8f4f9a1H2ciLWZFsz4FXFX4/w/wCtaj7OTwKuKvx/h/1rUfZyItZkWzPgVcVfj/D/AK1qPs5PAq4q/H+H/WtR9nIi1mRbM+BVxV+P8P8ArWo+zk8Crir8f4f9a1H2ciLWZFsz4FXF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hodQ0EBkkco21dQ8qJ4e0HOodmWhZBgrB0X6DItAPDV4q9H8P+q6j7RTw1eKvR/D/quo+0VhZW/6LQDw1eKvR/D/AKrqPtFPDV4q9H8P+q6j7RRFv+i0A8NXir0fw/6rqPtFPDV4q9H8P+q6j7RRFv8AotAPDV4q9H8P+q6j7RTw1eKvR/D/AKrqPtFEW/6LQDw1eKvR/D/quo+0U8NXir0fw/6rqPtFEW/6LQDw1eKvR/D/AKrqPtFPDV4q9H8P+q6j7RRFv+i0A8NXir0fw/6rqPtFPDV4q9H8P+q6j7RRFv8AotAPDV4q9H8P+q6j7RTw1eKvR/D/AKrqPtFEW/6LQDw1eKvR/D/quo+0U8NXir0fw/6rqPtFEW/6LQDw1eKvR/D/AKrqPtFPDV4q9H8P+q6j7RRFv+i0A8NXir0fw/6rqPtFPDV4q9H8P+q6j7RRFv8AotAPDV4q9H8P+q6j7RTw1eKvR/D/AKrqPtFEW/6LQDw1eKvR/D/quo+0U8NXir0fw/6rqPtFEW/682q/aJ/0Mn9DloT4avFXo/h/1XUfaK4WPdo8UvY9hoaBh7XNOKuo5w4EHGdR7eqItaURERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERf/2Q==\n",
"text/html": [
"\n",
" <iframe\n",
" width=\"60%\"\n",
" height=\"300\"\n",
" src=\"https://www.youtube.com/embed/nx2sCDoeC3I\"\n",
" frameborder=\"0\"\n",
" allowfullscreen\n",
" ></iframe>\n",
" "
],
"text/plain": [
"<IPython.lib.display.YouTubeVideo at 0x7fc4e8f75950>"
]
},
"execution_count": 238,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from IPython.display import YouTubeVideo\n",
"YouTubeVideo(\"nx2sCDoeC3I\", width=\"60%\")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.4"
},
"livereveal": {
"auto_select": "code",
"auto_select_fragment": true,
"scroll": true,
"theme": "serif"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": false,
"sideBar": true,
"skip_h1_title": true,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {
"height": "calc(100% - 180px)",
"left": "10px",
"top": "150px",
"width": "384px"
},
"toc_section_display": false,
"toc_window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 4
}