Streamline previous content

- run grammarly on all notebooks
- add section on short-circuiting in chapter 03
This commit is contained in:
Alexander Hess 2019-10-14 14:09:28 +02:00
parent 6bebe70e24
commit dbc3a67af4
11 changed files with 1566 additions and 1377 deletions

View file

@ -19,11 +19,9 @@
} }
}, },
"source": [ "source": [
"This book is set up to be a *thorough* introduction to programming in [Python](https://www.python.org/).\n", "This book is a *thorough* introduction to programming in [Python](https://www.python.org/).\n",
"\n", "\n",
"It teaches the concepts behind and the syntax of the core Python language as defined by the [Python Software Foundation](https://www.python.org/psf/) in the official [language reference](https://docs.python.org/3/reference/index.html) and introduces additions to the language as distributed with the [standard library](https://docs.python.org/3/library/index.html) that come with every installation.\n", "It teaches the concepts behind and the syntax of the core Python language as defined by the [Python Software Foundation](https://www.python.org/psf/) in the official [language reference](https://docs.python.org/3/reference/index.html). Furthermore, it introduces commonly used functionalities from the [standard library](https://docs.python.org/3/library/index.html) and popular third-party libraries like [numpy](https://www.numpy.org/), [pandas](https://pandas.pydata.org/), [matplotlib](https://matplotlib.org/), and others."
"\n",
"Furthermore, some very popular third-party libraries like [numpy](https://www.numpy.org/), [pandas](https://pandas.pydata.org/), [matplotlib](https://matplotlib.org/), and others are portrayed."
] ]
}, },
{ {
@ -56,7 +54,7 @@
} }
}, },
"source": [ "source": [
"To be suitable for *total beginners*, there are *no* formal prerequisites. It is only expected that the student has:\n", "This book is suitable for *total beginners*, and there are *no* formal prerequisites. The student only needs to have:\n",
"\n", "\n",
"- a *solid* understanding of the **English language**,\n", "- a *solid* understanding of the **English language**,\n",
"- knowledge of **basic mathematics** from high school,\n", "- knowledge of **basic mathematics** from high school,\n",
@ -94,7 +92,7 @@
} }
}, },
"source": [ "source": [
"This includes but is not limited to topics such as:\n", "These include but are not limited to topics such as:\n",
"- linear algebra\n", "- linear algebra\n",
"- statistics & econometrics\n", "- statistics & econometrics\n",
"- data cleaning & wrangling\n", "- data cleaning & wrangling\n",
@ -108,7 +106,7 @@
"- quantitative marketing (e.g., customer segmentation)\n", "- quantitative marketing (e.g., customer segmentation)\n",
"- quantitative supply chain management (e.g., forecasting)\n", "- quantitative supply chain management (e.g., forecasting)\n",
"- management science & decision models\n", "- management science & decision models\n",
"- backend / API / web development (to serve data products to clients)" "- backend/API/web development (to serve data products to clients)"
] ]
}, },
{ {
@ -130,9 +128,9 @@
} }
}, },
"source": [ "source": [
"The term **[data science](https://en.wikipedia.org/wiki/Data_science)** is rather vague and does actually *not* refer to an academic discipline. Instead the term was popularized by the tech industry who also coined non-meaningful job titles such as \"[rockstar](https://www.quora.com/Why-are-engineers-called-rockstars-and-ninjas)\" or \"[ninja developers](https://www.quora.com/Why-are-engineers-called-rockstars-and-ninjas)\". Most *serious* definitions describe the field as being **multi-disciplinary** *integrating* scientific methods, algorithms, and systems thinking to extract knowledge from (structured and unstructured) data *and* also emphasize the importance of **[domain knowledge](https://en.wikipedia.org/wiki/Domain_knowledge)**.\n", "The term **[data science](https://en.wikipedia.org/wiki/Data_science)** is rather vague and does *not* refer to an academic discipline. Instead, the term was popularized by the tech industry, who also coined non-meaningful job titles such as \"[rockstar](https://www.quora.com/Why-are-engineers-called-rockstars-and-ninjas)\" or \"[ninja developers](https://www.quora.com/Why-are-engineers-called-rockstars-and-ninjas).\" Most *serious* definitions describe the field as being **multi-disciplinary** *integrating* scientific methods, algorithms, and systems thinking to extract knowledge from (structured and unstructured) data *and* also emphasize the importance of **[domain knowledge](https://en.wikipedia.org/wiki/Domain_knowledge)**.\n",
"\n", "\n",
"Recently, this integration aspect feeds back into the academic world. The [MIT](https://www.mit.edu/), for example, created the new [Stephen A. Schwarzman College of Computing](http://computing.mit.edu) for [artifical intelligence](https://en.wikipedia.org/wiki/Artificial_intelligence) (with a 1 billion dollar initial investment) where students undergo a \"bilingual\" curriculum with half the classes in quantitative and method-centric fields (like the ones mentioned above) and the other half in domains such as biology, business, chemistry, politics, (art) history, or linguistics (cf., the [official Q&As](http://computing.mit.edu/faq/) or this [NYT article](https://www.nytimes.com/2018/10/15/technology/mit-college-artificial-intelligence.html)). Their strategists see a future where programming skills are just as naturally embedded into every students' studies as are nowadays subjects like calculus, statistics, or academic writing. Then, programming literacy is not just another \"nice to have\" skill but a prerequisite (or an enabler) to understanding more advanced topics in the actual domains studied. This could make teaching easier for top-notch researchers who use a lot of programming in their day-to-day lifes answering big questions: The student and the teacher are then more likely to \"speak\" the same language." "Recently, this integration aspect feeds back into the academic world. The [MIT](https://www.mit.edu/), for example, created the new [Stephen A. Schwarzman College of Computing](http://computing.mit.edu) for [artificial intelligence](https://en.wikipedia.org/wiki/Artificial_intelligence) (with a 1 billion dollar initial investment) where students undergo a \"bilingual\" curriculum with half the classes in quantitative and method-centric fields (like the ones mentioned above) and the other half in domains such as biology, business, chemistry, politics, (art) history, or linguistics (cf., the [official Q&As](http://computing.mit.edu/faq/) or this [NYT article](https://www.nytimes.com/2018/10/15/technology/mit-college-artificial-intelligence.html)). Their strategists see a future where programming skills are just as naturally embedded into every students' curricula as are nowadays subjects like calculus, statistics, or academic writing. Then, programming literacy is not just another \"nice to have\" skill but a prerequisite, or an enabler, to understanding more advanced topics in the actual domains studied. Top-notch researchers who use programming in their day-to-day lives could then teach students more efficiently in their \"language.\""
] ]
}, },
{ {
@ -154,11 +152,11 @@
} }
}, },
"source": [ "source": [
"To follow this book, a working installation of **Python 3.6** or higher is needed.\n", "To \"read\" this book in the most meaningful way, a working installation of **Python 3.6** or higher is needed.\n",
"\n", "\n",
"A popular and beginner friendly way is to install the [Anaconda Distribution](https://www.anaconda.com/distribution/) that not only ships Python and the standard library but comes pre-packaged with a lot of third-party libraries from the so-called \"scientific stack\". Just go to the [download](https://www.anaconda.com/download/) page and install the latest version (i.e., *2019-07* with Python 3.7 at the time of this writing) for your operating system.\n", "A popular and beginner-friendly way is to install the [Anaconda Distribution](https://www.anaconda.com/distribution/) that not only ships Python and the standard library but comes pre-packaged with a lot of third-party libraries from the so-called \"scientific stack.\" Just go to the [download](https://www.anaconda.com/download/) page and install the latest version (i.e., *2019-07* with Python 3.7 at the time of this writing) for your operating system.\n",
"\n", "\n",
"Then, among others, you will find an entry \"Jupyter Notebook\" in your start menu like below. Click on it and a new tab in your web browser will open where you can switch between folders as you could in your computer's default file browser." "Then, among others, you find an entry \"Jupyter Notebook\" in your start menu like below. Click on it to open a new tab in your web browser where you can switch between folders as you could in your computer's default file browser."
] ]
}, },
{ {
@ -180,7 +178,7 @@
} }
}, },
"source": [ "source": [
"To download the materials accompanying this book as a ZIP file, open this [GitHub repository](https://github.com/webartifex/intro-to-python) in a web browser and click on the green \"Clone or download\" button on the right. Then, unpack the ZIP file into a folder of your choosing (ideally somewhere within your personal user folder so that the files show up right away)." "To download the materials accompanying this book as a ZIP file, open this [GitHub repository](https://github.com/webartifex/intro-to-python) in a web browser and click on the green \"Clone or download\" button on the right. Then, unpack the ZIP file into a folder of your choosing (ideally somewhere within your user folder so that the files show up right away)."
] ]
}, },
{ {
@ -202,7 +200,7 @@
} }
}, },
"source": [ "source": [
"Python can also be installed in a \"pure\" way as obtained from its core development team. However, this is somewhat too \"advanced\" for a beginner who would then also be responsible for setting up all the third-party libraries needed to actually view this document. Plus, many of the involveld steps must be done in a [terminal](https://en.wikipedia.org/wiki/Terminal_emulator) window, which tends to be a bit intimidating for most beginners." "Python can also be installed in a \"pure\" way as obtained from its core development team. However, this is somewhat too \"advanced\" for a beginner who would then also be responsible for setting up all the third-party libraries needed to view this document. Plus, many of the involved steps are typed in a [terminal](https://en.wikipedia.org/wiki/Terminal_emulator) window, which tends to be a bit intimidating for most beginners."
] ]
}, },
{ {
@ -224,7 +222,7 @@
} }
}, },
"source": [ "source": [
"For more \"couragous\" beginners wanting to learn how to accomplish this, here is a rough sketch of the aspects to know. First, all Python realeases are available for free on the official [download](https://www.python.org/downloads/) page for any supported operating system. Choose the one you want, then download and install it (cf., the [instruction notes](https://wiki.python.org/moin/BeginnersGuide/Download)). As this only includes core Python and the standard library, the beginner then needs to learn about the [pip](https://pip.pypa.io/en/stable/) module, which is to be used inside a terminal. With the command `python -m pip install jupyter`, all necessary third-party libraries can be installed (cf., more background [here](https://jupyter.readthedocs.io/en/latest/install.html)). However, this would be done in a *system-wide* fashion and is not recommended. Instead, the best practice is to create a so-called **virtual environment** with the [venv](https://docs.python.org/3/library/venv.html) module with which the installed third-party packages are *isolated* on a per-project basis (the command `python -m venv env-name` creates a virtual enviroment called \"env-name\"). This tactic is employed to avoid a situation known as **[dependency hell](https://en.wikipedia.org/wiki/Dependency_hell)** Once created, the virtual environment must then be activated each time before resuming work in each terminal (with the command `source env-name/bin/activate`). While there exist convenience tools that automate parts of this (e.g., [poetry](https://poetry.eustace.io/docs/) or [virtualenvwrapper](https://virtualenvwrapper.readthedocs.io/en/latest/)), it only distracts a beginner from actually studying the Python language. Yet, it is still worthwhile to have heard about these terms and concepts as many online resources often implicitly assume the user to know about them." "For more \"courageous\" beginners wanting to learn how to accomplish this, here is a rough sketch of the aspects to know. First, all Python releases are available for free on the official [download](https://www.python.org/downloads/) page for any supported operating system. Choose the latest one applicable, then download and install it (cf., the [instruction notes](https://wiki.python.org/moin/BeginnersGuide/Download)). As this only includes core Python and the standard library, the beginner then needs to learn about the [pip](https://pip.pypa.io/en/stable/) module. With the command `python -m pip install jupyter`, all necessary third-party libraries are installed (cf., more background [here](https://jupyter.readthedocs.io/en/latest/install.html)). However, this would be done in a *system-wide* fashion and is not recommended. Instead, the best practice is to create a so-called **virtual environment** with the [venv](https://docs.python.org/3/library/venv.html) module with which the installed third-party packages are *isolated* on a per-project basis (the command `python -m venv env-name` creates a virtual environment called \"env-name\"). This tactic is employed to avoid a situation known as **[dependency hell](https://en.wikipedia.org/wiki/Dependency_hell)**. Once created, the virtual environment must then be activated each time before resuming work in each terminal (with the command `source env-name/bin/activate`). While there exist convenience tools that automate parts of this (e.g., [poetry](https://poetry.eustace.io/docs/) or [virtualenvwrapper](https://virtualenvwrapper.readthedocs.io/en/latest/)), it only distracts a beginner from studying the Python language. Yet, it is still worthwhile to have heard about these terms and concepts as many online resources often implicitly assume the user to know about them."
] ]
}, },
{ {
@ -250,9 +248,9 @@
"\n", "\n",
"\"Jupyter\" is an [acronym](https://en.wikipedia.org/wiki/Acronym) derived from the names of the three major programming languages **[Julia](https://julialang.org/)**, **[Python](https://www.python.org)**, and **[R](https://www.r-project.org/)**, all of which play significant roles in the world of data science. The Jupyter Project's idea is to serve as an integrating platform such that different programming languages and software packages can be used together within the same project easily.\n", "\"Jupyter\" is an [acronym](https://en.wikipedia.org/wiki/Acronym) derived from the names of the three major programming languages **[Julia](https://julialang.org/)**, **[Python](https://www.python.org)**, and **[R](https://www.r-project.org/)**, all of which play significant roles in the world of data science. The Jupyter Project's idea is to serve as an integrating platform such that different programming languages and software packages can be used together within the same project easily.\n",
"\n", "\n",
"Furthermore, Jupyter notebooks have become a de-facto standard for communicating and exchanging results in the data science community (both in academia and business) and often provide a more intuitive alternative to terminal based ways of running Python (e.g., the default [Python interpreter](https://docs.python.org/3/tutorial/interpreter.html) as shown above or a more advanced interactive version like [IPython](https://ipython.org/)) or even a full-fledged [Integrated Development Environment](https://en.wikipedia.org/wiki/Integrated_development_environment) (e.g., the commercial [PyCharm](https://www.jetbrains.com/pycharm/) or the free [Spyder](https://github.com/spyder-ide/spyder)).\n", "Furthermore, Jupyter notebooks have become a de-facto standard for communicating and exchanging results in the data science community (both in academia and business) and often provide a more intuitive alternative to terminal-based ways of running Python (e.g., the default [Python interpreter](https://docs.python.org/3/tutorial/interpreter.html) as shown above or a more advanced interactive version like [IPython](https://ipython.org/)) or even a full-fledged [Integrated Development Environment](https://en.wikipedia.org/wiki/Integrated_development_environment) (e.g., the commercial [PyCharm](https://www.jetbrains.com/pycharm/) or the free [Spyder](https://github.com/spyder-ide/spyder)).\n",
"\n", "\n",
"In particular, they allow to mix plain English text with Python code cells. The plain text can be formatted using the [Markdown](https://guides.github.com/features/mastering-markdown/) language and mathematical expressions can be typeset with [LaTeX](https://www.overleaf.com/learn/latex/Free_online_introduction_to_LaTeX_%28part_1%29). Lastly, we can include pictures, plots, and even videos. Because of these features, the notebooks developed for this book come in a self-contained \"tutorial\" style that enables students to learn and review the material on their own." "In particular, they allow mixing plain English text with Python code cells. The plain text can be formatted using the [Markdown](https://guides.github.com/features/mastering-markdown/) language, and mathematical expressions can be typeset with [LaTeX](https://www.overleaf.com/learn/latex/Free_online_introduction_to_LaTeX_%28part_1%29). Lastly, we can include pictures, plots, and even videos. Because of these features, the notebooks developed for this book come in a self-contained \"tutorial\" style that enables students to learn and review the material on their own."
] ]
}, },
{ {
@ -274,15 +272,15 @@
} }
}, },
"source": [ "source": [
"A Jupyter notebook consists of cells that have a type associated with them. So far, only cells of type \"Markdown\" have been used, which is the default way to present (formatted) text.\n", "A Jupyter notebook consists of cells that have a type associated with them. So far, only cells of type \"Markdown\" have been used, which is the default way to present formatted text.\n",
"\n", "\n",
"The next cell below is an example of a \"Code\" cell containing a line of actual Python code: it simply outputs the text \"Hello world\" when executed. To edit an existing code cell, just enter into it with a mouse click. You know that you are \"in\" a code cell when you see the frame of the code cell turn green.\n", "The next cell below is an example of a \"Code\" cell containing a line of actual Python code: it merely outputs the text \"Hello world\" when executed. To edit an existing code cell, enter into it with a mouse click. You know that you are \"in\" a code cell when you see the frame of the code cell turn green.\n",
"\n", "\n",
"Besides this **edit mode** there is also a so-called **command mode** that you can reach by hitting the \"Escape\" key after entering a code cell, which turns the frame's color into blue. Using the Enter\" and \"Escape\" keys you can now switch between the two modes.\n", "Besides this **edit mode**, there is also a so-called **command mode** that you can reach by hitting the \"Escape\" key after entering a code cell, which turns the frame's color blue. Using the \"Enter\" and \"Escape\" keys, you can now switch between the two modes.\n",
"\n", "\n",
"To *execute* (or *run*) a code cell, hold the \"Control\" key and press \"Enter\". Note how you do not go to the subsequent cell. Alternatively, you can hold the \"Shift\" key and press \"Enter\", which executes the cell and places your focus on the next cell right after.\n", "To *execute*, or \"*run*,\" a code cell, hold the \"Control\" key and press \"Enter.\" Note how you do not go to the subsequent cell. Alternatively, you can hold the \"Shift\" key and press \"Enter,\" which executes the cell and places your focus on the next cell right after.\n",
"\n", "\n",
"Similarly, a Markdown cell is also in either the edit or command mode. For example, simply double-click on the text you are just reading, which takes you into edit mode. Now you could change the formatting (e.g., make a word printed in *italics* or **bold** with single or double asterisks) and then \"execute\" the cell to actually render the text as specified.\n", "Similarly, a Markdown cell is also in either edit or command mode. For example, double-click on the text you are just reading, which takes you into edit mode. Now you could change the formatting (e.g., make a word printed in *italics* or **bold** with single or double asterisks) and then \"execute\" the cell to render the text as specified.\n",
"\n", "\n",
"To change a cell's type, choose either \"Code\" or \"Markdown\" in the navigation bar at the top." "To change a cell's type, choose either \"Code\" or \"Markdown\" in the navigation bar at the top."
] ]
@ -316,7 +314,7 @@
} }
}, },
"source": [ "source": [
"Sometimes a code cell starts with an exclamation mark `!`. Then, the Jupyter notebook behaves as if you just typed the following command right into a terminal. The cell below asks `python` to show its version number. This is actually *not* Python code but a command in the [Shell](https://en.wikipedia.org/wiki/Shell_script) language. The `!` is useful to execute short commands without leaving a Jupyter notebook." "Sometimes a code cell starts with an exclamation mark `!`. Then, the Jupyter notebook behaves as if the following command were typed directly into a terminal. The cell below asks `python` to show its version number and is *not* Python code but a command in the [Shell](https://en.wikipedia.org/wiki/Shell_script) language. The `!` is useful to execute short commands without leaving a Jupyter notebook."
] ]
}, },
{ {
@ -359,15 +357,15 @@
} }
}, },
"source": [ "source": [
"In this book *programming* is \"defined\" as:\n", "In this book, *programming* is \"defined\" as:\n",
"\n", "\n",
"- a **structured** way of **problem solving**\n", "- a **structured** way of **problem-solving**\n",
"- by **expressing** the steps of a **computation / process**\n", "- by **expressing** the steps of a **computation/process**\n",
"- and thereby **documenting** the process in a formal way\n", "- and thereby **documenting** the process in a formal way\n",
"\n", "\n",
"Programming is always **concrete** and based on a **particular case**.\n", "Programming is always **concrete** and based on a **particular case**.\n",
"\n", "\n",
"Programming definitely exhibits elements of an **art** or a **craft** as we will hear programmers call code \"beautiful\" or \"ugly\" or talk about the \"expressive\" power of an application." "It exhibits elements of a form of **art** or a **craft** as we hear programmers call code \"beautiful\" or \"ugly\" or talk about the \"expressive\" power of an application."
] ]
}, },
{ {
@ -385,7 +383,7 @@
"- develops and analyses **algorithms** and **data structures**,\n", "- develops and analyses **algorithms** and **data structures**,\n",
"- and **proves** the **correctness** of a program\n", "- and **proves** the **correctness** of a program\n",
"\n", "\n",
"In a sense, a computer scientist does not need to know a programming language to work. In fact, many computer scientists only know how to produce \"ugly\" code in the eyes of professional programmers." "In a sense, a computer scientist does not need to know a programming language to work, and many computer scientists only know how to produce \"ugly\"-looking code in the eyes of professional programmers."
] ]
}, },
{ {
@ -396,7 +394,7 @@
} }
}, },
"source": [ "source": [
"*IT* or *information technology* is a term that has many meanings to many people. Often, it has something to do with hardware and devices both of which are out of scope for programmers and computer scientists. In fact, many people from the two aforementioned fields are more than happy if their printer and internet just work as they do not know a lot more about that than non-technical people." "*IT* or *information technology* is a term that has many meanings to many people. Often, it has something to do with hardware or physical devices, both of which are out of scope for programmers and computer scientists. Many computer scientists and programmers are more than happy if their printer and internet connection work as they often do not know a lot more about that than non-technical people."
] ]
}, },
{ {
@ -434,8 +432,8 @@
"- [Guido van Rossum](https://en.wikipedia.org/wiki/Guido_van_Rossum) (Pythons **[Benevolent Dictator for Life](https://en.wikipedia.org/wiki/Benevolent_dictator_for_life)**) was bored during a week around Christmas 1989 and started Python as a hobby project \"that would keep \\[him\\] occupied\" for some days\n", "- [Guido van Rossum](https://en.wikipedia.org/wiki/Guido_van_Rossum) (Pythons **[Benevolent Dictator for Life](https://en.wikipedia.org/wiki/Benevolent_dictator_for_life)**) was bored during a week around Christmas 1989 and started Python as a hobby project \"that would keep \\[him\\] occupied\" for some days\n",
"- the idea was to create a **general-purpose scripting** language that would allow fast **prototyping** and would **run on every operating system**\n", "- the idea was to create a **general-purpose scripting** language that would allow fast **prototyping** and would **run on every operating system**\n",
"- Python grew through the 90s as van Rossum promoted it via his \"Computer Programming for Everybody\" initiative that had the **goal to encourage a basic level of coding literacy** as an equal knowledge alongside English literacy and math skills\n", "- Python grew through the 90s as van Rossum promoted it via his \"Computer Programming for Everybody\" initiative that had the **goal to encourage a basic level of coding literacy** as an equal knowledge alongside English literacy and math skills\n",
"- to become more independent from its creator the next major version **Python 2** (released in 2000; still in heavy use as of today) was **open-sourced** from the get-go which attracted a **large and global community of programmers** that **contributed** their individual knowledge and best practices in their free time to make Python even better\n", "- to become more independent from its creator the next major version **Python 2** (released in 2000; still in heavy use as of today) was **open-sourced** from the get-go which attracted a **large and global community of programmers** that **contributed** their expertise and best practices in their free time to make Python even better\n",
"- **Python 3** resulted from a major overhaul of the language in 2008 taking into account the **learnings from almost two decades**, streamlining the language, and getting ready for the age of **big data**\n", "- **Python 3** resulted from a significant overhaul of the language in 2008 taking into account the **learnings from almost two decades**, streamlining the language, and getting ready for the age of **big data**\n",
"- the language is named after the sketch comedy group [Monty Python](https://en.wikipedia.org/wiki/Monty_Python)" "- the language is named after the sketch comedy group [Monty Python](https://en.wikipedia.org/wiki/Monty_Python)"
] ]
}, },
@ -458,7 +456,7 @@
} }
}, },
"source": [ "source": [
"Python is a **general-purpose** programming language that allows for **fast development**, is **easy to read**, **open-source**, long established, unifies the knowledge of **hundreds of thousands of experts** around the world, runs on basically every machine, and can handle the complexities of applications involving **big data**." "Python is a **general-purpose** programming language that allows for **fast development**, is **easy to read**, **open-source**, long-established, unifies the knowledge of **hundreds of thousands of experts** around the world, runs on basically every machine, and can handle the complexities of applications involving **big data**."
] ]
}, },
{ {
@ -480,9 +478,9 @@
} }
}, },
"source": [ "source": [
"Couldn't a company like Google, Facebook, or Microsoft come up with a better programming language? The following argument provides hints why this cannot really be the case.\n", "Couldn't a company like Google, Facebook, or Microsoft come up with a better programming language? The following argument provides hints on why this cannot be the case.\n",
"\n", "\n",
"Wouldn't it be weird if professors and scholars of English literature and language studies dictated how we'd have to speak in day-to-day casual conversations or how authors of poesy and novels should use language constructs to achieve a certain type of mood? If you agree with that premise, it makes sense to assume that even programming languages should evolve in a \"natural\" way as users just *use* the language over time and in new and unpredictable contexts making up their own new conventions." "Wouldn't it be weird if professors and scholars of English literature and language studies dictated how we'd have to speak in day-to-day casual conversations or how authors of poesy and novels should use language constructs to achieve a particular type of mood? If you agree with that premise, it makes sense to assume that even programming languages should evolve in a \"natural\" way as users *use* the language over time and in new and unpredictable contexts creating new conventions."
] ]
}, },
{ {
@ -493,7 +491,7 @@
} }
}, },
"source": [ "source": [
"Loose *communities* are the main building block around which open-source software is built. Someone starts a project (like Guido) and makes it free to use for anybody (e.g., on a code-sharing platform like [GitHub](https://github.com/)). People find it useful enough to solve one of their daily problems and start using it. They see how a project could be improved and provide new use cases (via the popularized concept of a \"[pull request](https://help.github.com/articles/about-pull-requests/)\"). The project grows both in lines of code and number of people using it. After a while, people start local user groups to share their same interests and meet on a regular basis (e.g., this is a big market for companies like [Meetup](https://www.meetup.com/) or non-profits like [PyData](https://pydata.org/)). Out of these local and usually monthly meetups grow yearly conferences on the country or even continental level (e.g., the original [PyCon](https://us.pycon.org/) in the US, [EuroPython](https://europython.eu/), or [PyCon.DE](https://de.pycon.org/)). The content presented at these conferences is made publicly available via GitHub and YouTube (e.g., [PyCon 2019](https://www.youtube.com/channel/UCxs2IIVXaEHHA4BtTiWZ2mQ) or [EuroPython](http://europython.tv/)) and serves as references on what people are working on and introductions to the endless number of specialized fields." "Loose *communities* are the primary building block around which open-source software projects are built. Someone, like Guido, starts a project and makes it free to use for anybody (e.g., on a code-sharing platform like [GitHub](https://github.com/)). People find it useful enough to solve one of their daily problems and start using it. They see how a project could be improved and provide new use cases (via the popularized concept of a \"[pull request](https://help.github.com/articles/about-pull-requests/)\"). The project grows both in lines of code and people using it. After a while, people start local user groups to share their same interests and meet regularly (e.g., this is a big market for companies like [Meetup](https://www.meetup.com/) or non-profits like [PyData](https://pydata.org/)). Out of these local and usually monthly meetups grow yearly conferences on the country or even continental level (e.g., the original [PyCon](https://us.pycon.org/) in the US, [EuroPython](https://europython.eu/), or [PyCon.DE](https://de.pycon.org/)). The content presented at these conferences is made publicly available via GitHub and YouTube (e.g., [PyCon 2019](https://www.youtube.com/channel/UCxs2IIVXaEHHA4BtTiWZ2mQ) or [EuroPython](http://europython.tv/)) and serves as references on what people are working on and introductions to the endless number of specialized fields."
] ]
}, },
{ {
@ -504,11 +502,11 @@
} }
}, },
"source": [ "source": [
"While these communities are rather loose and constantly changing, smaller in-groups, often democratically organized and elected (e.g., the Python Software Foundation), take care of, for example, the development of the \"core\" Python language itself.\n", "While these communities are somewhat loose and continuously changing, smaller in-groups, often democratically organized and elected (e.g., the [Python Software Foundation](https://www.python.org/psf/)), take care of, for example, the development of the \"core\" Python language itself.\n",
"\n", "\n",
"Interestingly, Python is just a specification (i.e., a set of rules) as to what is allowed and what not. The current version of Python can always be lookep up in the [Python Language Reference](https://docs.python.org/3/reference/index.html). In order to make changes to that, anyone can make a so-called **[Python Enhancement Proposal](https://www.python.org/dev/peps/)**, or **PEP** for short, where it needs to be specified what exact changes are to be made and argued why that is a good thing to do. These PEPs are reviewed by the [core developers](https://devguide.python.org/coredev/) and anyone interested and are then either accepted, modified, or rejected if, for example, the change introduces internal inconsistencies. This is very similar to the **double blind peer review** established in academia. In fact, many of the contributors held or hold positions in academia, one more indicator of the high quality standards in the Python community. To learn more about PEPs, check out [PEP 1](https://www.python.org/dev/peps/pep-0001/) that describes the entire process.\n", "Interestingly, Python is just a specification (i.e., a set of rules) as to what is allowed and what not. The current version of Python can always be looked up in the [Python Language Reference](https://docs.python.org/3/reference/index.html). To make changes to that, anyone can make a so-called **[Python Enhancement Proposal](https://www.python.org/dev/peps/)**, or **PEP** for short, where it needs to be specified what exact changes are to be made and argued why that is a good thing to do. These PEPs are reviewed by the [core developers](https://devguide.python.org/coredev/) and interested people and are then either accepted, modified, or rejected if, for example, the change introduces internal inconsistencies. This process is similar to the **double-blind peer review** established in academia. Many of the contributors held or hold positions in academia, one more indicator of the high quality standards in the Python community. To learn more about PEPs, check out [PEP 1](https://www.python.org/dev/peps/pep-0001/) that describes the entire process.\n",
"\n", "\n",
"In total, no one single entity can control how the language evolves and the users' needs and ideas always feed back to the language specification via a quality controlled and \"democratic\" process." "In total, no one single entity can control how the language evolves, and the users' needs and ideas always feed back to the language specification via a quality controlled and \"democratic\" process."
] ]
}, },
{ {
@ -519,7 +517,7 @@
} }
}, },
"source": [ "source": [
"Besides being free as in **\"free beer\"**, a major benefit of open-source is that one can always **look up how something works in detail** (that is the literal meaning of *open* source). This is a huge benefit compared to commercial languages (e.g., MATLAB) since a programmer can always continue to **study best practices** or how things are done. Along this way, many **errors are uncovered** as well. Furthermore, if one runs an open-source application, one can be reasonably sure that no bad people built in a \"backdoor\". [Free software](https://en.wikipedia.org/wiki/Free_software) is consequently free of charge but brings many other freedoms with it, most notable the freedom to change the code." "Besides being free as in **\"free beer**,\" a major benefit of open-source is that one can always **look up how something works in detail**: That is the literal meaning of *open* source and different as compared to commercial languages (e.g., MATLAB) since a programmer can always continue to **study best practices** or find out how things are implemented. Along this way, many **errors are uncovered** as well. Furthermore, if one runs an open-source application, one can be reasonably sure that no bad people built in a \"backdoor.\" [Free software](https://en.wikipedia.org/wiki/Free_software) is consequently free of charge but brings many other freedoms with it, most notably the freedom to change the code."
] ]
}, },
{ {
@ -541,13 +539,13 @@
} }
}, },
"source": [ "source": [
"The \"weird\" thing is that the default Python implementation is actually written in the C language.\n", "The \"weird\" thing is that the default Python implementation is written in the C language.\n",
"\n", "\n",
"[C](https://en.wikipedia.org/wiki/C_%28programming_language%29) and [C++](https://en.wikipedia.org/wiki/C%2B%2B) (check this [introduction](https://www.learncpp.com/)) are wide-spread and long established (i.e., since the 1970s) programming languages that are employed in many mission critical softwares (e.g., operating systems themselves, low latency databases and web servers, nuclear reactor control systems, airplanes, ...). They are fast mainly because the programmer not only needs to come up with the **business logic** but also manage the computer's memory \"manually\" (and the knowledge necessary to do that is not easy to learn).\n", "[C](https://en.wikipedia.org/wiki/C_%28programming_language%29) and [C++](https://en.wikipedia.org/wiki/C%2B%2B) (cf., this [introduction](https://www.learncpp.com/)) are wide-spread and long-established (i.e., since the 1970s) programming languages employed in many mission-critical software systems (e.g., operating systems themselves, low latency databases and web servers, nuclear reactor control systems, airplanes, ...). They are fast, mainly because the programmer not only needs to come up with the **business logic** but also manage the computer's memory \"manually\" (and the knowledge necessary to do that is not easy to learn).\n",
"\n", "\n",
"In contrast, Python automatically manages the memory for the programmer. This speeds up the development process a lot. So speed here is really a trade-off of application run time vs. engineering / development time and in many applications the program's run time is not that important (e.g., what if C needs 0.001 seconds in a case where Python needs 0.1 seconds to train a linear regression model?). When the requirements change and computing speed becomes an issue, the Python community offers many third-party libraries (usually also written in C) where specific problems can be solved in near-C time.\n", "In contrast, Python automatically manages the memory for the programmer. So, speed here is a trade-off between application run time and engineering/development time. Often, the program's run time is not that important: For example, what if C needs 0.001 seconds in a case where Python needs 0.1 seconds to train a linear regression model? When the requirements change and computing speed becomes an issue, the Python community offers many third-party libraries (usually also written in C) where specific problems can be solved in near-C time.\n",
"\n", "\n",
"**In a nutshell**: While it is of course true that C is a lot faster than Python when it comes to **pure computation time**, in many cases this does not really matter as the **significantly shorter development cycles** are the more important cost factor in a rapidly changing business world." "**In a nutshell**: While it is, of course, true that C is a lot faster than Python when it comes to **pure computation time**, often, this does not matter as the **significantly shorter development cycles** are the more significant cost factor in a rapidly changing business world."
] ]
}, },
{ {
@ -580,25 +578,23 @@
} }
}, },
"source": [ "source": [
"While it is usually not the best argument to quote authorative figures like the pope, we will do this in this section anyways:\n", "While it is usually not the best argument to quote authoritative figures like the pope, we briefly look at who uses Python here and leave it up to the reader to decide if this is convincing or not:\n",
"\n", "\n",
"- **[Massachusetts Institute of Technology](https://www.mit.edu/)**\n", "- **[Massachusetts Institute of Technology](https://www.mit.edu/)**\n",
" - teaches Python in its [introductory course](https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-0001-introduction-to-computer-science-and-programming-in-python-fall-2016/) to computer science independent of the student's major\n", " - teaches Python in its [introductory course](https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-0001-introduction-to-computer-science-and-programming-in-python-fall-2016/) to computer science independent of the student's major\n",
" - replaced the infamous course on the [Scheme](https://groups.csail.mit.edu/mac/projects/scheme/) language ([source](https://news.ycombinator.com/item?id=602307))\n", " - replaced the infamous course on the [Scheme](https://groups.csail.mit.edu/mac/projects/scheme/) language (cf., [source](https://news.ycombinator.com/item?id=602307))\n",
"- **[Google](https://www.google.com/)**\n", "- **[Google](https://www.google.com/)**\n",
" - used the strategy **\"Python where we can, C++ where we must\"** from its early days on to stay flexible in a rapidly changing environment ([source](https://stackoverflow.com/questions/2560310/heavy-usage-of-python-at-google))\n", " - used the strategy **\"Python where we can, C++ where we must\"** from its early days on to stay flexible in a rapidly changing environment (cf., [source](https://stackoverflow.com/questions/2560310/heavy-usage-of-python-at-google))\n",
" - the very first web-crawler was written in **Java and so difficult** to maintain that it was **re-written in Python** right away ([source](https://www.amazon.com/Plex-Google-Thinks-Works-Shapes/dp/1416596585/ref=sr_1_1?ie=UTF8&qid=1539101827&sr=8-1&keywords=in+the+plex))\n", " - the very first web-crawler was written in **Java and so difficult to maintain** that it was **re-written in Python** right away (cf., [source](https://www.amazon.com/Plex-Google-Thinks-Works-Shapes/dp/1416596585/ref=sr_1_1?ie=UTF8&qid=1539101827&sr=8-1&keywords=in+the+plex))\n",
" - Python and C++, Java, and Go are the only four server-side languages to be deployed to production\n", " - Python and C++, Java, and Go are the only four server-side languages to be deployed to production\n",
" - Guido van Rossom was hired by Google from 2005 to 2012 to advance the language there\n", " - Guido van Rossom was hired by Google from 2005 to 2012 to advance the language there\n",
"- **[NASA](https://www.nasa.gov/)** open-sources a lot of its projects, many of which are written in Python and regard working with really big data ([source](https://code.nasa.gov/language/python/))\n", "- **[NASA](https://www.nasa.gov/)** open-sources many of its projects, often written in Python and regarding analyses with big data (cf., [source](https://code.nasa.gov/language/python/))\n",
"- **[Facebook](https://facebook.com/)** uses Python besides C++ and its legacy PHP (a language for building websites; the \"cool kid\" from the early 2000s)\n", "- **[Facebook](https://facebook.com/)** uses Python besides C++ and its legacy PHP (a language for building websites; the \"cool kid\" from the early 2000s)\n",
"- **[Instagram](https://instagram.com/)** operates the largest installation of the popular **web framework [Django](https://www.djangoproject.com/)** ([source](https://instagram-engineering.com/web-service-efficiency-at-instagram-with-python-4976d078e366))\n", "- **[Instagram](https://instagram.com/)** operates the largest installation of the popular **web framework [Django](https://www.djangoproject.com/)** (cf., [source](https://instagram-engineering.com/web-service-efficiency-at-instagram-with-python-4976d078e366))\n",
"- **[Spotify](https://spotify.com/)** bases its data science on Python ([source](https://labs.spotify.com/2013/03/20/how-we-use-python-at-spotify/))\n", "- **[Spotify](https://spotify.com/)** bases its data science on Python (cf., [source](https://labs.spotify.com/2013/03/20/how-we-use-python-at-spotify/))\n",
"- **[Netflix](https://netflix.com/)** also runs its predictive models on Python ([source](https://medium.com/netflix-techblog/python-at-netflix-86b6028b3b3e))\n", "- **[Netflix](https://netflix.com/)** also runs its predictive models on Python (cf., [source](https://medium.com/netflix-techblog/python-at-netflix-86b6028b3b3e))\n",
"- **[Dropbox](https://dropbox.com/)** \"stole\" Guido van Rossom from Google to help scale the platform ([source](https://medium.com/dropbox-makers/guido-van-rossum-on-finding-his-way-e018e8b5f6b1))\n", "- **[Dropbox](https://dropbox.com/)** \"stole\" Guido van Rossom from Google to help scale the platform (cf., [source](https://medium.com/dropbox-makers/guido-van-rossum-on-finding-his-way-e018e8b5f6b1))\n",
"- **[JPMorgan Chase](https://www.jpmorganchase.com/)** requires new employees to learn Python as part of the onboarding process starting with the 2018 intake ([source](https://www.ft.com/content/4c17d6ce-c8b2-11e8-ba8f-ee390057b8c9?segmentId=a7371401-027d-d8bf-8a7f-2a746e767d56))\n", "- **[JPMorgan Chase](https://www.jpmorganchase.com/)** requires new employees to learn Python as part of the onboarding process starting with the 2018 intake (cf., [source](https://www.ft.com/content/4c17d6ce-c8b2-11e8-ba8f-ee390057b8c9?segmentId=a7371401-027d-d8bf-8a7f-2a746e767d56))"
"- ...\n",
"- ... this list is intentionally shortened as Python is simply very popular ..."
] ]
}, },
{ {
@ -631,7 +627,7 @@
} }
}, },
"source": [ "source": [
"As the graph below shows, neither Google's very own language **[Go](https://golang.org/)** nor **[R](https://www.r-project.org/)**, a very popular language in the niche of statistics and data science, can compete with Python's year-to-year growth." "As the graph below shows, neither Google's very own language **[Go](https://golang.org/)** nor **[R](https://www.r-project.org/)**, a domain-specific language in the niche of statistics, can compete with Python's year-to-year growth."
] ]
}, },
{ {
@ -686,13 +682,13 @@
} }
}, },
"source": [ "source": [
"Simply put, **always be coding**.\n", "**A**lways **b**e **c**oding.\n",
"\n", "\n",
"Programming is more than just writing code into a text file. It means reading through parts of documentation, blogs with best practices, and tutorials, or researching some problem on Stack Overflow while trying to implement some feature in the application at hand. Also, it means using command line tools to automate some part of the work, or manage different versions of a software simultaneously using software like **[git](https://git-scm.com/)**. In short, programming involves a lot of \"muscle memory\". This can only be built and kept up through near-daily usage.\n", "Programming is more than just writing code into a text file. It means reading through parts of the [documentation](https://docs.python.org/), blogs with best practices, and tutorials, or researching problems on Stack Overflow while trying to implement features in the application at hand. Also, it means using command-line tools to automate some part of the work or manage different versions of a program, for example, with **[git](https://git-scm.com/)**. In short, programming involves a lot of \"muscle memory,\" which can only be built and kept up through near-daily usage.\n",
"\n", "\n",
"Further, many aspects of software architecture and best practices can only be understood after having implemented some requirement for the very first time. Coding also means \"breaking\" things to find out what makes them actually work to begin with.\n", "Further, many aspects of software architecture and best practices can only be understood after having implemented some requirements for the very first time. Coding also means \"breaking\" things to find out what makes them work in the first place.\n",
"\n", "\n",
"Coding is learned best by just doing it for some time on a daily or at least regular basis and not right before some task is due, just like learning a \"real\" language." "Coding is learned best by just doing it for some time on a daily or at least a regular basis and not right before some task is due, just like learning a \"real\" language."
] ]
}, },
{ {
@ -716,12 +712,12 @@
"source": [ "source": [
"[Y Combinator](https://www.ycombinator.com/)'s co-founder [Paul Graham](https://en.wikipedia.org/wiki/Paul_Graham_%28programmer%29) wrote a very popular and often cited [article](http://www.paulgraham.com/makersschedule.html) where he divides every person into belonging to one of two groups:\n", "[Y Combinator](https://www.ycombinator.com/)'s co-founder [Paul Graham](https://en.wikipedia.org/wiki/Paul_Graham_%28programmer%29) wrote a very popular and often cited [article](http://www.paulgraham.com/makersschedule.html) where he divides every person into belonging to one of two groups:\n",
"\n", "\n",
"- **Managers**: People that need to organize things and command others (like a \"boss\"). Their schedule is usually organized by the hour or even by 30 minute intervals.\n", "- **Managers**: People that need to organize things and command others (like a \"boss\"). Their schedule is usually organized by the hour or even 30-minute intervals.\n",
"- **Makers**: People that create things (like programmers, artists, or writers). Such people think in half days or full days.\n", "- **Makers**: People that create things (like programmers, artists, or writers). Such people think in half days or full days.\n",
"\n", "\n",
"Have you ever wondered why so many tech nerds work all the nights and sleep during \"weird\" hours? This is mainly because many programming related tasks require a certain \"flow\" state of one's mind. This is hard to achieve when one can get interupted, even if it is only for one short question. Graham describes that only knowing that one has an appointment in three hours can cause a programmer to not get into a flow state.\n", "Have you ever wondered why so many tech people work during nights and sleep at \"weird\" times? The reason is that many programming related tasks require a \"flow\" state in one's mind that is hard to achieve when one can get interrupted, even if it is only for one short question. Graham describes that only knowing that one has an appointment in three hours can cause a programmer to not get into a flow state.\n",
"\n", "\n",
"As a result, do not set aside a certain amount of time for learning something but rather plan in an **entire evening** or a **rainy Sunday** where you can work on a problem in an **open end** setting. And do not be surprised any more to hear a \"I looked at it over the weekend\" from a programmer." "As a result, do not set aside a certain amount of time for learning something but rather plan in an **entire evening** or a **rainy Sunday** where you can work on a problem in an **open end** setting. And do not be surprised anymore to hear \"I looked at it over the weekend\" from a programmer."
] ]
}, },
{ {
@ -743,17 +739,17 @@
} }
}, },
"source": [ "source": [
"When being asked the above question, most programmers will answer something that can be classified into one of two broader groups.\n", "When being asked the above question, most programmers answer something that can be classified into one of two broader groups.\n",
"\n", "\n",
"**1) Toy Problem / Case Study / Prototype**: Pick some problem that you want to write a programatic solution for, break it down into smaller packages, and solve them \"backwards\".\n", "**1) Toy Problem, Case Study, or Prototype**: Pick some problem, break it down into smaller sub-problems, and solve them with an end in mind.\n",
"\n", "\n",
"**2) Books / Video Tutorials / Courses**: Research the best book / blog post / video tutorial for something and work it through from start to end.\n", "**2) Books, Video Tutorials, and Courses**: Research the best book, blog, video, or tutorial for something and work it through from start to end.\n",
"\n", "\n",
"The truth is that you need to iterate between these two phases.\n", "The truth is that you need to iterate between these two phases.\n",
"\n", "\n",
"Building a prototype will always reveal issues no book or tutorial can think of before. Data is never clean as it comes. Some algorithm from a text book must be adapted to a very specific but important aspect of a case study. It is important to learn to \"ship a product\", i.e., to finish some project to the very end because only then you will have looked at all the aspects.\n", "Building a prototype always reveals issues no book or tutorial can think of before. Data is never as clean as it should be. An algorithm from a textbook must be adapted to a peculiar aspect of a case study. It is essential to learn to \"ship a product\" because only then will one have looked at all the aspects.\n",
"\n", "\n",
"The major downside of this approach is that you likely learn bad \"patterns\" as whatever you learn is overfitted to the case and you do not get the big picture or mental concepts behind a solution. This is a gap well written books can fill in (e.g., check the Python / Programming books by [Packt](https://www.packtpub.com/packt/offers/free-learning/) or [OReilly](https://www.oreilly.com/))." "The major downside of this approach is that one likely learns bad \"patterns\" overfitted to the case at hand, and one does not get the big picture or mental concepts behind a solution. This gap can be filled in by well-written books: For example, check the Python/programming books offered by [Packt](https://www.packtpub.com/packt/offers/free-learning/) or [OReilly](https://www.oreilly.com/)."
] ]
}, },
{ {
@ -801,7 +797,7 @@
" 7. Sequences\n", " 7. Sequences\n",
" 8. Mappings & Sets\n", " 8. Mappings & Sets\n",
" 9. Arrays\n", " 9. Arrays\n",
"- How can we create our own data types?\n", "- How can we create custom data types?\n",
" 10. Object-Orientation" " 10. Object-Orientation"
] ]
}, },

View file

@ -53,7 +53,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"**Q2**: Explain what is a *pull request* and elaborate how this concept fits to a *distributed* organization of work!" "**Q2**: Explain what is a *pull request* and elaborate on how this concept fits a *distributed* organization of work!"
] ]
}, },
{ {
@ -81,7 +81,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"**Q4**: What is a major advantage of a \"slow\" programming language like Python over a faster one like C?" "**Q4**: What is a significant advantage of a \"slow\" programming language like Python over a faster one like C?"
] ]
}, },
{ {
@ -109,7 +109,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"**Q5**: Python has been the fastest growing *major* programming language in recent years." "**Q5**: Python has been the fastest-growing *major* programming language in recent years."
] ]
}, },
{ {
@ -137,7 +137,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"**Q7**: Python was originally designed for highly intensive numerical computing, in particular for use cases from physics and astronomy." "**Q7**: Python was initially designed for highly intensive numerical computing, in particular for use cases from physics and astronomy."
] ]
}, },
{ {
@ -151,7 +151,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"**Q8**: JavaScript is a special subset of the Java language." "**Q8**: JavaScript is a subset of the Java language."
] ]
}, },
{ {
@ -165,7 +165,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"**Q9**: Python is *free software*. That means it will never cost anything." "**Q9**: Python is *free software*. That means it does not cost anything."
] ]
}, },
{ {
@ -179,7 +179,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"**Q10**: The main purpose of PEPs is to regulate how code should be documented and/or styled." "**Q10**: The primary purpose of PEPs is to regulate how code should be documented and styled."
] ]
}, },
{ {
@ -228,7 +228,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"**Q12**: The quote \"Education is what remains after one has forgotten what one has learned in school\" is attributed to Albert Einstein. Use a special Markdown syntax to display the author and his quote appropriately!" "**Q12**: The quote \"Education is what remains after one has forgotten what one has learned in school\" is attributed to Albert Einstein. Display the author and his quote appropriately!"
] ]
}, },
{ {

View file

@ -20,11 +20,11 @@
} }
}, },
"source": [ "source": [
"Do you remember how you first learned to speak in your mother tounge? Probably not. No one's memory goes back that far. Your earliest memory as a child should probably be around the age of three or four years old when you could already say simple things and interact with your environment. Although you did not know any grammar rules yet, other people just understood what you said. Well, most of the time.\n", "Do you remember how you first learned to speak in your mother tongue? Probably not. No one's memory goes back that far. Your earliest memory as a child should probably be around the age of three or four years old when you could already say simple things and interact with your environment. Although you did not know any grammar rules yet, other people just understood what you said. Well, most of the time.\n",
"\n", "\n",
"It is intuitively best to take the very mindset of a small child when learing a foreign language and we do so for learning the Python language as well. This first chapter introduces simplistic examples and we better just accept them as they are without knowing any of the \"grammar\" rules yet. Then, we analyze them in parts and slowly build up our understanding.\n", "It is intuitively best to take the very mindset of a small child when learning a foreign language. This first chapter introduces simplistic examples, and we accept them as they are *without* knowing any of the \"grammar\" rules yet. Then, we analyze them in parts and slowly build up our understanding.\n",
"\n", "\n",
"Consequently, if parts of this chapter do not make sense right away, let's not worry too much. Besides introducing some basics that we need to understand, it also serves as an outlook for what is to come. So, many terms and concepts used here will be covered in great detail in following chapters." "Consequently, if parts of this chapter do not make sense right away, let's not worry too much. Besides introducing the basic elements, it also serves as an outlook for what is to come. So, many terms and concepts used here are deconstructed in great detail in the following chapters."
] ]
}, },
{ {
@ -75,7 +75,7 @@
} }
}, },
"source": [ "source": [
"To verify that something happened in our computer's memory, we simply **reference** `numbers`." "To verify that something happened in our computer's memory, we **reference** `numbers`."
] ]
}, },
{ {
@ -116,11 +116,11 @@
"\n", "\n",
"The `if number % 2 == 0` may look disturbing at first sight. Both the `%` and `==` must have an unintuitive meaning here. Luckily, the **comment** in the same line after the `#` symbol has the answer: The program only does something if the current `number` is even.\n", "The `if number % 2 == 0` may look disturbing at first sight. Both the `%` and `==` must have an unintuitive meaning here. Luckily, the **comment** in the same line after the `#` symbol has the answer: The program only does something if the current `number` is even.\n",
"\n", "\n",
"In particular, it increases `count` by $1$ and adds the current `number` onto the [running](https://en.wikipedia.org/wiki/Running_total) `total`. Both `count` and `number` were initially set to $0$ and the single `=` symbol reads as \"... is *set* equal to ...\". It could not indicate a mathematical equation as, for example, `count` is generally not equal to `count + 1`.\n", "In particular, it increases `count` by $1$ and adds the current `number` onto the [running](https://en.wikipedia.org/wiki/Running_total) `total`. Both `count` and `number` are initially set to $0$ and the single `=` symbol reads as \"... is *set* equal to ...\". It could not indicate a mathematical equation as, for example, `count` is generally not equal to `count + 1`.\n",
"\n", "\n",
"Lastly, the `average` is calculated as the ratio of the final **values** of `total` and `count`. Overall, we divide the sum of all even numbers by the count of all even numbers, which is exactly what we are looking for.\n", "Lastly, the `average` is calculated as the ratio of the final **values** of `total` and `count`. Overall, we divide the sum of all even numbers by their count: This is nothing but the definition of an average.\n",
"\n", "\n",
"The lines of code \"within\" the `for` and `if` **statements** are **indented** and *aligned* with multiples of **four spaces**: This shows immediately how the lines relate to each other." "The lines of code \"within\" the `for` and `if` **statements** are **indented** and **aligned** with multiples of **four spaces**: This shows immediately how the lines relate to each other."
] ]
}, },
{ {
@ -133,8 +133,8 @@
}, },
"outputs": [], "outputs": [],
"source": [ "source": [
"count = 0 # initialize variables to keep track of the sum\n", "count = 0 # initialize variables to keep track of the\n",
"total = 0 # so far and the count of the even numbers\n", "total = 0 # running total and the count of even numbers\n",
"\n", "\n",
"for number in numbers:\n", "for number in numbers:\n",
" if number % 2 == 0: # only look at even numbers\n", " if number % 2 == 0: # only look at even numbers\n",
@ -152,7 +152,7 @@
} }
}, },
"source": [ "source": [
"We do not see any **output** yet but obtain the value of `average` by simply referencing it again." "We do not see any **output** yet but obtain the value of `average` by referencing it again."
] ]
}, },
{ {
@ -249,7 +249,7 @@
} }
}, },
"source": [ "source": [
"To visualize something before the end of the cell, we use the [print()](https://docs.python.org/3/library/functions.html#print) built-in **function**." "To visualize something before the end of the cell, we use the built-in [print()](https://docs.python.org/3/library/functions.html#print) **function**. Here, the parentheses `()` indicate that we execute code defined somewhere else."
] ]
}, },
{ {
@ -283,7 +283,7 @@
} }
}, },
"source": [ "source": [
"Outside Jupyter notebooks, the semicolon `;` is actually used as a **seperator** between several statements that would otherwise have to be on a line on their own. However, it as *not* considered good practice to use it as it makes code less readable." "Outside Jupyter notebooks, the semicolon `;` is used as a **separator** between statements that must otherwise be on a line on their own. However, it is *not* considered good practice to use it as it makes code less readable."
] ]
}, },
{ {
@ -327,11 +327,11 @@
} }
}, },
"source": [ "source": [
"Python comes with many **[operators](https://docs.python.org/3/reference/lexical_analysis.html#operators)** built in: They are **tokens** (i.e., \"symbols\") that have a special meaning to the Python interpreter.\n", "Python comes with many built-in **[operators](https://docs.python.org/3/reference/lexical_analysis.html#operators)**: They are **tokens** (i.e., \"symbols\") that have a special meaning to the Python interpreter.\n",
"\n", "\n",
"The arithmetic operators either \"operate\" with the number immediately following them (= **unary** operators; e.g., negation) or \"process\" the two numbers \"around\" them (= **binary** operators; e.g., addition). But we will see many exceptions from that as well.\n", "The arithmetic operators either \"operate\" with the number immediately following them (= **unary** operators; e.g., negation) or \"process\" the two numbers \"around\" them (= **binary** operators; e.g., addition).\n",
"\n", "\n",
"By definition, operators have **no** permanent **side effects** in the computer's memory. Although the code cells in this section do indeed create *new* numbers in memory (e.g., `77 + 13` creates `90`), they are immediately \"forgotten\" as they are not stored in a **variable** like `numbers` or `average` above. We will continue this thought further below when we compare **expressions** with **statements**.\n", "By definition, operators have **no** permanent **side effects** in the computer's memory. Although the code cells in this section do indeed create *new* numbers in memory (e.g., `77 + 13` creates `90`), they are immediately \"forgotten\" as they are not stored in a **variable** like `numbers` or `average` above. We continue this thought further below when we compare **expressions** with **statements**.\n",
"\n", "\n",
"Let's see some examples of operators. We start with the binary `+` and the `-` operators for addition and subtraction. Binary operators are designed to resemble what mathematicians call [infix notation](https://en.wikipedia.org/wiki/Infix_notation) and have the expected meaning." "Let's see some examples of operators. We start with the binary `+` and the `-` operators for addition and subtraction. Binary operators are designed to resemble what mathematicians call [infix notation](https://en.wikipedia.org/wiki/Infix_notation) and have the expected meaning."
] ]
@ -427,7 +427,7 @@
} }
}, },
"source": [ "source": [
"When we compare the output of the `*` and `/` operators for multiplication and division, we note the subtle *difference* between the $42$ and the $42.0$. This is a first illustration of the concept of a **data type**." "When we compare the output of the `*` and `/` operators for multiplication and division, we note the subtle *difference* between the $42$ and the $42.0$: They are the *same* number represented as a *different* **data type**."
] ]
}, },
{ {
@ -486,7 +486,7 @@
} }
}, },
"source": [ "source": [
"The so-called **floor division operator** `//` always \"rounds\" to the next integer and is thus also called **integer division operator**. This is a first example of an operator we commonly do not know from high school mathematics." "The so-called **floor division operator** `//` always \"rounds\" to an integer and is thus also called **integer division operator**. It is an example of an arithmetic operator we commonly do not know from high school mathematics."
] ]
}, },
{ {
@ -615,7 +615,9 @@
} }
}, },
"source": [ "source": [
"Note that the remainder is $0$ *only if* a number is *divisable* by another." "The remainder is $0$ *only if* a number is *divisible* by another.\n",
"\n",
"A popular convention in both, computer science and mathematics, is to abbreviate \"only if\" as **iff**, which is short for \"**[if and only if](https://en.wikipedia.org/wiki/If_and_only_if)**.\" The iff means that a remainder of $0$ implies that a number is divisible by another but also that a number divisible by another implies a remainder of $0$. The implication goes in *both* directions!"
] ]
}, },
{ {
@ -744,7 +746,7 @@
} }
}, },
"source": [ "source": [
"Raising a number to a power is performed with the **exponentiation operator** `**`. This is different from the `^` operator many other programming languages use and that also exists in Python with a *different* meaning." "Raising a number to a power is performed with the **exponentiation operator** `**`. It is different from the `^` operator other programming languages may use and that also exists in Python with a *different* meaning."
] ]
}, },
{ {
@ -779,7 +781,7 @@
} }
}, },
"source": [ "source": [
"The normal [order of precedence](https://docs.python.org/3/reference/expressions.html#operator-precedence) from mathematics applies (i.e., \"PEMDAS\" rule)." "The standard [order of precedence](https://docs.python.org/3/reference/expressions.html#operator-precedence) from mathematics applies (i.e., \"PEMDAS\" rule)."
] ]
}, },
{ {
@ -908,7 +910,7 @@
} }
}, },
"source": [ "source": [
"There are many more non-mathematical operators that are introduced throughout this book together with the concepts they implement. Some of these are already shown in the next section." "There exist many non-mathematical operators that are introduced throughout this book, together with the concepts they implement. They often come in a form different from the unary and binary mentioned above."
] ]
}, },
{ {
@ -932,9 +934,9 @@
"source": [ "source": [
"Python is a so-called **object-oriented** language, which is a paradigm of organizing a program's memory.\n", "Python is a so-called **object-oriented** language, which is a paradigm of organizing a program's memory.\n",
"\n", "\n",
"An **object** can be viewed as a \"bag\" of $0$s and $1$s in a distinct memory location that not only portrayes the idea of a certain **value** but also has some associated rules as to how this value is treated and may be worked with.\n", "An **object** may be viewed as a \"bag\" of $0$s and $1$s in a distinct memory location. The concrete $0$s and $1$s in a bag portray the idea of the object's **value**, and there exist different **types** of bags that each come with associated rules as to how the $0$s and $1$s are interpreted and may be worked with.\n",
"\n", "\n",
"An object *always* has **three** main characteristics. Let's look at the following examples and work them out." "So, an object *always* has **three** main characteristics. Let's look at the following examples and work them out."
] ]
}, },
{ {
@ -986,7 +988,7 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"140098012863472" "139829040614096"
] ]
}, },
"execution_count": 28, "execution_count": 28,
@ -1010,7 +1012,7 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"140098013042536" "139829040789352"
] ]
}, },
"execution_count": 29, "execution_count": 29,
@ -1034,7 +1036,7 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"140098012714672" "139829040474736"
] ]
}, },
"execution_count": 30, "execution_count": 30,
@ -1054,7 +1056,7 @@
} }
}, },
"source": [ "source": [
"These addresses are really not that meaningful for anything other than checking if two variables actually **point** at the same object. This may be helpful as, for example, different objects in memory may of course have the same value." "These addresses are *not* meaningful for anything other than checking if two variables **point** at the same object. Let's create a second variable `d` and also set it to `789`."
] ]
}, },
{ {
@ -1078,7 +1080,7 @@
} }
}, },
"source": [ "source": [
"`a` and `d` indeed have the same value as is checked with the **equality operator** `==`. The resulting `True` (and the `False` below) is yet another data type, a so-called **boolean**. We will look into that closely in [Chapter 3](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/03_conditionals.ipynb)." "`a` and `d` indeed have the same value as is checked with the **equality operator** `==`. The resulting `True` (and the `False` further below) is yet another data type, a so-called **boolean**. We look into them closely in [Chapter 3](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/03_conditionals.ipynb)."
] ]
}, },
{ {
@ -1113,7 +1115,7 @@
} }
}, },
"source": [ "source": [
"On the contrary, `a` and `d` are different objects as the **identity operator** `is` shows: they are stored at seperate addresses in the memory." "On the contrary, `a` and `d` are different objects as the **identity operator** `is` shows: they are stored at separate addresses in the memory."
] ]
}, },
{ {
@ -1220,9 +1222,7 @@
"source": [ "source": [
"Different types imply different behaviors for the objects. The `b` object, for example, can be \"asked\" if it could also be interpreted as an `int` with the [is_integer()](https://docs.python.org/3/library/stdtypes.html#float.is_integer) \"functionality\" that comes with every `float` object.\n", "Different types imply different behaviors for the objects. The `b` object, for example, can be \"asked\" if it could also be interpreted as an `int` with the [is_integer()](https://docs.python.org/3/library/stdtypes.html#float.is_integer) \"functionality\" that comes with every `float` object.\n",
"\n", "\n",
"Formally, we call such type-specific functionalities **methods** (to differentiate them from functions) and we will eventually fully introduce them when we talk about object-orientation in Chapter 10. For now, it suffices to know that we access them using the **dot operator** `.`. Of course `b` could be converted into an `int`, which the boolean value `True` tells us.\n", "Formally, we call such type-specific functionalities **methods** (to differentiate them from functions) and we formally introduce them in Chapter 10. For now, it suffices to know that we access them using the **dot operator** `.`. Of course, `b` could be converted into an `int`, which the boolean value `True` tells us."
"\n",
"Also note how the `.` operator is neiter a unary nor a binary operator as specified above."
] ]
}, },
{ {
@ -1257,7 +1257,7 @@
} }
}, },
"source": [ "source": [
"For an `int` object this [is_integer()](https://docs.python.org/3/library/stdtypes.html#float.is_integer) check does not make sense as we know it is an `int` to begin with. This is why we see the `AttributeError` below as `a` does not even know what `is_integer()` means." "For an `int` object, this [is_integer()](https://docs.python.org/3/library/stdtypes.html#float.is_integer) check does *not* make sense as we already know it is an `int`: We see the `AttributeError` below as `a` does not even know what `is_integer()` means."
] ]
}, },
{ {
@ -1293,7 +1293,7 @@
} }
}, },
"source": [ "source": [
"The `c` object is a so-called **string** type (i.e., `str`), which is Python's way of representing \"text\". Strings also come with their own behaviors, for example, to convert a text to lower or upper case." "The `c` object is a so-called **string** type (i.e., `str`), which is Python's way of representing \"text.\" Strings also come with peculiar behaviors, for example, to convert a text to lower or upper case."
] ]
}, },
{ {
@ -1411,9 +1411,9 @@
} }
}, },
"source": [ "source": [
"Almost trivially, every object also has a value to which it \"evaluates\" when referenced. We think of the value as the **conceptual idea** of what the $0$s and $1$s in memory mean to us humans. A machine does not really see beyond the $0$s and $1$s. At least, not yet.\n", "Almost trivially, every object also has a value to which it \"evaluates\" when referenced. We think of the value as the **conceptual idea** of what the $0$s and $1$s in memory mean to humans as machines cannot see beyond $0$s and $1$s.\n",
"\n", "\n",
"For built-in data types, Python prints out the object's value in a so-called **[literal](https://docs.python.org/3/reference/lexical_analysis.html#literals)** notation. This basically means that we can just copy & paste the output back into a code cell to create a *new* object with the *same* value." "For built-in data types, Python prints out the object's value as a so-called **[literal](https://docs.python.org/3/reference/lexical_analysis.html#literals)**: This means that we can copy and paste the output back into a code cell to create a *new* object with the *same* value."
] ]
}, },
{ {
@ -1472,7 +1472,7 @@
} }
}, },
"source": [ "source": [
"In this book, we follow the convention of creating strings with **double quotes** `\"` instead of the **single quotes** `'` to which Python defaults in its literal output for `str` objects. Both types of quotes may be used interchangebly." "In this book, we follow the convention of creating strings with **double quotes** `\"` instead of the **single quotes** `'` to which Python defaults in its literal output for `str` objects. Both types of quotes may be used interchangeably."
] ]
}, },
{ {
@ -1544,7 +1544,7 @@
"source": [ "source": [
"If we do not follow the rules, the code cannot be **parsed** correctly, i.e., the program does not even start to run but **raises** a **syntax error** indicated as `SyntaxError` in the output. Computers are very dumb in the sense that the slightest syntax error leads to the machine not understanding our code.\n", "If we do not follow the rules, the code cannot be **parsed** correctly, i.e., the program does not even start to run but **raises** a **syntax error** indicated as `SyntaxError` in the output. Computers are very dumb in the sense that the slightest syntax error leads to the machine not understanding our code.\n",
"\n", "\n",
"For example, if we wanted to write an accounting program that adds up currencies, we would have to model dollar prices as `float` objects as the dollar symbol cannot be read by Python." "For example, if we wanted to write an accounting program that adds up currencies, we would have to model dollar prices as `float` objects as the dollar symbol cannot be understood by Python."
] ]
}, },
{ {
@ -1577,7 +1577,7 @@
} }
}, },
"source": [ "source": [
"Python requires certain symbols at certain places (e.g., a `:` is missing here) ..." "Python requires certain symbols at certain places (e.g., a `:` is missing here)."
] ]
}, },
{ {
@ -1611,7 +1611,7 @@
} }
}, },
"source": [ "source": [
"... and relies on whitespace (i.e., indentation) unlike many other programming languages. An `IndentationError` is just a special type of a `SyntaxError`." "Furthermore, it relies on whitespace (i.e., indentation), unlike many other programming languages. The `IndentationError` below is just a particular type of a `SyntaxError`."
] ]
}, },
{ {
@ -1656,7 +1656,7 @@
} }
}, },
"source": [ "source": [
"Syntax errors as above are easy to find as the code will not even run to begin with.\n", "Syntax errors are easy to find as the code does not even run in the first place.\n",
"\n", "\n",
"However, there are also so-called **runtime errors**, often called **exceptions**, that occur whenever otherwise (i.e., syntactically) correct code does not run because of invalid input.\n", "However, there are also so-called **runtime errors**, often called **exceptions**, that occur whenever otherwise (i.e., syntactically) correct code does not run because of invalid input.\n",
"\n", "\n",
@ -1709,7 +1709,7 @@
"source": [ "source": [
"So-called **semantic errors**, on the contrary, are hard to spot as they do *not* crash the program. The only way to find such errors is to run a program with test input for which we can predict the output. However, testing software is a whole discipline on its own and often very hard to do in practice.\n", "So-called **semantic errors**, on the contrary, are hard to spot as they do *not* crash the program. The only way to find such errors is to run a program with test input for which we can predict the output. However, testing software is a whole discipline on its own and often very hard to do in practice.\n",
"\n", "\n",
"The cell below copies our introductory example from above with a \"tiny\" error. How fast could you have spotted it without the comment?" "The cell below copies our first example from above with a \"tiny\" error. How fast could you have spotted it without the comment?"
] ]
}, },
{ {
@ -1766,7 +1766,7 @@
} }
}, },
"source": [ "source": [
"Finding errors in a systematic way is called **debugging**. For the history of the term, see this [article](https://en.wikipedia.org/wiki/Debugging)." "Systematically finding errors is called **debugging**. For the history of the term, see this [article](https://en.wikipedia.org/wiki/Debugging)."
] ]
}, },
{ {
@ -1788,7 +1788,7 @@
} }
}, },
"source": [ "source": [
"Adhering to just syntax rules is therefore *never* enough. Over time, **best practices** and common **style guides** were created to make it less likely for a developer to mess up a program and also to allow \"onboarding\" him as a contributor to an established code base, often called **legacy code**, faster. These rules are not enforced by Python itself: Badly styled and un-readable code will still run. At the very least, Python programs should be styled according to [PEP 8](https://www.python.org/dev/peps/pep-0008/) and documented \"inline\" (i.e., in the code itself) according to [PEP 257](https://www.python.org/dev/peps/pep-0257/).\n", "Thus, adhering to just syntax rules is *never* enough. Over time, **best practices** and **style guides** were created to make it less likely for a developer to mess up a program and also to allow \"onboarding\" him as a contributor to an established code base, often called **legacy code**, faster. These rules are not enforced by Python itself: Badly styled code still runs. At the very least, Python programs should be styled according to [PEP 8](https://www.python.org/dev/peps/pep-0008/) and documented \"inline\" (i.e., in the code itself) according to [PEP 257](https://www.python.org/dev/peps/pep-0257/).\n",
"\n", "\n",
"An easier to read version of PEP 8 is [here](https://pep8.org/). The video below features a well known \"[Pythonista](https://en.wiktionary.org/wiki/Pythonista)\" talking about the importance of code style." "An easier to read version of PEP 8 is [here](https://pep8.org/). The video below features a well known \"[Pythonista](https://en.wiktionary.org/wiki/Pythonista)\" talking about the importance of code style."
] ]
@ -1817,7 +1817,7 @@
" " " "
], ],
"text/plain": [ "text/plain": [
"<IPython.lib.display.YouTubeVideo at 0x7f6b1c40e8d0>" "<IPython.lib.display.YouTubeVideo at 0x7f2c7c4206d8>"
] ]
}, },
"execution_count": 51, "execution_count": 51,
@ -1838,7 +1838,7 @@
} }
}, },
"source": [ "source": [
"For example, while the above code to calculate the average of the even numbers from 1 through 10 is correct, a Pythonista would re-write it in a more \"Pythonic\" way and use the [sum()](https://docs.python.org/3/library/functions.html#sum) and [len()](https://docs.python.org/3/library/functions.html#len) (= \"length\") built-in functions (cf., [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb)) as well as a so-called **list comprehension** (cf., Chapter 7). Pythonic code runs faster in many cases and is less error prone." "For example, while the above code to calculate the average of the even numbers from 1 through 10 is correct, a Pythonista would re-write it in a more \"Pythonic\" way and use the [sum()](https://docs.python.org/3/library/functions.html#sum) and [len()](https://docs.python.org/3/library/functions.html#len) (= \"length\") built-in functions (cf., [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb)) as well as a so-called **list comprehension** (cf., Chapter 7). Pythonic code runs faster in many cases and is less error-prone."
] ]
}, },
{ {
@ -1936,7 +1936,7 @@
} }
}, },
"source": [ "source": [
"To get a rough overview of the mindsets of a typical Python programmer, check these rules by an early Python core developer that are deemed so important that they are actually included in every Python installation." "To get a rough overview of the mindsets of a typical Python programmer, see these rules by an early Python core developer deemed so important that they are included in every Python installation."
] ]
}, },
{ {
@ -2012,7 +2012,7 @@
"source": [ "source": [
"Observe that you can run the code cells in a Jupyter notebook in any arbitrary order.\n", "Observe that you can run the code cells in a Jupyter notebook in any arbitrary order.\n",
"\n", "\n",
"That means, for example, that a variable defined towards the bottom could accidently be referenced at the top of the notebook. This happens easily if we iteratively built a program and go back and forth between cells.\n", "That means, for example, that a variable defined towards the bottom could accidentally be referenced at the top of the notebook. This happens quickly when we iteratively built a program and go back and forth between cells.\n",
"\n", "\n",
"As a good practice, it is recommended to click on \"Kernel\" > \"Restart & Run All\" in the navigation bar once a notebook is finished. That restarts the Python process forgetting any **state** (i.e., all variables) and ensures that the notebook runs top to bottom without any errors the next time it is opened." "As a good practice, it is recommended to click on \"Kernel\" > \"Restart & Run All\" in the navigation bar once a notebook is finished. That restarts the Python process forgetting any **state** (i.e., all variables) and ensures that the notebook runs top to bottom without any errors the next time it is opened."
] ]
@ -2036,11 +2036,11 @@
} }
}, },
"source": [ "source": [
"While this book is built with Jupyter notebooks, it is important to understand that \"real\" programs are almost always just \"linear\" (= top to bottom) sequences of instructions but instead may take many different **flows of execution**.\n", "While this book is built with Jupyter notebooks, it is crucial to understand that \"real\" programs are almost always just \"linear\" (= top to bottom) sequences of instructions but instead may take many different **flows of execution**.\n",
"\n", "\n",
"At the same time, for a beginner's course it is often easier to just code in a linear fashion.\n", "At the same time, for a beginner's course, it is often easier to code linearly.\n",
"\n", "\n",
"In real data science projects one would probably employ a mixed approach and put re-usable code into so-called Python modules (i.e., *.py* files; cf., [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb)) and then use Jupyter notebooks to built up a linear report or story line for a business argument to be made." "In real data science projects, one would probably employ a mixed approach and put re-usable code into so-called Python modules (i.e., *.py* files; cf., [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb)) and then use Jupyter notebooks to build up a linear report or storyline for a business argument to be made."
] ]
}, },
{ {
@ -2064,7 +2064,7 @@
"source": [ "source": [
"**Variables** are created with the **[assignment statement](https://docs.python.org/3/reference/simple_stmts.html#assignment-statements)** `=`, which is *not* an operator, mainly because of its side effect of making a **[name](https://docs.python.org/3/reference/lexical_analysis.html#identifiers)** point to an object in memory.\n", "**Variables** are created with the **[assignment statement](https://docs.python.org/3/reference/simple_stmts.html#assignment-statements)** `=`, which is *not* an operator, mainly because of its side effect of making a **[name](https://docs.python.org/3/reference/lexical_analysis.html#identifiers)** point to an object in memory.\n",
"\n", "\n",
"We will read the terms **variable**, **name**, and **identifier** used interchangebly in many Python related texts. In this book, we adopt the following convention: First, we treat *name* and *identifier* as perfect synonyms but only use the term *name* in the text for clarity. Second, whereas *name* only refers to a string of letters, numbers, and some other symbols, a *variable* refers to the combination of a *name* and a *pointer* to some object in memory." "We read the terms **variable**, **name**, and **identifier** used interchangebly in many Python-related texts. In this book, we adopt the following convention: First, we treat *name* and *identifier* as perfect synonyms but only use the term *name* in the text for clarity. Second, whereas *name* only refers to a string of letters, numbers, and some other symbols, a *variable* refers to the combination of a *name* and a *pointer* to some object in memory."
] ]
}, },
{ {
@ -2089,7 +2089,7 @@
} }
}, },
"source": [ "source": [
"When referenced, a variable evaluates to the value of the object it points to. Colloquially, we could say that `a` evaluates to `20.0` here but this would not be a full description of what is really going on in memory. We will see some more colloquial jargons in this section but should always relate this to what Python actually does in memory." "When referenced, a variable evaluates to the value of the object it points to. Colloquially, we could say that `a` evaluates to `20.0`, but this would not be an accurate description of what is going on in memory. We see some more colloquialisms in this section but should always relate this to what Python actually does in memory."
] ]
}, },
{ {
@ -2172,7 +2172,7 @@
} }
}, },
"source": [ "source": [
"If we want to re-assign a variable while referencing its \"old\" (i.e., current) object, we may also **update** it using a so-called **[augmented assignment statement](https://docs.python.org/3/reference/simple_stmts.html#augmented-assignment-statements)** (*not* operator), originally introduced with [PEP 203](https://www.python.org/dev/peps/pep-0203/). This implicitly inserts the currently mapped object as the first operand on the right-hand side." "If we want to re-assign a variable while referencing its \"old\" (i.e., current) object, we may also **update** it using a so-called **[augmented assignment statement](https://docs.python.org/3/reference/simple_stmts.html#augmented-assignment-statements)** (*not* operator), introduced with [PEP 203](https://www.python.org/dev/peps/pep-0203/): The currently mapped object is implicitly inserted as the first operand on the right-hand side."
] ]
}, },
{ {
@ -2246,7 +2246,7 @@
} }
}, },
"source": [ "source": [
"Variables are **[de-referenced](https://docs.python.org/3/reference/simple_stmts.html#the-del-statement)** (i.e., \"deleted\") with the `del` statement. This does *not* delete the object to which a variable points to. It merely removes the variable's name from the \"global list of all names\"." "Variables are **[de-referenced](https://docs.python.org/3/reference/simple_stmts.html#the-del-statement)** (i.e., \"deleted\") with the `del` statement. It does *not* delete the object to which a variable points to but merely removes the variable's name from the \"global list of all names.\""
] ]
}, },
{ {
@ -2294,7 +2294,7 @@
} }
}, },
"source": [ "source": [
"If we refer to an unknown name, a *runtime* error occurs, namely a `NameError`. The `Name` in `NameError` gives a hint as to why we prefer the term *name* over *identifier*: Python just uses it more often in its error messages." "If we refer to an unknown name, a *runtime* error occurs, namely a `NameError`. The `Name` in `NameError` gives a hint as to why we prefer the term *name* over *identifier*: Python uses it more often in its error messages."
] ]
}, },
{ {
@ -2557,9 +2557,9 @@
} }
}, },
"source": [ "source": [
"It is important to understand that *several* variables may point to the *same* object in memory. This may be counter-intuitive in the beginning and lead to many hard to track down bugs.\n", "It is *crucial* to understand that *several* variables may point to the *same* object in memory. Not having this in mind may lead to many hard to track down bugs.\n",
"\n", "\n",
"This makes `b` point to whatever object `a` is pointing to." "Make `b` point to whatever object `a` is pointing to."
] ]
}, },
{ {
@ -2631,9 +2631,9 @@
} }
}, },
"source": [ "source": [
"For \"simple\" types like `int` or `float` this will never cause troubles.\n", "For \"simple\" types like `int` or `float` this never causes troubles.\n",
"\n", "\n",
"Let's \"change the value\" of `a`. Really, let's create a *new* `123` object and make `a` point to it." "Let's \"change the value\" of `a`. To be precise, let's create a *new* `123` object and make `a` point to it."
] ]
}, },
{ {
@ -2681,7 +2681,7 @@
} }
}, },
"source": [ "source": [
"`b` \"is still the same\" as before. Really, `b` still points to the same object as before." "`b` \"is still the same\" as before. To be precise, `b` still points to the *same object* as before."
] ]
}, },
{ {
@ -2779,7 +2779,7 @@
"source": [ "source": [
"Let's change the first element of `x`.\n", "Let's change the first element of `x`.\n",
"\n", "\n",
"Chapter 7 discusses lists in more depth. For now, let's just view a `list` object as some sort of **container** that holds an arbitrary number of pointers to other objects and treat the brackets `[]` attached to it as just another operator, called the **indexing operator**. `x[0]` instructs Python to first follow the pointer from the global list of all names to the `x` object. Then, it follows the first pointer it finds there to the `1` object. The indexing operator must be an operator as we merely read the first element and do not change anything in memory.\n", "Chapter 7 discusses lists in more depth. For now, let's view a `list` object as some sort of **container** that holds an arbitrary number of pointers to other objects and treat the brackets `[]` attached to it as just another operator, called the **indexing operator**. `x[0]` instructs Python to first follow the pointer from the global list of all names to the `x` object. Then, it follows the first pointer it finds there to the `1` object. The indexing operator must be an operator as we merely read the first element and do not change anything in memory.\n",
"\n", "\n",
"Note how Python **begins counting at 0**. This is not the case for many other languages, for example, [MATLAB](https://en.wikipedia.org/wiki/MATLAB), [R](https://en.wikipedia.org/wiki/R_%28programming_language%29), or [Stata](https://en.wikipedia.org/wiki/Stata). To understand why this makes sense, see this short [note](https://www.cs.utexas.edu/users/EWD/transcriptions/EWD08xx/EWD831.html) by one of the all-time greats in computer science, the late [Edsger Dijkstra](https://en.wikipedia.org/wiki/Edsger_W._Dijkstra)." "Note how Python **begins counting at 0**. This is not the case for many other languages, for example, [MATLAB](https://en.wikipedia.org/wiki/MATLAB), [R](https://en.wikipedia.org/wiki/R_%28programming_language%29), or [Stata](https://en.wikipedia.org/wiki/Stata). To understand why this makes sense, see this short [note](https://www.cs.utexas.edu/users/EWD/transcriptions/EWD08xx/EWD831.html) by one of the all-time greats in computer science, the late [Edsger Dijkstra](https://en.wikipedia.org/wiki/Edsger_W._Dijkstra)."
] ]
@ -2816,7 +2816,7 @@
} }
}, },
"source": [ "source": [
"To change the first entry in the list, we use the assignment statement `=` again. Here, this does actually *not* create a *new* variable (or overwrite an existing one) but only changes the object to which the first pointer in the `x` list points to. As we only change parts of the `x` object, we say that we **mutate** (i.e., \"change\") its **state**. To use the bag analogy from above, we keep the same bag but \"flip\" some of the $0$s into $1$s and some of the $1$s into $0$s." "To change the first entry in the list, we use the assignment statement `=` again. Here, this does *not* create a *new* variable (or overwrite an existing one) but only changes the object to which the first pointer in the `x` list points to. As we only change parts of the `x` object, we say that we **mutate** (i.e., \"change\") its **state**. To use the bag analogy from above, we keep the same bag but \"flip\" some of the $0$s into $1$s and some of the $1$s into $0$s."
] ]
}, },
{ {
@ -2901,11 +2901,11 @@
"source": [ "source": [
"The illustrated difference in behavior has to do with the fact that integers and floats are **immutable** types while lists are **mutable**.\n", "The illustrated difference in behavior has to do with the fact that integers and floats are **immutable** types while lists are **mutable**.\n",
"\n", "\n",
"In the first case, an object cannot be changed \"in place\" once it is created in memory. When we assigned `123` to the already existing `a`, we actually did not change the $0$s and $1$s in the object `a` pointed to before the assignment but created a new integer object and made `a` point to it while the `b` variable is *not* affected.\n", "In the first case, an object cannot be changed \"in place\" once it is created in memory. When we assigned `123` to the already existing `a`, we did not change the $0$s and $1$s in the object `a` pointed to before the assignment but created a new integer object and made `a` point to it while the `b` variable is *not* affected.\n",
"\n", "\n",
"In the second case, `x[0] = 99` creates a *new* integer object `99` and merely changes the first pointer in the `x` list.\n", "In the second case, `x[0] = 99` creates a *new* integer object `99` and merely changes the first pointer in the `x` list.\n",
"\n", "\n",
"In general, the assignment statement creates (or overwrites) a variable and makes it point to whatever object is on the right-hand side *only if* the left-hand side is a *pure* name (i.e., it contains no operators like the indexing operator in the example). Otherwise, it mutates some already existing object. And we always have to expect that the latter might have more than one variable pointing at it.\n", "In general, the assignment statement creates a new name and makes it point to whatever object is on the right-hand side *iff* the left-hand side is a *pure* name (i.e., it contains no operators like the indexing operator in the example). Otherwise, it *mutates* an already existing object. And, we always must expect that the latter might have more than one variable pointing to it.\n",
"\n", "\n",
"Visualizing what is going on in the memory with a tool like [PythonTutor](http://pythontutor.com/visualize.html#code=x%20%3D%20%5B1,%202,%203%5D%0Ay%20%3D%20x%0Ax%5B0%5D%20%3D%2099%0Aprint%28y%5B0%5D%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) might be helpful for a beginner." "Visualizing what is going on in the memory with a tool like [PythonTutor](http://pythontutor.com/visualize.html#code=x%20%3D%20%5B1,%202,%203%5D%0Ay%20%3D%20x%0Ax%5B0%5D%20%3D%2099%0Aprint%28y%5B0%5D%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) might be helpful for a beginner."
] ]
@ -2944,7 +2944,7 @@
"source": [ "source": [
"Variable names may contain upper and lower case letters, numbers, and underscores (i.e., `_`) and be as long as we want them to be. However, they must not begin with a number. Also, they must not be any of Python's built-in **[keywords](https://docs.python.org/3/reference/lexical_analysis.html#keywords)**.\n", "Variable names may contain upper and lower case letters, numbers, and underscores (i.e., `_`) and be as long as we want them to be. However, they must not begin with a number. Also, they must not be any of Python's built-in **[keywords](https://docs.python.org/3/reference/lexical_analysis.html#keywords)**.\n",
"\n", "\n",
"Variable names are usually chosen such that they do not need any more documentation and are self-explanatory. A very common convention is to use so-called **[snake\\_case](https://en.wikipedia.org/wiki/Snake_case)**: Keep everything lowercase and use underscores to seperate words.\n", "Variable names should be chosen such that they do not need any more documentation and are self-explanatory. A widespread convention is to use so-called **[snake\\_case](https://en.wikipedia.org/wiki/Snake_case)**: Keep everything lowercase and use underscores to separate words.\n",
"\n", "\n",
"See this [link](https://en.wikipedia.org/wiki/Naming_convention_%28programming%29#Python_and_Ruby) for a comparison of different naming conventions." "See this [link](https://en.wikipedia.org/wiki/Naming_convention_%28programming%29#Python_and_Ruby) for a comparison of different naming conventions."
] ]
@ -3092,7 +3092,7 @@
} }
}, },
"source": [ "source": [
"If a variable name collides with a built-in name, just add a trailing underscore." "If a variable name collides with a built-in name, we add a trailing underscore."
] ]
}, },
{ {
@ -3116,7 +3116,7 @@
} }
}, },
"source": [ "source": [
"Variables with leading and trailing double underscores, referred to as **dunder** in Python jargon, are used for important built-in functionalities. Do *not* use this style for custom variables unless you know exactly what you are doing!" "Variables with leading and trailing double underscores, referred to as **dunder** in Python jargon, are used for built-in functionalities. Do *not* use this style for custom variables unless you exactly know what you are doing!"
] ]
}, },
{ {
@ -3162,7 +3162,7 @@
} }
}, },
"source": [ "source": [
"This PyCon talk by [Ned Batchelder](https://nedbatchelder.com/), a well-known Pythonista and the organizer of the [Python User Group](https://www.meetup.com/bostonpython/) in Boston, summarizes all situations where some sort of variable assignment is done in Python. The content is intermediate and therefore it is ok if you do not understand everything at this point. However, the contents should be known by everyone claiming to be proficient in Python." "This PyCon talk by [Ned Batchelder](https://nedbatchelder.com/), a well-known Pythonista and the organizer of the [Python User Group](https://www.meetup.com/bostonpython/) in Boston, summarizes all situations where some sort of assignment is done in Python. The content is intermediate, and, thus, it might be worthwhile to come back to this talk at a later point in time. However, the contents should be known by everyone claiming to be proficient in Python."
] ]
}, },
{ {
@ -3189,7 +3189,7 @@
" " " "
], ],
"text/plain": [ "text/plain": [
"<IPython.lib.display.YouTubeVideo at 0x7f6b1c4606d8>" "<IPython.lib.display.YouTubeVideo at 0x7f2c7c41bc50>"
] ]
}, },
"execution_count": 94, "execution_count": 94,
@ -3225,7 +3225,7 @@
"\n", "\n",
"In simple words, anything that may be used on the right-hand side of an assignment statement without creating a `SyntaxError` is an expression.\n", "In simple words, anything that may be used on the right-hand side of an assignment statement without creating a `SyntaxError` is an expression.\n",
"\n", "\n",
"What we said about individual operators above, namely that they have *no* side effects, should have been put here to begin with. The examples in the section on operators above were actually all expressions!\n", "What we said about individual operators above, namely that they have *no* side effects, should have been put here, to begin with. The code cells in the section on operators above are all expressions!\n",
"\n", "\n",
"The simplest possible expressions contain only one variable or literal." "The simplest possible expressions contain only one variable or literal."
] ]
@ -3313,7 +3313,7 @@
} }
}, },
"source": [ "source": [
"The definition of an expression is **recursive**. So, here the sub-expression `a + b` is combined with the literal `3` by the operator `**` to form the full expression." "The definition of an expression is **recursive**. So, the sub-expression `a + b` is combined with the literal `3` by the operator `**` to form the full expression."
] ]
}, },
{ {
@ -3383,7 +3383,7 @@
} }
}, },
"source": [ "source": [
"When not used as a delimiter, parentheses also constitute an operator, namely the **call operator** `()`. We have seen this syntax above when we \"called\" (i.e., executed) built-in functions and methods." "When not used as a delimiter, parentheses also constitute an operator, namely the **call operator** `()`. We saw this syntax above when we \"called\" (i.e., executed) built-in functions and methods."
] ]
}, },
{ {
@ -3429,7 +3429,7 @@
} }
}, },
"source": [ "source": [
"Python **overloads** certain operators. For example, you may not only \"add\" numbers but also strings. This is called **string concatenation**." "Python **overloads** certain operators. For example, you may not only \"add\" numbers but also strings: This is called **string concatenation**."
] ]
}, },
{ {
@ -3524,9 +3524,9 @@
} }
}, },
"source": [ "source": [
"A **[statement](https://docs.python.org/3/reference/simple_stmts.html)** is anything that *changes* the *state of a program* or has some other *side effect*. Statements do not just evaluate to a value like expressions; instead, they create or change values.\n", "A **[statement](https://docs.python.org/3/reference/simple_stmts.html)** is anything that *changes* the *state of a program* or has another *side effect*. Statements, unlike expressions, do not just evaluate to a value; instead, they create or change values.\n",
"\n", "\n",
"Most notably of course are the `=` and `del` statements." "Most notably, of course, are the `=` and `del` statements."
] ]
}, },
{ {
@ -3563,7 +3563,7 @@
} }
}, },
"source": [ "source": [
"The built-in [print()](https://docs.python.org/3/library/functions.html#print) function is regarded a \"statement\" as well. In fact, it used to be a real statement in Python 2 and has all the necessary properties. It is a bit of a corner case as expressions are also \"printed\" in a Jupyter notebook when evaluated last in a code cell." "The built-in [print()](https://docs.python.org/3/library/functions.html#print) function is regarded as a \"statement\" as well. It used to be an actual statement in Python 2 and has all the necessary properties. It is a bit of a corner case as expressions are also \"printed\" in a Jupyter notebook when evaluated last in a code cell."
] ]
}, },
{ {
@ -3608,9 +3608,9 @@
"source": [ "source": [
"We use the `#` symbol to write comments in plain English right into the code.\n", "We use the `#` symbol to write comments in plain English right into the code.\n",
"\n", "\n",
"As a good practice, comments should not describe what happens (this should be evident by reading the code, otherwise it is most likely badly written code) but why something happens.\n", "As a good practice, comments should not describe *what* happens (this should be evident by reading the code; otherwise, it is most likely badly written code) but *why* something happens.\n",
"\n", "\n",
"Comments may be added either at the end of a line of code, by convention seperated with two spaces, or on a line on their own." "Comments may be added either at the end of a line of code, by convention separated with two spaces, or on a line on their own."
] ]
}, },
{ {
@ -3637,8 +3637,8 @@
} }
}, },
"source": [ "source": [
"But let's think wisely if we really need to use a comment.\n", "But let's think wisely if we need to use a comment.\n",
"The second cell is a lot more \"Pythonic\"." "The second cell is a lot more Pythonic."
] ]
}, },
{ {
@ -3686,7 +3686,7 @@
} }
}, },
"source": [ "source": [
"We end each chapter with a summary of the main points. The essence in this first chapter is that just like a sentence in a real language like English may be decomposed into its parts (subject, predicate, objects, ...) the same may be done with programming languages." "We end each chapter with a summary of the main points. The essence in this first chapter is that just like a sentence in a real language like English may be decomposed into its parts (e.g., subject, predicate, and objects), the same may be done with programming languages."
] ]
}, },
{ {
@ -3707,18 +3707,18 @@
" - (numeric) data from a CSV file\n", " - (numeric) data from a CSV file\n",
" - text entered on a command line\n", " - text entered on a command line\n",
" - (relational) data obtained from a database\n", " - (relational) data obtained from a database\n",
" - ...\n", " - etc.\n",
"\n", "\n",
"\n", "\n",
"- output (examples)\n", "- output (examples)\n",
" - result of a computation (e.g., statistical summary of a sample dataset)\n", " - result of a computation (e.g., statistical summary of a sample dataset)\n",
" - a \"side effect\" (e.g., a transformation of raw input data into cleaned data)\n", " - a \"side effect\" (e.g., a transformation of raw input data into cleaned data)\n",
" - a physical \"behavior\" (e.g., a robot moving or a document printed)\n", " - a physical \"behavior\" (e.g., a robot moving or a document printed)\n",
" - ...\n", " - etc.\n",
"\n", "\n",
"\n", "\n",
"- objects\n", "- objects\n",
" - distinct and well-contained areas / parts of the memory that hold the actual data\n", " - distinct and well-contained areas/parts of the memory that hold the actual data\n",
" - the concept by which Python manages the memory for us\n", " - the concept by which Python manages the memory for us\n",
" - can be classified into objects of the same **type** (i.e., same abstract \"structure\" but different concrete data)\n", " - can be classified into objects of the same **type** (i.e., same abstract \"structure\" but different concrete data)\n",
" - built-in objects (incl. **literals**) vs. user-defined objects (cf., Chapter 8)\n", " - built-in objects (incl. **literals**) vs. user-defined objects (cf., Chapter 8)\n",
@ -3733,13 +3733,13 @@
"\n", "\n",
"- operators\n", "- operators\n",
" - special built-in symbols that perform operations with objects in memory\n", " - special built-in symbols that perform operations with objects in memory\n",
" - usually operate with one or two objects\n", " - usually, operate with one or two objects\n",
" - e.g., addition `+`, subtraction `-`, multiplication `*`, and division `/` all take two objects whereas the negation `-` only takes one\n", " - e.g., addition `+`, subtraction `-`, multiplication `*`, and division `/` all take two objects whereas the negation `-` only takes one\n",
"\n", "\n",
"\n", "\n",
"- expressions\n", "- expressions\n",
" - **combinations** of **variables** (incl. **literals**) and **operators**\n", " - **combinations** of **variables** (incl. **literals**) and **operators**\n",
" - do *not* change the involved objects / state of the program\n", " - do *not* change the involved objects/state of the program\n",
" - evaluate to a **value** (i.e., the \"result\" of the expression, usually a new object)\n", " - evaluate to a **value** (i.e., the \"result\" of the expression, usually a new object)\n",
" - e.g., `x + 2` evaluates to the (new) object `3` and `1 - 1.0` to `0.0`\n", " - e.g., `x + 2` evaluates to the (new) object `3` and `1 - 1.0` to `0.0`\n",
"\n", "\n",
@ -3747,7 +3747,7 @@
"- statements\n", "- statements\n",
" - instructions that **\"do\" something** and **have side effects** in memory\n", " - instructions that **\"do\" something** and **have side effects** in memory\n",
" - re-map names to different objects and *change* the state of the program\n", " - re-map names to different objects and *change* the state of the program\n",
" - usually work with expressions\n", " - usually, work with expressions\n",
" - e.g., the assignment statement `=` makes a name point to an object\n", " - e.g., the assignment statement `=` makes a name point to an object\n",
"\n", "\n",
"\n", "\n",

View file

@ -40,7 +40,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"**Q1**: Elaborate on how **modulo division** might be a very useful operation to know!" "**Q1**: Elaborate on how **modulo division** might be a handy operation to know!"
] ]
}, },
{ {

View file

@ -19,11 +19,11 @@
} }
}, },
"source": [ "source": [
"In [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb) we typed the **business logic** of our little program to calculate the mean of a subset of a list of numbers right into the code cells. Then, we executed them one after another. We had no way of **re-using** the code except for either re-executing the cells or copying and pasting their contents into other cells. And whenever we find ourselves doing repetitive manual work, we can be sure that there must be a way of automating what we are doing.\n", "In [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb), we typed the **business logic** of our little program to calculate the mean of a subset of a list of numbers right into the code cells. Then, we executed them one after another. We had no way of **re-using** the code except for either re-executing the cells or copying and pasting their contents into other cells. And whenever we find ourselves doing repetitive manual work, we can be sure that there must be a way of automating what we are doing.\n",
"\n", "\n",
"At the same time, we executed built-in functions (e.g., [print()](https://docs.python.org/3/library/functions.html#print), [sum()](https://docs.python.org/3/library/functions.html#sum), [len()](https://docs.python.org/3/library/functions.html#len), [id()](https://docs.python.org/3/library/functions.html#id), or [type()](https://docs.python.org/3/library/functions.html#type)) that obviously must be re-using the same parts inside core Python every time we use them.\n", "At the same time, we executed built-in functions (e.g., [print()](https://docs.python.org/3/library/functions.html#print), [sum()](https://docs.python.org/3/library/functions.html#sum), [len()](https://docs.python.org/3/library/functions.html#len), [id()](https://docs.python.org/3/library/functions.html#id), or [type()](https://docs.python.org/3/library/functions.html#type)) that obviously must be re-using the same parts inside core Python every time we use them.\n",
"\n", "\n",
"This chapter shows how Python offers language constructs that let us **define** our own functions that we may then **call** just like the built-in ones." "This chapter shows how Python offers language constructs that let us **define** functions ourselves that we may then **call** just like the built-in ones."
] ]
}, },
{ {
@ -47,17 +47,17 @@
"source": [ "source": [
"So-called **[user-defined functions](https://docs.python.org/3/reference/compound_stmts.html#function-definitions)** may be created with the `def` statement. To extend an already familiar example, we re-use the introductory example from [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb) in its final Pythonic version and transform it into the function `average_evens()` below. \n", "So-called **[user-defined functions](https://docs.python.org/3/reference/compound_stmts.html#function-definitions)** may be created with the `def` statement. To extend an already familiar example, we re-use the introductory example from [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb) in its final Pythonic version and transform it into the function `average_evens()` below. \n",
"\n", "\n",
"A function's **name** must be chosen according to the same naming rules as for ordinary variables. In fact, Python manages function names just like variables. In this book, we further adopt the convention of ending function names with parentheses \"`()`\" in text cells for faster comprehension when reading (i.e., `average_evens()` vs. `average_evens`). These are not actually part of the name but must always be written out in the `def` statement for syntactic reasons.\n", "A function's **name** must be chosen according to the same naming rules as ordinary variables as Python manages function names like variables. In this book, we further adopt the convention of ending function names with parentheses `()` in text cells for faster comprehension when reading (i.e., `average_evens()` vs. `average_evens`). These are *not* part of the name but must always be written out in the `def` statement for syntactic reasons.\n",
"\n", "\n",
"Functions may define an arbitrary number of **parameters** as inputs that are then referenced within the indented **code block**: They are simply listed within the parentheses in the `def` statement (i.e., `numbers` below). \n", "Functions may define an arbitrary number of **parameters** as inputs that can then be referenced within the indented **code block**: They are listed within the parentheses in the `def` statement (i.e., `numbers` below). \n",
"\n", "\n",
"The code block is often also called a function's **body** while the first line with the `def` in it is the **header** and must end with a colon.\n", "The code block is also called a function's **body**, while the first line starting with `def` and ending with a colon is the **header**.\n",
"\n", "\n",
"Together, the name and the list of parameters are also referred to as the function's **[signature](https://en.wikipedia.org/wiki/Type_signature)** (i.e., `average_evens(numbers)` below).\n", "Together, the name and the list of parameters are also referred to as the function's **[signature](https://en.wikipedia.org/wiki/Type_signature)** (i.e., `average_evens(numbers)` below).\n",
"\n", "\n",
"A function may come with an *explicit* **[return value](https://docs.python.org/3/reference/simple_stmts.html#the-return-statement)** (i.e., \"result\" or \"output\") specified with the `return` statement: Functions that have one are considered **fruitful**; otherwise, they are **void**. Functions of the latter kind are still useful because of their **side effects** (e.g., the [print()](https://docs.python.org/3/library/functions.html#print) built-in). Strictly speaking, they also have an *implicit* return value of `None` that is different from the `False` we saw in Chapter 1.\n", "A function may come with an *explicit* **[return value](https://docs.python.org/3/reference/simple_stmts.html#the-return-statement)** (i.e., \"result\" or \"output\") specified with the `return` statement: Functions that have one are considered **fruitful**; otherwise, they are **void**. Functions of the latter kind are still useful because of their **side effects** (e.g., the [print()](https://docs.python.org/3/library/functions.html#print) built-in). Strictly speaking, they also have an *implicit* return value of `None` that is different from the `False` we saw in Chapter 1.\n",
"\n", "\n",
"To maintain good coding practices, a function should define a **docstring** that describes what it does in a short subject line, what parameters it expects (i.e., their types), and what it returns (if anything). A docstring is a syntactically valid multi-line string (i.e., type `str`) defined with **triple-double quotes** (strings are covered in depth in Chapter 6). Good standards as to how to format a docstring are [PEP 257](https://www.python.org/dev/peps/pep-0257/) and section 3.8 in [Google's Python Style Guide](https://github.com/google/styleguide/blob/gh-pages/pyguide.md)." "A function should define a **docstring** that describes what it does in a short subject line, what parameters it expects (i.e., their types), and what it returns (if anything). A docstring is a syntactically valid multi-line string (i.e., type `str`) defined within **triple-double quotes** `\"\"\"`. Strings are covered in depth in Chapter 6. Widely adopted standards as to how to format a docstring are [PEP 257](https://www.python.org/dev/peps/pep-0257/) and section 3.8 in [Google's Python Style Guide](https://github.com/google/styleguide/blob/gh-pages/pyguide.md)."
] ]
}, },
{ {
@ -142,7 +142,7 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"139925407773152" "140519730762208"
] ]
}, },
"execution_count": 3, "execution_count": 3,
@ -254,7 +254,7 @@
} }
}, },
"source": [ "source": [
"Two questions marks even show a function's source code." "Two question marks even show a function's source code."
] ]
}, },
{ {
@ -337,7 +337,7 @@
} }
}, },
"source": [ "source": [
"The return value is usually assigned to a new variable for later reference. Otherwise we would loose access to it in memory right away." "The return value is commonly assigned to a new variable for later reference. Otherwise, we would lose access to it in memory right away."
] ]
}, },
{ {
@ -493,7 +493,7 @@
} }
}, },
"source": [ "source": [
"[PythonTutor](http://pythontutor.com/visualize.html#code=nums%20%3D%20%5B1,%202,%203,%204,%205,%206,%207,%208,%209,%2010%5D%0A%0Adef%20average_evens%28numbers%29%3A%0A%20%20%20%20evens%20%3D%20%5Bn%20for%20n%20in%20numbers%20if%20n%20%25%202%20%3D%3D%200%5D%0A%20%20%20%20average%20%3D%20sum%28evens%29%20/%20len%28evens%29%0A%20%20%20%20return%20average%0A%0Arv%20%3D%20average_evens%28nums%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) visualizes what happens in memory: To be precise, in the exact moment when the function call is initiated and `nums` is passed in as the `numbers` argument, there are *two* pointers to the *same* `list` object (cf., steps 4-5 in the visualization). We also see how Python creates a *new* **frame** that holds the function's local scope (i.e., \"internal names\") in addition to the **global** frame. Frames are nothing but [namespaces](https://en.wikipedia.org/wiki/Namespace) to *isolate* the names of different **scopes** from each other. The list comprehension `[n for n in numbers if n % 2 == 0]` constitutes yet another frame that is in scope as the `list` object assigned to `evens` is *being* created (cf., steps 6-18). When the function returns, only the global frame is left (cf., steps 21-22)." "[PythonTutor](http://pythontutor.com/visualize.html#code=nums%20%3D%20%5B1,%202,%203,%204,%205,%206,%207,%208,%209,%2010%5D%0A%0Adef%20average_evens%28numbers%29%3A%0A%20%20%20%20evens%20%3D%20%5Bn%20for%20n%20in%20numbers%20if%20n%20%25%202%20%3D%3D%200%5D%0A%20%20%20%20average%20%3D%20sum%28evens%29%20/%20len%28evens%29%0A%20%20%20%20return%20average%0A%0Arv%20%3D%20average_evens%28nums%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) visualizes what happens in memory: To be precise, in the exact moment when the function call is initiated and `nums` passed in as the `numbers` argument, there are *two* pointers to the *same* `list` object (cf., steps 4-5 in the visualization). We also see how Python creates a *new* **frame** that holds the function's local scope (i.e., \"internal names\") in addition to the **global** frame. Frames are nothing but [namespaces](https://en.wikipedia.org/wiki/Namespace) to *isolate* the names of different **scopes** from each other. The list comprehension `[n for n in numbers if n % 2 == 0]` constitutes yet another frame that is in scope as the `list` object assigned to `evens` is *being* created (cf., steps 6-18). When the function returns, only the global frame is left (cf., steps 21-22)."
] ]
}, },
{ {
@ -515,7 +515,7 @@
} }
}, },
"source": [ "source": [
"On the contrary, while a function is being executed, it can reference the variables of **enclosing scopes** (i.e., \"outside\" of it). This is a common source of *semantic* errors. Consider the following stylized (and incorrect) example `average_wrong()`. The error is hard to spot with eyes: The function never references the `numbers` parameter but the `nums` variable in the **global scope** instead." "On the contrary, while a function is *being* executed, it can reference the variables of **enclosing scopes** (i.e., \"outside\" of it). This is a common source of *semantic* errors. Consider the following stylized (and incorrect) example `average_wrong()`. The error is hard to spot with eyes: The function never references the `numbers` parameter but the `nums` variable in the **global scope** instead."
] ]
}, },
{ {
@ -550,7 +550,7 @@
} }
}, },
"source": [ "source": [
"`nums` in the global scope is of course unchanged." "`nums` in the global scope is, of course, unchanged."
] ]
}, },
{ {
@ -657,7 +657,7 @@
"source": [ "source": [
"[PythonTutor](http://pythontutor.com/visualize.html#code=nums%20%3D%20%5B1,%202,%203,%204,%205,%206,%207,%208,%209,%2010%5D%0A%0Adef%20average_wrong%28numbers%29%3A%0A%20%20%20%20evens%20%3D%20%5Bn%20for%20n%20in%20nums%20if%20n%20%25%202%20%3D%3D%200%5D%0A%20%20%20%20average%20%3D%20sum%28evens%29%20/%20len%28evens%29%0A%20%20%20%20return%20average%0A%0Arv%20%3D%20average_wrong%28%5B123,%20456,%20789%5D%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) is again helpful at visualizing the error interactively: Creating the `list` object `evens` eventually points to takes *12* computational steps, namely one for setting up an empty `list` object, *ten* for filling it with elements derived from `nums` in the global scope, and one to make `evens` point at it (cf., steps 6-18).\n", "[PythonTutor](http://pythontutor.com/visualize.html#code=nums%20%3D%20%5B1,%202,%203,%204,%205,%206,%207,%208,%209,%2010%5D%0A%0Adef%20average_wrong%28numbers%29%3A%0A%20%20%20%20evens%20%3D%20%5Bn%20for%20n%20in%20nums%20if%20n%20%25%202%20%3D%3D%200%5D%0A%20%20%20%20average%20%3D%20sum%28evens%29%20/%20len%28evens%29%0A%20%20%20%20return%20average%0A%0Arv%20%3D%20average_wrong%28%5B123,%20456,%20789%5D%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) is again helpful at visualizing the error interactively: Creating the `list` object `evens` eventually points to takes *12* computational steps, namely one for setting up an empty `list` object, *ten* for filling it with elements derived from `nums` in the global scope, and one to make `evens` point at it (cf., steps 6-18).\n",
"\n", "\n",
"The frames logic shown by PythonTutor is the mechanism by which Python not only manages the names inside one function call but for potentially many calls occuring simultaneously as we will see in [Chapter 4](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration.ipynb). It is the reason why we may re-use the same names for the parameters and variables inside both `average_evens()` and `average_wrong()` without Python mixing them up. So, as we already read in the [Zen of Python](https://www.python.org/dev/peps/pep-0020/), \"namespaces are one honking great idea\" (cf., `import this`) and a frame is just a special kind of namespace." "The frames logic shown by PythonTutor is the mechanism by which Python not only manages the names inside *one* function call but also for *many* potentially simultaneous* calls, as revealed in [Chapter 4](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration.ipynb). It is the reason why we may re-use the same names for the parameters and variables inside both `average_evens()` and `average_wrong()` without Python mixing them up. So, as we already read in the [Zen of Python](https://www.python.org/dev/peps/pep-0020/), \"namespaces are one honking great idea\" (cf., `import this`), and a frame is just a special kind of namespace."
] ]
}, },
{ {
@ -752,7 +752,7 @@
} }
}, },
"source": [ "source": [
"As good practice, let's first use inputs for which we can calculate the answer in our heads to \"verify\" that `average_odds()` is \"correct\"." "As good practice, let's first use inputs for which we can calculate the answer in our heads to \"verify\" that `average_odds()` is \"correct.\""
] ]
}, },
{ {
@ -787,7 +787,7 @@
} }
}, },
"source": [ "source": [
"To make the confusion even bigger, let's also pass the global `nums` as an argument to `average_odds()`." "To add to the confusion, let's also pass the global `nums` as an argument to `average_odds()`."
] ]
}, },
{ {
@ -857,13 +857,13 @@
} }
}, },
"source": [ "source": [
"The reason why everything just works is that *every time* we (re-)assign an object to a variable *inside* a function with the `=` statement, this is done in the *local* scope by default. There are ways to change variables existing in an outer scope from within a function but this is a rather advanced topic on its own.\n", "The reason why everything works is that *every time* we (re-)assign an object to a variable *inside* a function with the `=` statement, this is done in the *local* scope by default. There are ways to change variables existing in an outer scope from within a function, but this is a rather advanced topic on its own.\n",
"\n", "\n",
"[PythonTutor](http://pythontutor.com/visualize.html#code=nums%20%3D%20%5B1,%202,%203,%204,%205,%206,%207,%208,%209,%2010%5D%0A%0Adef%20average_odds%28numbers%29%3A%0A%20%20%20%20nums%20%3D%20%5Bint%28n%29%20for%20n%20in%20numbers%5D%0A%20%20%20%20odds%20%3D%20%5Bn%20for%20n%20in%20nums%20if%20n%20%25%202%20!%3D%200%5D%0A%20%20%20%20average%20%3D%20sum%28odds%29%20/%20len%28odds%29%0A%20%20%20%20return%20average%0A%0Arv%20%3D%20average_odds%28%5B1.0,%2010.0,%203.0,%2010.0,%205.0%5D%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) shows how *two* `nums` variables exist in *different* scopes pointing to *different* objects (cf., steps 14-25) when we execute `average_odds([1.0, 10.0, 3.0, 10.0, 5.0])`.\n", "[PythonTutor](http://pythontutor.com/visualize.html#code=nums%20%3D%20%5B1,%202,%203,%204,%205,%206,%207,%208,%209,%2010%5D%0A%0Adef%20average_odds%28numbers%29%3A%0A%20%20%20%20nums%20%3D%20%5Bint%28n%29%20for%20n%20in%20numbers%5D%0A%20%20%20%20odds%20%3D%20%5Bn%20for%20n%20in%20nums%20if%20n%20%25%202%20!%3D%200%5D%0A%20%20%20%20average%20%3D%20sum%28odds%29%20/%20len%28odds%29%0A%20%20%20%20return%20average%0A%0Arv%20%3D%20average_odds%28%5B1.0,%2010.0,%203.0,%2010.0,%205.0%5D%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) shows how *two* `nums` variables exist in *different* scopes pointing to *different* objects (cf., steps 14-25) when we execute `average_odds([1.0, 10.0, 3.0, 10.0, 5.0])`.\n",
"\n", "\n",
"Variables whose names collide with the ones of variables in enclosing scopes - and the global scope is just the most enclosing scope - are said to **shadow** them.\n", "Variables whose names collide with the ones of variables in enclosing scopes - and the global scope is just the most enclosing scope - are said to **shadow** them.\n",
"\n", "\n",
"While this is not a problem for Python, it may lead to less readable code for us humans and should be avoided if possible. But, as we have also heard, \"[naming things](https://skeptics.stackexchange.com/questions/19836/has-phil-karlton-ever-said-there-are-only-two-hard-things-in-computer-science)\" is often considered hard as well and we have to be prepared to encounter shadowing variables." "While this is not a problem for Python, it may lead to less readable code for us humans and should be avoided if possible. But, as we have also heard, \"[naming things](https://skeptics.stackexchange.com/questions/19836/has-phil-karlton-ever-said-there-are-only-two-hard-things-in-computer-science)\" is often considered hard as well, and we have to be prepared to encounter shadowing variables."
] ]
}, },
{ {
@ -946,7 +946,7 @@
} }
}, },
"source": [ "source": [
"We may cast certain objects as a different type. For example, to \"convert\" a float or a text into an integer, we use the [int()](https://docs.python.org/3/library/functions.html#int) built-in. This actually creates a *new* object of type `int` from the provided `avg` or `\"6\"` objects who continue to exist in memory unchanged." "We may cast objects as a different type. For example, to \"convert\" a float or a text into an integer, we use the [int()](https://docs.python.org/3/library/functions.html#int) built-in. It creates a *new* object of type `int` from the provided `avg` or `\"6\"` objects that continue to exist in memory unchanged."
] ]
}, },
{ {
@ -1170,7 +1170,7 @@
} }
}, },
"source": [ "source": [
"So far we have only specified one parameter in each of our user-defined functions. In [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb), however, we saw the built-in function [divmod()](https://docs.python.org/3/library/functions.html#divmod) taking two arguments. Obviously, the order of the numbers passed in mattered. Whenever we call a function and list its arguments in a comma seperated manner, we say that we pass in the arguments by position or refer to them as **positional arguments**." "So far, we have only specified one parameter in each of our user-defined functions. In [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb), however, we saw the built-in function [divmod()](https://docs.python.org/3/library/functions.html#divmod) take two arguments. And, the order of the numbers passed in mattered! Whenever we call a function and list its arguments in a comma separated manner, we say that we pass in the arguments by position or refer to them as **positional arguments**."
] ]
}, },
{ {
@ -1229,7 +1229,7 @@
} }
}, },
"source": [ "source": [
"For many functions there is a natural order to the arguments. But what if this is not the case? For example, let's create a close relative of the above `average_evens()` function that also scales the resulting average by a factor. What is more natural? Passing in `numbers` first? Or `scalar`? There is no obvious way and we continue with the first alternative for no real reason." "For many functions, there is a natural order to the arguments. But what if this is not the case? For example, let's create a close relative of the above `average_evens()` function that also scales the resulting average by a factor. What is more natural? Passing in `numbers` first? Or `scalar`? There is no obvious way and we continue with the first alternative for no concrete reason."
] ]
}, },
{ {
@ -1301,9 +1301,9 @@
} }
}, },
"source": [ "source": [
"However, now the function call is a bit harder to comprehend as we need to always remember what the `2` means. This becomes even harder the more parameters we specify.\n", "However, now the function call is a bit harder to comprehend as we always need to remember what the `2` means. This becomes harder the more parameters we specify.\n",
"\n", "\n",
"Luckily, we may also reference the formal parameter names as **keyword arguments**. We can even combine positional and keyword arguments in the same function call. Each of the following does the exact same thing." "Luckily, we may also reference the formal parameter names as **keyword arguments**. We can even combine positional and keyword arguments in the same function call. Each of the following does the *same*."
] ]
}, },
{ {
@ -1430,11 +1430,11 @@
} }
}, },
"source": [ "source": [
"Defining both `average_evens()` and `scaled_average_evens()` is also kind of repetitive as most of their code is the same. Such a redundancy will make a code base harder to maintain in the long run as whenever we change the logic in one function we must *not* forget to do so for the other function as well.\n", "Defining both `average_evens()` and `scaled_average_evens()` is also kind of repetitive as most of their code is the same. Such a redundancy makes a codebase harder to maintain in the long run as whenever we change the logic in one function, we must *not* forget to do so for the other function as well.\n",
"\n", "\n",
"A better way is to design related functions in a **modular** fashion such that they re-use each other's logic.\n", "A better way is to design related functions in a **modular** fashion such that they re-use each other's logic.\n",
"\n", "\n",
"For example, as not scaling an average is just a special case of scaling it with `1`, we could re-define the two functions like below: In this setting, the function resembling the *special* case (i.e., `average_evens()`) simply **forwards** the call to the more *general* function (i.e., `scaled_average_evens()`) using a `scalar=1` argument." "For example, as not scaling an average is just a special case of scaling it with `1`, we could re-define the two functions like below: In this setting, the function resembling the *special* case (i.e., `average_evens()`) **forwards** the call to the more *general* function (i.e., `scaled_average_evens()`) using a `scalar=1` argument."
] ]
}, },
{ {
@ -1483,7 +1483,7 @@
} }
}, },
"source": [ "source": [
"The outcome of `average_evens(nums)` is of course still `6.0`." "The outcome of `average_evens(nums)` is, of course, still `6.0`."
] ]
}, },
{ {
@ -1557,7 +1557,7 @@
"source": [ "source": [
"Now we call the function either with or without the `scalar` argument.\n", "Now we call the function either with or without the `scalar` argument.\n",
"\n", "\n",
"If `scalar` is passed in, this may be done as either a positional or a keyword argument. Which of the two versions where `scalar` is `2` is easier to comprehend in a large program?" "If `scalar` is to be passed in, this may be done as either a positional or a keyword argument. Which of the two versions where `scalar` is `2` is more \"natural\" and faster to comprehend in a larger program?"
] ]
}, },
{ {
@ -1651,9 +1651,9 @@
} }
}, },
"source": [ "source": [
"Since we *assumed* that scaling will occur *rarely*, we'd prefer that our new version of `average_evens()` be called with a *keyword argument* whenever `scalar` is passed in explicitly. Then, the second argument is never ambiguous as we could always read its name.\n", "Since we *assumed* that scaling occurs *rarely*, we'd prefer that our new version of `average_evens()` be called with a *keyword argument* whenever `scalar` is to be passed in explicitly. Then, the second argument is never ambiguous as we could always read its name.\n",
"\n", "\n",
"Luckily, Python offers a **keyword-only** syntax, where all we need to do is to place the arguments for which we require *explicit* keyword use after an asterix `*`." "Luckily, Python offers a **keyword-only** syntax, where all we need to do is to place the arguments for which we require *explicit* keyword use after an asterisk `*`."
] ]
}, },
{ {
@ -1798,13 +1798,13 @@
"source": [ "source": [
"The `def` statement is a statement because of its side effect of creating a *new* name that points to a *new* `function` object in memory.\n", "The `def` statement is a statement because of its side effect of creating a *new* name that points to a *new* `function` object in memory.\n",
"\n", "\n",
"We can thus think of it as doing *two* things at once (i.e., either both of them happen or none). First, a `function` object is created that contains the concrete $0$s and $1$s that resemble the instructions we put into the function's body. In the context of a function, these $0$s and $1$s are also called **[byte code](https://en.wikipedia.org/wiki/Bytecode)**. Then, a name is created pointing at the new `function` object.\n", "We can thus think of it as doing *two* things at once (i.e., either both of them happen or none). First, a `function` object is created that contains the concrete $0$s and $1$s that resemble the instructions we put into the function's body. In the context of a function, these $0$s and $1$s are also called **[byte code](https://en.wikipedia.org/wiki/Bytecode)**. Then, a name pointing at the new `function` object is created.\n",
"\n", "\n",
"Only this second aspect makes `def` a statement: Merely creating a new object in memory without making it accessible for later reference does *not* constitute a side effect. This is because the state the program is *not* changed. After all, if we cannot reference an object, how do we know that it is actually existing?\n", "Only this second aspect makes `def` a statement: Merely creating a new object in memory without making it accessible for later reference does *not* constitute a side effect because the state the program is *not* changed. After all, if we cannot reference an object, how do we know it exists in the first place?\n",
"\n", "\n",
"Python provides a so-called **[lambda expression](https://docs.python.org/3/reference/expressions.html#lambda)** syntax that allows us to *only* create a `function` object in memory *without* making a name point to it.\n", "Python provides a so-called **[lambda expression](https://docs.python.org/3/reference/expressions.html#lambda)** syntax that allows us to *only* create a `function` object in memory *without* making a name point to it.\n",
"\n", "\n",
"It starts with the keyword `lambda` followed by an optional comma seperated enumeration of parameters, a mandatory colon, and *one* expression that also is the resulting `function` object's return value.\n", "It starts with the keyword `lambda` followed by an optional comma separated enumeration of parameters, a mandatory colon, and *one* expression that also is the resulting `function` object's return value.\n",
"\n", "\n",
"Because it does not create a name pointing to the object, we effectively create \"anonymous\" functions with it. In the example, we create a `function` object that adds `3` to the only argument passed in as the parameter `x` and returns that sum." "Because it does not create a name pointing to the object, we effectively create \"anonymous\" functions with it. In the example, we create a `function` object that adds `3` to the only argument passed in as the parameter `x` and returns that sum."
] ]
@ -1845,7 +1845,7 @@
"\n", "\n",
"We created a `function` object, dit *not* call it, and Python immediately forgot about it. So what's the point?\n", "We created a `function` object, dit *not* call it, and Python immediately forgot about it. So what's the point?\n",
"\n", "\n",
"Just to prove that the `lambda` expression really creates a callable `function` object, we use the simple `=` statement to assign it to the variable `add_three`, which is really `add_three()` as per our convention from above." "To prove that a `lambda` expression creates a callable `function` object, we use the simple `=` statement to assign it to the variable `add_three`, which is really `add_three()` as per our convention from above."
] ]
}, },
{ {
@ -1869,7 +1869,7 @@
} }
}, },
"source": [ "source": [
"Now we call `add_three()` as if we defined it with the `def` statement to begin with." "Now we call `add_three()` as if we defined it with the `def` statement in the first place."
] ]
}, },
{ {
@ -1904,7 +1904,7 @@
} }
}, },
"source": [ "source": [
"Alternatively, we could call any anonymous `function` object created with an `lambda` expression right away (i.e. without assigning it to a variable), which looks really weird for now as we need *two* pairs of parentheses: The first is just a delimiter whereas the second the call operator." "Alternatively, we could call an anonymous `function` object created with a `lambda` expression right away (i.e., without assigning it to a variable), which looks quite weird for now as we need *two* pairs of parentheses: The first is just a delimiter whereas the second the call operator."
] ]
}, },
{ {
@ -1939,9 +1939,9 @@
} }
}, },
"source": [ "source": [
"The main point of having functions without a name is to use them in a situation where we know ahead of time that we will use the function only once.\n", "The main point of having functions without a name is to use them in a situation where we know ahead of time that we use the function *once* only.\n",
"\n", "\n",
"Very popular contexts where we will apply lambda expressions are with the **map-filter-reduce** paradigm in Chapter 7 or when we do \"number crunching\" with **arrays** and **data frames** in Chapter 9." "Popular applications of lambda expressions are with the **map-filter-reduce** paradigm in Chapter 7 or when we do \"number crunching\" with **arrays** and **data frames** in Chapter 9."
] ]
}, },
{ {
@ -1963,13 +1963,13 @@
} }
}, },
"source": [ "source": [
"So far, we have only used what we refer to as **core** Python in this book. By this, we mean all the syntactical rules as specified in the [language reference](https://docs.python.org/3/reference/) and a minimal set of about 50 built-in [functions](https://docs.python.org/3/library/functions.html). With this we could already implement any algorithm or business logic we can think of!\n", "So far, we have only used what we refer to as **core** Python in this book. By this, we mean all the syntactical rules as specified in the [language reference](https://docs.python.org/3/reference/) and a minimal set of about 50 built-in [functions](https://docs.python.org/3/library/functions.html). With this, we could already implement any algorithm or business logic we can think of!\n",
"\n", "\n",
"However, after our first couple of programs, we would already start seeing recurring patterns in the code we write. In other words, we would constantly be \"re-inventing the wheel\" in each new project.\n", "However, after our first couple of programs, we would already start seeing recurring patterns in the code we write. In other words, we would constantly be \"re-inventing the wheel\" in each new project.\n",
"\n", "\n",
"Would it not be smarter to pull out the re-usable components from our programs and put them into some project independent **library** of generically useful functionalities? Then we would only need a way of including these **utilities** in our projects.\n", "Would it not be smarter to pull out the re-usable components from our programs and put them into some project independent **library** of generically useful functionalities? Then we would only need a way of including these **utilities** in our projects.\n",
"\n", "\n",
"As all programmers across all languages face this very same issue, most programming languages come with a so-called **[standard library](https://en.wikipedia.org/wiki/Standard_library)** that provides utilities to accomplish common tasks without a lot of code. Examples are making an HTTP request to some website, open and read popular file types (e.g., CSV or Excel files), do something on a computer's file system, and many more." "As all programmers across all languages face this very same issue, most programming languages come with a so-called **[standard library](https://en.wikipedia.org/wiki/Standard_library)** that provides utilities to accomplish everyday tasks without much code. Examples are making an HTTP request to some website, open and read popular file types (e.g., CSV or Excel files), do something on a computer's file system, and many more."
] ]
}, },
{ {
@ -1991,13 +1991,13 @@
} }
}, },
"source": [ "source": [
"Python also comes with its own [standard library](https://docs.python.org/3/library/index.html) that is structured into coherent modules and packages for given topics: A **module** is just a plain text file with the file extension *.py* that contains Python code while a **package** is a folder that groups several related modules.\n", "Python also comes with a [standard library](https://docs.python.org/3/library/index.html) that is structured into coherent modules and packages for given topics: A **module** is just a plain text file with the file extension *.py* that contains Python code while a **package** is a folder that groups several related modules.\n",
"\n", "\n",
"The code in the [standard library](https://docs.python.org/3/library/index.html) is contributed and maintained by many volunteers around the world. In contrast to so-called \"third-party\" packages (cf., the next section below), the Python core development team closely monitors and tests the code in the [standard library](https://docs.python.org/3/library/index.html). Consequently, we can be reasonably sure that anything provided by it works correctly independent of our computer's operating system and will most likely also be there in the next Python versions. Parts in the [standard library](https://docs.python.org/3/library/index.html) that are computationally expensive are often re-written in C and therefore much faster than anything we could code in Python ourselves. So, whenever we can solve a problem with the help of the [standard library](https://docs.python.org/3/library/index.html), it is almost always the best way to do so as well.\n", "The code in the [standard library](https://docs.python.org/3/library/index.html) is contributed and maintained by many volunteers around the world. In contrast to so-called \"third-party\" packages (cf., the next section below), the Python core development team closely monitors and tests the code in the [standard library](https://docs.python.org/3/library/index.html). Consequently, we can be reasonably sure that anything provided by it works correctly independent of our computer's operating system and will most likely also be there in the next Python versions. Parts in the [standard library](https://docs.python.org/3/library/index.html) that are computationally expensive are often re-written in C and, therefore, much faster than anything we could write in Python ourselves. So, whenever we can solve a problem with the help of the [standard library](https://docs.python.org/3/library/index.html), it is almost always the best way to do so as well.\n",
"\n", "\n",
"The [standard library](https://docs.python.org/3/library/index.html) has grown very big over the years and we refer to the website [PYMOTW](https://pymotw.com/3/index.html) (i.e., \"Python Module of the Week\") that features well written introductory tutorials and how-to guides to most parts of the library. The same author also published a [book](https://www.amazon.com/Python-Standard-Library-Example-Developers/dp/0134291050/ref=as_li_ss_tl?ie=UTF8&qid=1493563121&sr=8-1&keywords=python+3+standard+library+by+example) that many Pythonistas keep on their shelf for reference. Knowing what is in the [standard library](https://docs.python.org/3/library/index.html) is quite valuable for solving real world tasks quickly.\n", "The [standard library](https://docs.python.org/3/library/index.html) has grown very big over the years, and we refer to the website [PYMOTW](https://pymotw.com/3/index.html) (i.e., \"Python Module of the Week\") that features well written introductory tutorials and how-to guides to most parts of the library. The same author also published a [book](https://www.amazon.com/Python-Standard-Library-Example-Developers/dp/0134291050/ref=as_li_ss_tl?ie=UTF8&qid=1493563121&sr=8-1&keywords=python+3+standard+library+by+example) that many Pythonistas keep on their shelf for reference. Knowing what is in the [standard library](https://docs.python.org/3/library/index.html) is quite valuable for solving real-world tasks quickly.\n",
"\n", "\n",
"Throughout this book we will look at many modules and packages from the [standard library](https://docs.python.org/3/library/index.html) in more depth, starting with the [math](https://docs.python.org/3/library/math.html) and [random](https://docs.python.org/3/library/random.html) modules in this chapter." "Throughout this book, we look at many modules and packages from the [standard library](https://docs.python.org/3/library/index.html) in more depth, starting with the [math](https://docs.python.org/3/library/math.html) and [random](https://docs.python.org/3/library/random.html) modules in this chapter."
] ]
}, },
{ {
@ -2084,7 +2084,7 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"139925472068136" "140519828111832"
] ]
}, },
"execution_count": 58, "execution_count": 58,
@ -2132,7 +2132,7 @@
"\n", "\n",
"Let's see what we can do with the `math` module.\n", "Let's see what we can do with the `math` module.\n",
"\n", "\n",
"The [dir()](https://docs.python.org/3/library/functions.html#dir) built-in function may also be used with an argument passed in. Ignoring the dunder-style names, `math` offers quite a lot of ... names. As we cannot know at this point in time if a listed name refers to a function or an ordinary variable, we use the more generic term **attribute** to mean either one of them." "The [dir()](https://docs.python.org/3/library/functions.html#dir) built-in function may also be used with an argument passed in. Ignoring the dunder-style names, `math` offers quite a lot of names. As we cannot know at this point if a listed name refers to a function or an ordinary variable, we use the more generic term **attribute** to mean either one of them."
] ]
}, },
{ {
@ -2356,7 +2356,7 @@
"source": [ "source": [
"Observe how the arguments passed to functions do not need to be just variables or simple literals. Instead, we may pass in any *expression* that evaluates to a *new* object of the type the function expects.\n", "Observe how the arguments passed to functions do not need to be just variables or simple literals. Instead, we may pass in any *expression* that evaluates to a *new* object of the type the function expects.\n",
"\n", "\n",
"So just as a reminder from the expression vs. statement discussion in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb): An expression is *any* syntactically correct combination of variables and literals with operators. And the call operator `()` is just ... well another operator. So both of the next two code cells are just expressions! They have no permanent side effect in memory. We may execute them as often as we want *without* changing the state of the program (i.e., this Jupyter notebook).\n", "So just as a reminder from the expression vs. statement discussion in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb): An expression is *any* syntactically correct combination of variables and literals with operators. And the call operator `()` is yet another operator. So both of the next two code cells are just expressions! They have no permanent side effects in memory. We may execute them as often as we want *without* changing the state of the program (i.e., this Jupyter notebook).\n",
"\n", "\n",
"So, regarding the very next cell in particular: Although the `2 ** 2` creates a *new* object `4` in memory that is then immediately passed into the [math.sqrt()](https://docs.python.org/3/library/math.html#math.sqrt) function, once that function call returns, \"all is lost\" and the newly created `4` object is forgotten again, as well as the return value of [math.sqrt()](https://docs.python.org/3/library/math.html#math.sqrt)." "So, regarding the very next cell in particular: Although the `2 ** 2` creates a *new* object `4` in memory that is then immediately passed into the [math.sqrt()](https://docs.python.org/3/library/math.html#math.sqrt) function, once that function call returns, \"all is lost\" and the newly created `4` object is forgotten again, as well as the return value of [math.sqrt()](https://docs.python.org/3/library/math.html#math.sqrt)."
] ]
@ -2489,7 +2489,7 @@
} }
}, },
"source": [ "source": [
"Often times, we need a random variable, for example, when we want to build a simulation program. The [random](https://docs.python.org/3/library/random.html) module in the [standard library](https://docs.python.org/3/library/index.html) often suffices for that." "Often, we need a random variable, for example, when we want to build a simulation program. The [random](https://docs.python.org/3/library/random.html) module in the [standard library](https://docs.python.org/3/library/index.html) often suffices for that."
] ]
}, },
{ {
@ -2537,7 +2537,7 @@
} }
}, },
"source": [ "source": [
"Besides the usual dunder-style attributes, the [dir()](https://docs.python.org/3/library/functions.html#dir) built-in function lists some attributes in an upper case naming convention and many others starting with a single underscore `_`. To understand the former, we have to wait until Chapter 10 while the latter are explained further below." "Besides the usual dunder-style attributes, the built-in [dir()](https://docs.python.org/3/library/functions.html#dir) function lists some attributes in an upper case naming convention and many others starting with a *single* underscore `_`. To understand the former, we must wait until Chapter 10, while the latter is explained further below."
] ]
}, },
{ {
@ -2697,7 +2697,7 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"0.270353921677863" "0.5291407120147841"
] ]
}, },
"execution_count": 75, "execution_count": 75,
@ -2717,7 +2717,7 @@
} }
}, },
"source": [ "source": [
"While we could build some conditional logic with an `if` statement to map the number generated by [random.random()](https://docs.python.org/3/library/random.html#random.random) to a finite set of elements manually, the [random.choice()](https://docs.python.org/3/library/random.html#random.choice) function provides a lot more **convenience** for us. We simply call it with, for example, the `nums` list and it draws one element out of it with equal chance." "While we could build some conditional logic with an `if` statement to map the number generated by [random.random()](https://docs.python.org/3/library/random.html#random.random) to a finite set of elements manually, the [random.choice()](https://docs.python.org/3/library/random.html#random.choice) function provides a lot more **convenience** for us. We call it with, for example, the `nums` list and it draws one element out of it with equal chance."
] ]
}, },
{ {
@ -2732,7 +2732,7 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"<bound method Random.choice of <random.Random object at 0x56063d20aba8>>" "<bound method Random.choice of <random.Random object at 0x5609be5deba8>>"
] ]
}, },
"execution_count": 76, "execution_count": 76,
@ -2781,7 +2781,7 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"7" "4"
] ]
}, },
"execution_count": 78, "execution_count": 78,
@ -2801,7 +2801,7 @@
} }
}, },
"source": [ "source": [
"In order to re-produce the same random numbers in a simulation each time we run it, we set the **[random seed](https://en.wikipedia.org/wiki/Random_seed)**. It is good practice to do this at the beginning of a program or notebook. Then every time we re-start the program, we will get the exact same random numbers again. This becomes very important, for example, when we employ certain machine learning algorithms that rely on randomization, like the infamous [Random Forest](https://en.wikipedia.org/wiki/Random_forest), and want to obtain **re-producable** results.\n", "To reproduce the same random numbers in a simulation each time we run it, we set the **[random seed](https://en.wikipedia.org/wiki/Random_seed)**. It is good practice to do this at the beginning of a program or notebook. Then every time we re-start the program, we get the *same* random numbers again. This becomes very important, for example, when we employ machine learning algorithms that rely on randomization, like the infamous [Random Forest](https://en.wikipedia.org/wiki/Random_forest), and want to obtain **reproducable** results.\n",
"\n", "\n",
"The [random](https://docs.python.org/3/library/random.html) module provides the [random.seed()](https://docs.python.org/3/library/random.html#random.seed) function to do that." "The [random](https://docs.python.org/3/library/random.html) module provides the [random.seed()](https://docs.python.org/3/library/random.html#random.seed) function to do that."
] ]
@ -2899,11 +2899,11 @@
} }
}, },
"source": [ "source": [
"As the Python community is based around open source, many developers publish their code, for example, on the Python Package Index [PyPI](https://pypi.org) from where anyone may download and install it for free using command line based tools like [pip](https://pip.pypa.io/en/stable/) or [conda](https://conda.io/en/latest/). This way, we can always customize our Python installation even more. Managing many such packages is actually quite a deep topic on its own, sometimes fearfully called **[dependency hell](https://en.wikipedia.org/wiki/Dependency_hell)**.\n", "As the Python community is based around open source, many developers publish their code, for example, on the Python Package Index [PyPI](https://pypi.org) from where anyone may download and install it for free using command-line based tools like [pip](https://pip.pypa.io/en/stable/) or [conda](https://conda.io/en/latest/). This way, we can always customize our Python installation even more. Managing many such packages is quite a deep topic on its own, sometimes fearfully called **[dependency hell](https://en.wikipedia.org/wiki/Dependency_hell)**.\n",
"\n", "\n",
"The difference between the [standard library](https://docs.python.org/3/library/index.html) and such **third-party** packages is that in the first case the code goes through a much more formalized review process and is officially endorsed by the Python core developers. Yet, many third-party projects also offer the highest quality standards and a lot of such software is actually also relied on by many businesses and researchers.\n", "The difference between the [standard library](https://docs.python.org/3/library/index.html) and such **third-party** packages is that in the first case, the code goes through a much more formalized review process and is officially endorsed by the Python core developers. Yet, many third-party projects also offer the highest quality standards and are also relied on by many businesses and researchers.\n",
"\n", "\n",
"Throughout this book, we will look at many third-party libraries, mostly from Python's [scientific stack](https://scipy.org/about.html), a tightly coupled set of third-party libraries for storing **big data** efficiently ([numpy](http://www.numpy.org/)), \"wrangling\" ([pandas](https://pandas.pydata.org/)) and visualizing them ([matplotlib](https://matplotlib.org/) and [seaborn](https://seaborn.pydata.org/)), fitting classical statistical models ([statsmodels](http://www.statsmodels.org/)), training machine learning models ([sklearn](http://scikit-learn.org/)), and much more.\n", "Throughout this book, we will look at many third-party libraries, mostly from Python's [scientific stack](https://scipy.org/about.html), a tightly coupled set of third-party libraries for storing **big data** efficiently (e.g., [numpy](http://www.numpy.org/)), \"wrangling\" (e.g., [pandas](https://pandas.pydata.org/)) and visualizing them (e.g., [matplotlib](https://matplotlib.org/) or [seaborn](https://seaborn.pydata.org/)), fitting classical statistical models (e.g., [statsmodels](http://www.statsmodels.org/)), training machine learning models (e.g., [sklearn](http://scikit-learn.org/)), and much more.\n",
"\n", "\n",
"Below, we briefly show how to install third-party libraries." "Below, we briefly show how to install third-party libraries."
] ]
@ -2927,11 +2927,11 @@
} }
}, },
"source": [ "source": [
"[numpy](http://www.numpy.org/) is the de-facto standard in the Python world for handling **array-like** data. That is a fancy word for data that can be put into a matrix or vector format. We will look at it in depth in Chapter 9.\n", "[numpy](http://www.numpy.org/) is the de-facto standard in the Python world for handling **array-like** data. That is a fancy word for data that can be put into a matrix or vector format. We look at it in depth in Chapter 9.\n",
"\n", "\n",
"As [numpy](http://www.numpy.org/) is *not* in the [standard library](https://docs.python.org/3/library/index.html), it must be *manually* installed, for example, with the [pip](https://pip.pypa.io/en/stable/) tool. As mentioned in [Chapter 0](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/00_start_up.ipynb), to execute terminal commands from within a Jupyter notebook, we just need to start a code cell with an exclamation mark.\n", "As [numpy](http://www.numpy.org/) is *not* in the [standard library](https://docs.python.org/3/library/index.html), it must be *manually* installed, for example, with the [pip](https://pip.pypa.io/en/stable/) tool. As mentioned in [Chapter 0](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/00_start_up.ipynb), to execute terminal commands from within a Jupyter notebook, we start a code cell with an exclamation mark.\n",
"\n", "\n",
"If you are running this notebook with an installation of the [Anaconda Distribution](https://www.anaconda.com/distribution/), then [numpy](http://www.numpy.org/) is probably already installed. Running the cell below, will just confirm that." "If you are running this notebook with an installation of the [Anaconda Distribution](https://www.anaconda.com/distribution/), then [numpy](http://www.numpy.org/) is probably already installed. Running the cell below confirms that."
] ]
}, },
{ {
@ -2963,7 +2963,7 @@
} }
}, },
"source": [ "source": [
"[numpy](http://www.numpy.org/) is conventionally imported with the shorter **idiomatic** name `np`. The `as` in the import statement just changes the resulting variable name. It is a shortcut for the three lines `import numpy`, `np = numpy`, and `del numpy`." "[numpy](http://www.numpy.org/) is conventionally imported with the shorter **idiomatic** name `np`. The `as` in the import statement changes the resulting variable name. It is a shortcut for the three lines `import numpy`, `np = numpy`, and `del numpy`."
] ]
}, },
{ {
@ -3096,7 +3096,7 @@
"source": [ "source": [
"[numpy](http://www.numpy.org/) somehow magically adds new behavior to Python's built-in arithmetic operators. For example, we may now [scalar-multiply](https://en.wikipedia.org/wiki/Scalar_multiplication) `vec`.\n", "[numpy](http://www.numpy.org/) somehow magically adds new behavior to Python's built-in arithmetic operators. For example, we may now [scalar-multiply](https://en.wikipedia.org/wiki/Scalar_multiplication) `vec`.\n",
"\n", "\n",
"[numpy](http://www.numpy.org/)'s functions are implemented in highly optimized C code and therefore fast, especially when it comes to big data." "[numpy](http://www.numpy.org/)'s functions are implemented in highly optimized C code and, therefore, are fast, especially when dealing with bigger amounts of data."
] ]
}, },
{ {
@ -3236,13 +3236,13 @@
} }
}, },
"source": [ "source": [
"For sure, we can create our own modules and packages. In the repository's main directory, there is a [*sample_module.py*](https://github.com/webartifex/intro-to-python/blob/master/sample_module.py) file that contains, among others, a function equivalent to the final version of `average_evens()`. To be realistic, this sample module is structured in a modular manner with several functions building on each other. It is best to skim over it *now* before reading on.\n", "For sure, we can create local modules and packages. In the repository's main directory, there is a [*sample_module.py*](https://github.com/webartifex/intro-to-python/blob/master/sample_module.py) file that contains, among others, a function equivalent to the final version of `average_evens()`. To be realistic, this sample module is structured in a modular manner with several functions building on each other. It is best to skim over it *now* before reading on.\n",
"\n", "\n",
"To make code we put into a *.py* file available in our program, we import it as a module just as we did above with modules in the [standard library](https://docs.python.org/3/library/index.html) or third-party packages.\n", "To make code we put into a *.py* file available in our program, we import it as a module just as we did above with modules in the [standard library](https://docs.python.org/3/library/index.html) or third-party packages.\n",
"\n", "\n",
"The *name* to be imported is the file's name except for the *.py* part. In order for this to work, the file's name *must* adhere to the *same* rules as hold for [variable names](https://docs.python.org/3/reference/lexical_analysis.html#identifiers) in general.\n", "The *name* to be imported is the file's name except for the *.py* part. For this to work, the file's name *must* adhere to the *same* rules as hold for [variable names](https://docs.python.org/3/reference/lexical_analysis.html#identifiers) in general.\n",
"\n", "\n",
"What happens during an import is conceptually as follows. When Python sees the `import sample_module` part, it first creates a *new* object of type `module` in memory. This is effectively an *empty* namespace. Then, it executes the imported file's code from top to bottom. Whatever variables are still defined at the end of this, are put into the module's namespace. Only if the file's code does *not* raise an error, will Python make a variable in our current location (i.e., `mod` here) point to the created `module` object. Otherwise, it is discarded. In essence, it is as if we copied and pasted the file's code in place of the import statement. If we import an already imported module again, Python is smart enough to avoid doing all this work all over and does nothing." "What happens during an import is as follows. When Python sees the `import sample_module` part, it first creates a *new* object of type `module` in memory. This is effectively an *empty* namespace. Then, it executes the imported file's code from top to bottom. Whatever variables are still defined at the end of this, are put into the module's namespace. Only if the file's code does *not* raise an error, will Python make a variable in our current location (i.e., `mod` here) point to the created `module` object. Otherwise, it is discarded. In essence, it is as if we copied and pasted the file's code in place of the import statement. If we import an already imported module again, Python is smart enough to avoid doing all this work all over and does nothing."
] ]
}, },
{ {
@ -3292,7 +3292,7 @@
"source": [ "source": [
"Disregarding the dunder-style attributes, `mod` defines the five attributes `_default_scalar`, `_scaled_average`, `average`, `average_evens`, and `average_odds`, which are exactly the ones we would expect from reading the [*sample_module.py*](https://github.com/webartifex/intro-to-python/blob/master/sample_module.py) file.\n", "Disregarding the dunder-style attributes, `mod` defines the five attributes `_default_scalar`, `_scaled_average`, `average`, `average_evens`, and `average_odds`, which are exactly the ones we would expect from reading the [*sample_module.py*](https://github.com/webartifex/intro-to-python/blob/master/sample_module.py) file.\n",
"\n", "\n",
"An important convention when working with imported code is to *disregard* any attributes starting with an underscore `_`. These are considered **private** and constitute **implementation details** the author of the imported code might change in a future version of his software. We *must* not rely on them in any way.\n", "A convention when working with imported code is to *disregard* any attributes starting with an underscore `_`. These are considered **private** and constitute **implementation details** the author of the imported code might change in a future version of his software. We *must* not rely on them in any way.\n",
"\n", "\n",
"In contrast, the three remaining **public** attributes are the functions `average()`, `average_evens()`, and `average_odds()` that we may use after the import." "In contrast, the three remaining **public** attributes are the functions `average()`, `average_evens()`, and `average_odds()` that we may use after the import."
] ]
@ -3433,9 +3433,9 @@
} }
}, },
"source": [ "source": [
"Packages are a generalization of modules and we will look at one in Chapter 10 in detail. You may, however, already look at a [sample package](https://github.com/webartifex/intro-to-python/tree/master/sample_package) in the repository, which is nothing but a folder with *.py* files in it.\n", "Packages are a generalization of modules, and we look at one in detail in Chapter 10. You may, however, already look at a [sample package](https://github.com/webartifex/intro-to-python/tree/master/sample_package) in the repository, which is nothing but a folder with *.py* files in it.\n",
"\n", "\n",
"As a further references on modules, we refer to the [official tutorial](https://docs.python.org/3/tutorial/modules.html)." "As a further reading on modules and packages, we refer to the [official tutorial](https://docs.python.org/3/tutorial/modules.html)."
] ]
}, },
{ {
@ -3461,7 +3461,7 @@
"\n", "\n",
"Functions provide benefits as they:\n", "Functions provide benefits as they:\n",
"\n", "\n",
"- make programs easier to comprehend and debug for humans as they give names to the smaller parts of a larger program (i.e., they **modularize** a code base), and\n", "- make programs easier to comprehend and debug for humans as they give names to the smaller parts of a larger program (i.e., they **modularize** a codebase), and\n",
"- eliminate redundancies by allowing **re-use of code**.\n", "- eliminate redundancies by allowing **re-use of code**.\n",
"\n", "\n",
"Functions are **defined** once with the `def` statement. Then, they may be **called** many times with the call operator `()`.\n", "Functions are **defined** once with the `def` statement. Then, they may be **called** many times with the call operator `()`.\n",

View file

@ -324,9 +324,9 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"**Q11.5**: Using the [range()](https://docs.python.org/3/library/functions.html#func-range) built-in, write a `for`-loop and calculate the volume of a sphere with `radius = 42.0` for all `digits` from `1` through `20`. Print out each volume on a seperate line.\n", "**Q11.5**: Using the [range()](https://docs.python.org/3/library/functions.html#func-range) built-in, write a `for`-loop and calculate the volume of a sphere with `radius = 42.0` for all `digits` from `1` through `20`. Print out each volume on a separate line.\n",
"\n", "\n",
"Note: This is the first task where you actually need to use the [print()](https://docs.python.org/3/library/functions.html#print) built-in function." "Note: This is the first task where you need to use the built-in [print()](https://docs.python.org/3/library/functions.html#print) function."
] ]
}, },
{ {
@ -349,7 +349,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"**Q11.6**: What important lesson did you learn about the `float` type?" "**Q11.6**: What lesson did you learn about the `float` type?"
] ]
}, },
{ {

View file

@ -19,7 +19,7 @@
} }
}, },
"source": [ "source": [
"We analyzed every aspect of the `average_evens()` function in [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb) except for the `if` related parts. While it seems to intuitively do what we expect it to, there is a whole lot more to be learned from taking it apart. In particular, the `if` may occur within both a **statement** as in our introductory example in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb) but also an **expression** as in `average_evens()`. This is analogous as to how a noun in a natural language is *either* the subject of *or* an object in a sentence. What is common to both versions of the `if` is that it leads to code being executed for *parts* of the input only. It is our first way of **controlling** the **flow of execution** in a program.\n", "We analyzed every aspect of the `average_evens()` function in [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb) except for the `if` related parts. While it seems to do what we expect it to, there is a whole lot more we learn from taking it apart. In particular, the `if` may occur within both a **statement** or an **expression**, analogous as to how a noun in a natural language is *either* the subject of *or* an object in a sentence. What is common to both usages is that it leads to code being executed for *parts* of the input only. It is our first way of **controlling** the **flow of execution** in a program.\n",
"\n", "\n",
"After deconstructing `if` in the first part of this chapter, we take a close look at a similar concept, namely handling **exceptions**." "After deconstructing `if` in the first part of this chapter, we take a close look at a similar concept, namely handling **exceptions**."
] ]
@ -43,7 +43,7 @@
} }
}, },
"source": [ "source": [
"Any expression that is either true or not is called a **boolean expression**. If you think such expressions are boring or just not so useful, read a bit on [propositional logic](https://en.wikipedia.org/wiki/Propositional_calculus) and you will quickly realize how mathematicians and originally philosophers base their rules of how to prove or disprove a conclusion on simple true-or-false \"statements\" about the world. It is the underlying principle of all of reasoning.\n", "Any expression that is either true or not is called a **boolean expression**. It is such simple true-or-false \"statements\" about the world on which mathematicians, and originally philosophers, base their rules of reasoning: They are studied formally in the field of [propositional logic](https://en.wikipedia.org/wiki/Propositional_calculus).\n",
"\n", "\n",
"A trivial example involves the equality operator `==` that evaluates to either `True` or `False` depending on its operands \"comparing equal\" or not." "A trivial example involves the equality operator `==` that evaluates to either `True` or `False` depending on its operands \"comparing equal\" or not."
] ]
@ -104,7 +104,7 @@
} }
}, },
"source": [ "source": [
"The `==` operator handles objects of *different* type. This shows how it implements a notion of equality in line with how we humans think of things being equal or not. After all, `42` and `42.0` are totally different $0$s and $1$s for a computer and many programming languages would actually say `False` here! Technically, this is yet another example of operator overloading." "The `==` operator handles objects of *different* types: It implements a notion of equality in line with how humans think of things being equal or not. After all, `42` and `42.0` are different $0$s and $1$s for a computer and other programming languages might say `False` here! Technically, this is yet another example of operator overloading."
] ]
}, },
{ {
@ -139,7 +139,7 @@
} }
}, },
"source": [ "source": [
"There are, however, cases where even well-behaved Python does not make us happy. [Chapter 5](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/05_numbers.ipynb) will provide more insights on this \"bug\"." "There are, however, cases where even well-behaved Python does not make us happy. [Chapter 5](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/05_numbers.ipynb) provides more insights into this \"bug.\""
] ]
}, },
{ {
@ -174,7 +174,7 @@
} }
}, },
"source": [ "source": [
"`True` and `False` are special built-in *objects* of type `bool`." "`True` and `False` are built-in *objects* of type `bool`."
] ]
}, },
{ {
@ -189,7 +189,7 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"94697002906592" "94906834637792"
] ]
}, },
"execution_count": 5, "execution_count": 5,
@ -213,7 +213,7 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"94697002906560" "94906834637760"
] ]
}, },
"execution_count": 6, "execution_count": 6,
@ -281,11 +281,11 @@
} }
}, },
"source": [ "source": [
"Let's not confuse the boolean `False` with `None`, another special built-in object! We saw the latter before in [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb) as the *implicit* return value of a function without a `return` statement.\n", "Let's not confuse the boolean `False` with `None`, another built-in object! We saw the latter before in [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb) as the *implicit* return value of a function without a `return` statement.\n",
"\n", "\n",
"We might think of `None` in a boolean context indicating a \"maybe\" or even an \"unknown\" answer; however, for Python, there are no \"maybe\" or \"unknown\" objects as we will see further below!\n", "We might think of `None` in a boolean context indicating a \"maybe\" or even an \"unknown\" answer; however, for Python, there are no \"maybe\" or \"unknown\" objects, as we see further below!\n",
"\n", "\n",
"Whereas `False` is of type `bool`, `None` is of type `NoneType`. So, they are totally unrelated. On the contrary, as both `True` and `False` are of the same type, we could call them \"siblings\"." "Whereas `False` is of type `bool`, `None` is of type `NoneType`. So, they are unrelated. On the contrary, as both `True` and `False` are of the same type, we could call them \"siblings.\""
] ]
}, },
{ {
@ -313,7 +313,7 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"94697002893552" "94906834624752"
] ]
}, },
"execution_count": 10, "execution_count": 10,
@ -357,7 +357,7 @@
} }
}, },
"source": [ "source": [
"`True`, `False`, and `None` have the property that they each exist in memory only *once*. Objects designed this way are so-called **singletons**. This **[design pattern](https://en.wikipedia.org/wiki/Design_Patterns)** was originally developed to keep a program's memory usage at a minimum. It may only be employed in situations where we know that an object will *not* mutate its value (i.e., to re-use the bag analogy from [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb), no flipping of $0$s and $1$s in the bag is allowed). In languages \"closer\" to the memory like C we would have to code this singleton logic ourselves but Python has this already built in for *some* types.\n", "`True`, `False`, and `None` have the property that they each exist in memory only *once*. Objects designed this way are so-called **singletons**. This **[design pattern](https://en.wikipedia.org/wiki/Design_Patterns)** was originally developed to keep a program's memory usage at a minimum. It may only be employed in situations where we know that an object does *not* mutate its value (i.e., to re-use the bag analogy from [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb), no flipping of $0$s and $1$s in the bag is allowed). In languages \"closer\" to the memory like C, we would have to code this singleton logic ourselves, but Python has this built in for *some* types.\n",
"\n", "\n",
"We verify this with either the `is` operator or by comparing memory addresses." "We verify this with either the `is` operator or by comparing memory addresses."
] ]
@ -523,7 +523,7 @@
} }
}, },
"source": [ "source": [
"The \"less than\" `<` or \"greater than\" `>` operators on their own mean \"strictly less than\" or \"strictly greater than\" but may be combined with the equality operator into just `<=` and `>=`. This is a shortcut for using the logical `or` operator as described in the next section." "The \"less than\" `<` or \"greater than\" `>` operators mean \"strictly less than\" or \"strictly greater than\" but may be combined with the equality operator into just `<=` and `>=`. This is a shortcut for using the logical `or` operator as described in the next section."
] ]
}, },
{ {
@ -643,7 +643,7 @@
"source": [ "source": [
"Boolean expressions may be combined or negated with the **logical operators** `and`, `or`, and `not` to form new boolean expressions. Of course, this may be done *recursively* as well to obtain boolean expressions of arbitrary complexity.\n", "Boolean expressions may be combined or negated with the **logical operators** `and`, `or`, and `not` to form new boolean expressions. Of course, this may be done *recursively* as well to obtain boolean expressions of arbitrary complexity.\n",
"\n", "\n",
"Their usage is similar to how the equivalent words are used in plain English:\n", "Their usage is similar to how the equivalent words are used in everyday English:\n",
"\n", "\n",
"- `and` evaluates to `True` if *both* sub-expressions evaluate to `True` and `False` otherwise,\n", "- `and` evaluates to `True` if *both* sub-expressions evaluate to `True` and `False` otherwise,\n",
"- `or` evaluates to `True` if either one *or* both sub-expressions evaluate to `True` and `False` otherwise, and\n", "- `or` evaluates to `True` if either one *or* both sub-expressions evaluate to `True` and `False` otherwise, and\n",
@ -672,7 +672,7 @@
} }
}, },
"source": [ "source": [
"Relational operators have a **[higher precedence](https://docs.python.org/3/reference/expressions.html#operator-precedence)** over logical operators. So the following expression means what we intuitively think it does." "Relational operators have **[higher precedence](https://docs.python.org/3/reference/expressions.html#operator-precedence)** over logical operators. So the following expression means what we intuitively think it does."
] ]
}, },
{ {
@ -707,7 +707,7 @@
} }
}, },
"source": [ "source": [
"However, sometimes it is good to use *parentheses* around each sub-expression for clarity." "However, sometimes, it is good to use *parentheses* around each sub-expression for clarity."
] ]
}, },
{ {
@ -742,7 +742,7 @@
} }
}, },
"source": [ "source": [
"This is especially useful when several logical operators are combined." "This is especially the case when several logical operators are combined."
] ]
}, },
{ {
@ -825,9 +825,9 @@
} }
}, },
"source": [ "source": [
"For even better readability, [some practitioners](https://llewellynfalco.blogspot.com/2016/02/dont-use-greater-than-sign-in.html) suggest to *never* use the `>` and `>=` operators (note that the included example is written in [Java](https://en.wikipedia.org/wiki/Java_%28programming_language%29) and `&&` means `and` and `||` means `or`).\n", "For even better readability, some practitioners suggest to *never* use the `>` and `>=` operators (cf., [source](https://llewellynfalco.blogspot.com/2016/02/dont-use-greater-than-sign-in.html); note that the included example is written in [Java](https://en.wikipedia.org/wiki/Java_%28programming_language%29) where `&&` means `and` and `||` means `or`).\n",
"\n", "\n",
"Python allows **chaining** relational operators that are combined with the `and` operator. For example, the following two cells implement the same logic where the second is a lot easier to read." "Python allows **chaining** relational operators that are combined with the `and` operator. For example, the following two cells implement the same logic, where the second is a lot easier to read."
] ]
}, },
{ {
@ -897,9 +897,9 @@
} }
}, },
"source": [ "source": [
"The operands of the logical operators do not actually have to be *boolean* expressions as defined above but may be *any* kind of expression. If a sub-expression does *not* evaluate to an object of type `bool`, Python automatically casts the resulting object as such.\n", "The operands of the logical operators do not need to be *boolean* expressions as defined above but may be *any* expression. If a sub-expression does *not* evaluate to an object of type `bool`, Python automatically casts it as such.\n",
"\n", "\n",
"For example, any non-zero numeric object becomes `True`. While this behavior allows writing conciser and thus more \"beautiful\" code, it is also a common source of confusion." "For example, any non-zero numeric object becomes `True`. While this behavior allows writing more concise and thus more \"beautiful\" code, it is also a common source of confusion. `(x - 9)` is cast as `True` and then the overall expression evaluates to `True` as well."
] ]
}, },
{ {
@ -923,7 +923,7 @@
} }
], ],
"source": [ "source": [
"(x - 9) and (y < 100) # = 33 and (y < 100)" "(x - 9) and (y < 100) # = 33 and (y < 100) = 33 and True"
] ]
}, },
{ {
@ -934,7 +934,7 @@
} }
}, },
"source": [ "source": [
"Whenever we are unsure as to how Python will evaluate a non-boolean expression in a boolean context, the [bool()](https://docs.python.org/3/library/functions.html#bool) built-in allows us to check it ourselves." "Whenever we are unsure as to how Python evaluates a non-boolean expression in a boolean context, the [bool()](https://docs.python.org/3/library/functions.html#bool) built-in allows us to check it ourselves."
] ]
}, },
{ {
@ -1028,7 +1028,7 @@
} }
}, },
"source": [ "source": [
"In a boolean context, `None` is casted as `False`! So, `None` is really *not* a \"maybe\" answer but a \"no\"." "In a boolean context, `None` is cast as `False`! So, `None` is *not* a \"maybe\" answer but a \"no.\""
] ]
}, },
{ {
@ -1063,7 +1063,7 @@
} }
}, },
"source": [ "source": [
"Another good rule to know is that container types (e.g., `list`) evaluate to `True` whenever they are not empty and `False` otherwise." "Another good rule to know is that container types (e.g., `list`) evaluate to `True` whenever they are *not* empty and `False` if they are."
] ]
}, },
{ {
@ -1125,6 +1125,388 @@
"Pythonistas often use the terms **truthy** or **falsy** to describe a non-boolean expression's behavior when used in place of a boolean one." "Pythonistas often use the terms **truthy** or **falsy** to describe a non-boolean expression's behavior when used in place of a boolean one."
] ]
}, },
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"### Short-Circuiting"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"When evaluating boolean expressions with logical operators in it, Python follows the **[short-circuiting](https://en.wikipedia.org/wiki/Short-circuit_evaluation)** strategy: First, the inner-most sub-expressions are evaluated. Second, with identical **[operator precedence](https://docs.python.org/3/reference/expressions.html#operator-precedence)**, evaluation goes from left to right. Once it is clear what the overall truth value is, no more sub-expressions are evaluated, and the result is *immediately* returned.\n",
"\n",
"In summary, data science practitioners must know *how* the following two generic expressions are evaluated:\n",
"\n",
"- `x or y`: The `y` expression is evaluated *only if* `x` evaluates to `False`, in which case `y` is returned; otherwise, `x` is returned *without* even looking at `y`.\n",
"- `x and y`: The `y` expression is evaluated *only if* `x` evaluates to `True`. Then, if `y` also evaluates to `True`, it is returned; otherwise, `x` is returned.\n",
"\n",
"Let's look at a couple of examples."
]
},
{
"cell_type": "code",
"execution_count": 36,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"x = 0\n",
"y = 1\n",
"z = 2"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"We define a helper function `expr()` that prints out the only argument it is passed before returning it. With `expr()`, we can see if a sub-expression is evaluated or not."
]
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"def expr(arg):\n",
" \"\"\"Print and return the only argument.\"\"\"\n",
" print(\"Arg:\", arg)\n",
" return arg"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"With the `or` operator, the first sub-expression that evaluates to `True` is returned."
]
},
{
"cell_type": "code",
"execution_count": 38,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Arg: 0\n",
"Arg: 1\n"
]
},
{
"data": {
"text/plain": [
"1"
]
},
"execution_count": 38,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"expr(x) or expr(y)"
]
},
{
"cell_type": "code",
"execution_count": 39,
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Arg: 1\n"
]
},
{
"data": {
"text/plain": [
"1"
]
},
"execution_count": 39,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"expr(y) or expr(z)"
]
},
{
"cell_type": "code",
"execution_count": 40,
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Arg: 0\n",
"Arg: 1\n"
]
},
{
"data": {
"text/plain": [
"1"
]
},
"execution_count": 40,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"expr(x) or expr(y) or expr(z)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"If all sub-expressions evaluate to `False`, the last one is the result."
]
},
{
"cell_type": "code",
"execution_count": 41,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Arg: False\n",
"Arg: []\n",
"Arg: 0\n"
]
},
{
"data": {
"text/plain": [
"0"
]
},
"execution_count": 41,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"expr(False) or expr([]) or expr(x)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"With the `and` operator, the first sub-expression that evaluates to `False` is returned."
]
},
{
"cell_type": "code",
"execution_count": 42,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Arg: 0\n"
]
},
{
"data": {
"text/plain": [
"0"
]
},
"execution_count": 42,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"expr(x) and expr(y)"
]
},
{
"cell_type": "code",
"execution_count": 43,
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Arg: 1\n",
"Arg: 0\n"
]
},
{
"data": {
"text/plain": [
"0"
]
},
"execution_count": 43,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"expr(y) and expr(x)"
]
},
{
"cell_type": "code",
"execution_count": 44,
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Arg: 2\n",
"Arg: 1\n",
"Arg: 0\n"
]
},
{
"data": {
"text/plain": [
"0"
]
},
"execution_count": 44,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"expr(z) and expr(y) and expr(x)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"If all sub-expressions evaluate to `True`, the last one is returned."
]
},
{
"cell_type": "code",
"execution_count": 45,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Arg: 1\n",
"Arg: 2\n"
]
},
{
"data": {
"text/plain": [
"2"
]
},
"execution_count": 45,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"expr(y) and expr(z)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The crucial takeaway is that Python does *not* necessarily evaluate *all* sub-expressions and, therefore, our code should never rely on that assumption."
]
},
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": { "metadata": {
@ -1144,9 +1526,9 @@
} }
}, },
"source": [ "source": [
"In order to write useful programs, we need to control the flow of execution, for example, to react to user input. The logic by which a program does that is referred to as **business logic**.\n", "To write useful programs, we need to control the flow of execution, for example, to react to user input. The logic by which a program does that is referred to as **business logic**.\n",
"\n", "\n",
"One major language construct to do so is the **[conditional statement](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement)**, or `if` statement for short. It consists of:\n", "One language construct to do so is the **[conditional statement](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement)**, or `if` statement for short. It consists of:\n",
"\n", "\n",
"- *one* mandatory `if`-clause,\n", "- *one* mandatory `if`-clause,\n",
"- an *arbitrary* number of `elif`-clauses (i.e. \"else if\"), and\n", "- an *arbitrary* number of `elif`-clauses (i.e. \"else if\"), and\n",
@ -1154,14 +1536,14 @@
"\n", "\n",
"The `if`- and `elif`-clauses each specify one *boolean* expression, also called **condition**, while the `else`-clause serves as the \"catch everything else\" case.\n", "The `if`- and `elif`-clauses each specify one *boolean* expression, also called **condition**, while the `else`-clause serves as the \"catch everything else\" case.\n",
"\n", "\n",
"In terms of syntax, the header lines end with a colon and the code blocks are indented.\n", "In terms of syntax, the header lines end with a colon, and the code blocks are indented.\n",
"\n", "\n",
"In contrast to our intuitive interpretation in natural languages, only the code in *one* of the alternatives, also called **branches**, is executed. To be precise, it is always the code in the first branch whose condition evaluates to `True`." "In contrast to our intuitive interpretation in natural languages, only the code in *one* of the alternatives, also called **branches**, is executed. To be precise, it is always the code in the first branch whose condition evaluates to `True`."
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 36, "execution_count": 46,
"metadata": { "metadata": {
"code_folding": [], "code_folding": [],
"slideshow": { "slideshow": {
@ -1175,7 +1557,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 37, "execution_count": 47,
"metadata": { "metadata": {
"code_folding": [], "code_folding": [],
"slideshow": { "slideshow": {
@ -1212,12 +1594,12 @@
"source": [ "source": [
"In many situations, we only need a reduced form of the `if` statement.\n", "In many situations, we only need a reduced form of the `if` statement.\n",
"\n", "\n",
"We could **inject** code only at random to, for example, implement some sort of [A/B testing](https://en.wikipedia.org/wiki/A/B_testing)." "We could **inject** code only at random, for example, to implement an [A/B testing](https://en.wikipedia.org/wiki/A/B_testing) strategy."
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 38, "execution_count": 48,
"metadata": { "metadata": {
"slideshow": { "slideshow": {
"slide_type": "slide" "slide_type": "slide"
@ -1230,7 +1612,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 39, "execution_count": 49,
"metadata": { "metadata": {
"slideshow": { "slideshow": {
"slide_type": "-" "slide_type": "-"
@ -1239,7 +1621,7 @@
"outputs": [], "outputs": [],
"source": [ "source": [
"if random.random() > 0.5:\n", "if random.random() > 0.5:\n",
" print(\"You will read this just as often as you see heads when tossing a coin\")" " print(\"You read this just as often as you see heads when tossing a coin\")"
] ]
}, },
{ {
@ -1255,7 +1637,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 40, "execution_count": 50,
"metadata": { "metadata": {
"slideshow": { "slideshow": {
"slide_type": "skip" "slide_type": "skip"
@ -1292,7 +1674,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 41, "execution_count": 51,
"metadata": { "metadata": {
"slideshow": { "slideshow": {
"slide_type": "slide" "slide_type": "slide"
@ -1303,7 +1685,7 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"z is odd\n" "z is positive\n"
] ]
} }
], ],
@ -1328,14 +1710,14 @@
} }
}, },
"source": [ "source": [
"A good way to make this code more readable is to introduce **temporary variables** *in combination* with the `and` operator to **flatten** the branching logic. The `if` statement then reads almost like plain English. In contrast to many other languages, creating variables is a computationally *cheap* operation in Python and also helps to document the code *inline* with meaningful variable names.\n", "A way to make this code more readable is to introduce **temporary variables** *in combination* with the `and` operator to **flatten** the branching logic. The `if` statement then reads almost like plain English. In contrast to many other languages, creating variables is a computationally *cheap* operation in Python and also helps to document the code *inline* with meaningful variable names.\n",
"\n", "\n",
"Flattening the logic *without* temporary variables could actually lead to more sub-expressions in the conditions be evaluated than necessary. Do you see why?" "Flattening the logic *without* temporary variables could lead to *more* sub-expressions in the conditions be evaluated than necessary. Do you see why?"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 42, "execution_count": 52,
"metadata": { "metadata": {
"slideshow": { "slideshow": {
"slide_type": "slide" "slide_type": "slide"
@ -1384,7 +1766,7 @@
} }
}, },
"source": [ "source": [
"When all we do with an `if` statement is to assign an object to a variable with respect to a single true-or-false condition (i.e., a binary choice), there is a shortcut for that: We could simply assign the result of a so-called **conditional expression**, or `if` expression for short, to the variable.\n", "When all we do with an `if` statement is to assign an object to a variable according to a single true-or-false condition (i.e., a binary choice), there is a shortcut: We assign the variable the result of a so-called **conditional expression**, or `if` expression for short, instead.\n",
"\n", "\n",
"Think of a situation where we evaluate a piece-wise functional relationship $y = f(x)$ at a given $x$, for example:" "Think of a situation where we evaluate a piece-wise functional relationship $y = f(x)$ at a given $x$, for example:"
] ]
@ -1408,7 +1790,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 43, "execution_count": 53,
"metadata": { "metadata": {
"slideshow": { "slideshow": {
"slide_type": "slide" "slide_type": "slide"
@ -1432,7 +1814,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 44, "execution_count": 54,
"metadata": { "metadata": {
"slideshow": { "slideshow": {
"slide_type": "-" "slide_type": "-"
@ -1448,7 +1830,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 45, "execution_count": 55,
"metadata": { "metadata": {
"slideshow": { "slideshow": {
"slide_type": "-" "slide_type": "-"
@ -1461,7 +1843,7 @@
"9" "9"
] ]
}, },
"execution_count": 45, "execution_count": 55,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@ -1483,7 +1865,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 46, "execution_count": 56,
"metadata": { "metadata": {
"slideshow": { "slideshow": {
"slide_type": "fragment" "slide_type": "fragment"
@ -1496,7 +1878,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 47, "execution_count": 57,
"metadata": { "metadata": {
"slideshow": { "slideshow": {
"slide_type": "skip" "slide_type": "skip"
@ -1509,7 +1891,7 @@
"9" "9"
] ]
}, },
"execution_count": 47, "execution_count": 57,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@ -1531,7 +1913,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 48, "execution_count": 58,
"metadata": { "metadata": {
"slideshow": { "slideshow": {
"slide_type": "fragment" "slide_type": "fragment"
@ -1544,7 +1926,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 49, "execution_count": 59,
"metadata": { "metadata": {
"slideshow": { "slideshow": {
"slide_type": "skip" "slide_type": "skip"
@ -1557,7 +1939,7 @@
"9" "9"
] ]
}, },
"execution_count": 49, "execution_count": 59,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@ -1574,7 +1956,7 @@
} }
}, },
"source": [ "source": [
"Conditional expressions may not only be used in the way described in this section. We already saw them as part of a list comprehension in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb) and [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb)." "Conditional expressions may not only be used in the way described in this section. We already saw them as part of a *list comprehension* in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb) and [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb) and revisit this in Chapter 7 in greater detail."
] ]
}, },
{ {
@ -1596,16 +1978,16 @@
} }
}, },
"source": [ "source": [
"In the previous two chapters we already encountered a couple of *runtime* errors. A natural urge we might have after reading about conditional statements is to write code that somehow reacts to the occurence of such exceptions. All we need is a way to formulate a condition for that.\n", "In the previous two chapters, we encountered a couple of *runtime* errors. A natural urge we might have after reading about conditional statements is to write code that somehow reacts to the occurrence of such exceptions. All we need is a way to formulate a condition for that.\n",
"\n", "\n",
"For sure, this is such a common thing to do that Python provides its own language construct for it, namely the `try` [statement](https://docs.python.org/3/reference/compound_stmts.html#the-try-statement).\n", "For sure, this is such a common thing to do that Python provides a language construct for it, namely the `try` [statement](https://docs.python.org/3/reference/compound_stmts.html#the-try-statement).\n",
"\n", "\n",
"In its simplest form, it comes with just two branches: `try` and `except`. The following basically tells Python to execute the code in the `try`-branch and if *anything* goes wrong, continue in the `except`-branch instead of **raising** an error to us. Of course, if nothing goes wrong, the `except`-branch is *not* executed." "In its simplest form, it comes with just two branches: `try` and `except`. The following tells Python to execute the code in the `try`-branch, and if *anything* goes wrong, continue in the `except`-branch instead of **raising** an error to us. Of course, if nothing goes wrong, the `except`-branch is *not* executed."
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 50, "execution_count": 60,
"metadata": { "metadata": {
"slideshow": { "slideshow": {
"slide_type": "slide" "slide_type": "slide"
@ -1618,7 +2000,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 51, "execution_count": 61,
"metadata": { "metadata": {
"slideshow": { "slideshow": {
"slide_type": "-" "slide_type": "-"
@ -1648,16 +2030,16 @@
} }
}, },
"source": [ "source": [
"However, it is good practice to *not* **handle** *any* possible exception but only the ones we may expect from the code in the `try`-branch. The reasoning why this is done is a bit involved. We only remark here that the code base becomes easier to understand as we clearly communicate to any human reader what could go wrong during execution. Python comes with a lot of [built-in exceptions](https://docs.python.org/3/library/exceptions.html#concrete-exceptions) that we should familiarize ourselves with.\n", "However, it is good practice *not* to **handle** *any* possible exception but only the ones we may *expect* from the code in the `try`-branch. The reasoning why this is done is a bit involved. We only remark that the codebase becomes easier to understand as we communicate to any human reader what could go wrong during execution in an *explicit* way. Python comes with a lot of [built-in exceptions](https://docs.python.org/3/library/exceptions.html#concrete-exceptions) that we should familiarize ourselves with.\n",
"\n", "\n",
"Another good practice is to always keep the code in the `try`-branch short so as to not accidently handle an exception we do not want to handle.\n", "Another good practice is to always keep the code in the `try`-branch short to not *accidentally* handle an exception we do *not* want to handle.\n",
"\n", "\n",
"In the example, we are dividing numbers and may therefore expect a `ZeroDivisionError`." "In the example, we are dividing numbers and may expect a `ZeroDivisionError`."
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 52, "execution_count": 62,
"metadata": { "metadata": {
"slideshow": { "slideshow": {
"slide_type": "fragment" "slide_type": "fragment"
@ -1687,16 +2069,16 @@
} }
}, },
"source": [ "source": [
"Often, we may have to run some code *independent* of an exception occuring, for example, to close a connection to a database. To achieve that, we add a `finally`-branch to the `try` statement.\n", "Often, we may have to run some code *independent* of an exception occurring, for example, to close a connection to a database. To achieve that, we add a `finally`-branch to the `try` statement.\n",
"\n", "\n",
"Similarly, we may have to run some code *only if* no exception occurs but we do not want to put it in the `try`-branch as per the good practice mentioned above. To achieve that, we add an `else`-branch to the `try` statement.\n", "Similarly, we may have to run some code *only if* no exception occurs, but we do not want to put it in the `try`-branch as per the good practice mentioned above. To achieve that, we add an `else`-branch to the `try` statement.\n",
"\n", "\n",
"To showcase everything together, we look at one last example. To spice it up a bit, we randomize the input. So run the cell several times and see for yourself. It's actually quite easy." "To showcase everything together, we look at one last example. To spice it up a bit, we randomize the input. So run the cell several times and see for yourself."
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 53, "execution_count": 63,
"metadata": { "metadata": {
"slideshow": { "slideshow": {
"slide_type": "slide" "slide_type": "slide"
@ -1747,9 +2129,9 @@
"- **boolean expressions** evaluate to either `True` or `False`\n", "- **boolean expressions** evaluate to either `True` or `False`\n",
"- **relational operators** compare operands according to \"human\" interpretations\n", "- **relational operators** compare operands according to \"human\" interpretations\n",
"- **logical operators** combine boolean sub-expressions to more \"complex\" expressions\n", "- **logical operators** combine boolean sub-expressions to more \"complex\" expressions\n",
"- the **conditional statement** is a *major* concept to **control** the **flow of execution** depending on some **conditions**\n", "- the **conditional statement** allows **controlling** the **flow of execution** depending on some **conditions**\n",
"- a **conditional expression** is a short form of a conditional statement\n", "- a **conditional expression** is a short form of a conditional statement\n",
"- **exception handling** is also a common way of **controlling** the **flow of execution**, in particular if we have to be prepared for bad input data" "- **exception handling** is also a common way of **controlling** the **flow of execution**, in particular, if we have to be prepared for bad input data"
] ]
} }
], ],

View file

@ -19,7 +19,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Read [Chapter 3](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/03_conditionals.ipynb) of the book. Then work through the seven review questions." "Read [Chapter 3](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/03_conditionals.ipynb) of the book. Then work through the eight review questions."
] ]
}, },
{ {
@ -68,7 +68,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"**Q3**: Explain how the conceptual difference between a **statement** and an **expression** relates to the difference between a **conditional statement** and a **conditional expression**." "**Q3**: Describe in your own words the concept of **short-circuiting**! What does it imply for an individual sub-expression in a boolean expression?"
] ]
}, },
{ {
@ -82,7 +82,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"**Q4**: Why is the use of **temporary variables** encouraged in Python?" "**Q4**: Explain how the conceptual difference between a **statement** and an **expression** relates to the difference between a **conditional statement** and a **conditional expression**."
] ]
}, },
{ {
@ -96,7 +96,21 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"**Q5**: What does the `finally`-branch enforce in this code snippet? How can a `try` statement be useful *without* an `except`-branch?" "**Q5**: Why is the use of **temporary variables** encouraged in Python?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q6**: What does the `finally`-branch enforce in this code snippet? How can a `try` statement be useful *without* an `except`-branch?"
] ]
}, },
{ {
@ -136,7 +150,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"**Q6**: The objects `True`, `False`, and `None` represent the idea of \"yes\", \"no\", and \"maybe\" answers in a natural language.\n", "**Q7**: The objects `True`, `False`, and `None` represent the idea of *yes*, *no*, and *maybe* answers in a natural language.\n",
"\n", "\n",
"Hint: you also respond with a code cell." "Hint: you also respond with a code cell."
] ]
@ -152,7 +166,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"**Q7**: The `try` statement is useful for handling **syntax** errors." "**Q8**: The `try` statement is useful for handling **syntax** errors."
] ]
}, },
{ {
@ -180,7 +194,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"**Q8.1**: Write a function `discounted_price()` that takes the positional arguments `unit_price` (of type `float`) and `quantity` (of type `int`) and implements a discount scheme for a line item in a customer order as follows:\n", "**Q9.1**: Write a function `discounted_price()` that takes the positional arguments `unit_price` (of type `float`) and `quantity` (of type `int`) and implements a discount scheme for a line item in a customer order as follows:\n",
"\n", "\n",
"- if the unit price is over 100 dollars, grant 10% relative discount\n", "- if the unit price is over 100 dollars, grant 10% relative discount\n",
"- if a customer orders more than 10 items, one in every five items is for free\n", "- if a customer orders more than 10 items, one in every five items is for free\n",
@ -218,7 +232,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"**Q8.2**: Calculate the final price for the following line items of an order:\n", "**Q9.2**: Calculate the final price for the following line items of an order:\n",
"- $7$ smartphones @ $99.00$ USD\n", "- $7$ smartphones @ $99.00$ USD\n",
"- $3$ workstations @ $999.00$ USD\n", "- $3$ workstations @ $999.00$ USD\n",
"- $19$ GPUs @ $879.95$ USD\n", "- $19$ GPUs @ $879.95$ USD\n",
@ -265,7 +279,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"**Q8.3**: Re-calculate the last two line items with order quantities of $20$ and $15$. What do you observe?" "**Q9.3**: Re-calculate the last two line items with order quantities of $20$ and $15$. What do you observe?"
] ]
}, },
{ {
@ -297,7 +311,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"**Q8.4**: Looking at the `if`-`else`-logic in the function, why do you think the four example line items in **Q8.2** were chosen as they were?" "**Q9.4**: Looking at the `if`-`else`-logic in the function, why do you think the four example line items in **Q9.2** were chosen as they were?"
] ]
}, },
{ {
@ -318,16 +332,16 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"The kids game [Fizz Buzz](https://en.wikipedia.org/wiki/Fizz_buzz) is said to be often used in job interviews for entry level positions. However, opinions vary as to how good of a test it actually is ([source](https://news.ycombinator.com/item?id=16446774)).\n", "The kids game [Fizz Buzz](https://en.wikipedia.org/wiki/Fizz_buzz) is said to be often used in job interviews for entry-level positions. However, opinions vary as to how good of a test it is (cf., [source](https://news.ycombinator.com/item?id=16446774)).\n",
"\n", "\n",
"In its simplest form, a group of people start counting upwards in an alternating fashion. Whenever a number is divisible by $3$, the person must say \"Fizz\" instead of the number. The same holds for numbers divisible by $5$ when the person must say \"Buzz\". If a number is divisible by both numbers, one must say \"FizzBuzz\". Probably, this game would also make a good drinking game with the \"right\" beverages." "In its simplest form, a group of people starts counting upwards in an alternating fashion. Whenever a number is divisible by $3$, the person must say \"Fizz\" instead of the number. The same holds for numbers divisible by $5$ when the person must say \"Buzz.\" If a number is divisible by both numbers, one must say \"FizzBuzz.\" Probably, this game would also make a good drinking game with the \"right\" beverages."
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"**Q9.1**: First, create a list `numbers` with the numbers from 1 through 100. You could type all numbers manually but there is of course a smarter way. The built-in [range()](https://docs.python.org/3/library/functions.html#func-range) may be useful here. Read how it works in the documentation. To make the output of [range()](https://docs.python.org/3/library/functions.html#func-range) a `list` object, you have to wrap it with the [list()](https://docs.python.org/3/library/functions.html#func-list) built-in (i.e., `list(range(...))`)." "**Q10.1**: First, create a list `numbers` with the numbers from 1 through 100. You could type all numbers manually, but there is, of course, a smarter way. The built-in [range()](https://docs.python.org/3/library/functions.html#func-range) may be useful here. Read how it works in the documentation. To make the output of [range()](https://docs.python.org/3/library/functions.html#func-range) a `list` object, you have to wrap it with the [list()](https://docs.python.org/3/library/functions.html#func-list) built-in (i.e., `list(range(...))`)."
] ]
}, },
{ {
@ -343,11 +357,11 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"**Q9.2**: Loop over the `numbers` list and replace numbers for which one of the two (or both) conditions apply with text strings `\"Fizz\"`, `\"Buzz\"`, or `\"FizzBuzz\"` using the indexing operator `[]` and the assignment statement `=`.\n", "**Q10.2**: Loop over the `numbers` list and replace numbers for which one of the two (or both) conditions apply with text strings `\"Fizz\"`, `\"Buzz\"`, or `\"FizzBuzz\"` using the indexing operator `[]` and the assignment statement `=`.\n",
"\n", "\n",
"In Chapter 1 we saw that Python starts indexing with `0` as the first element. Keep that in mind.\n", "In [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb), we saw that Python starts indexing with `0` as the first element. Keep that in mind.\n",
"\n", "\n",
"So in each iteration of the `for`-loop you have to determine an `index` variable as well as checking the actual `number` for its divisors.\n", "So in each iteration of the `for`-loop, you have to determine an `index` variable as well as check the actual `number` for its divisors.\n",
"\n", "\n",
"Hint: the order of the conditions is important!" "Hint: the order of the conditions is important!"
] ]
@ -372,7 +386,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"**Q9.3**: Create a loop that prints out either the number or any of the Fizz Buzz substitutes. Do it in such a way that we do not end up with 100 lines of output here." "**Q10.3**: Create a loop that prints out either the number or any of the Fizz Buzz substitutes. Do it in such a way that we do not end up with 100 lines of output here."
] ]
}, },
{ {

View file

@ -19,11 +19,11 @@
} }
}, },
"source": [ "source": [
"While controlling the flow of execution with an `if` statement is definitely a must-have building block in any programming language, it alone does not allow us to run a block of code repetitively and we need to be able to do exactly that in order to write useful software.\n", "While controlling the flow of execution with an `if` statement is a must-have building block in any programming language, it alone does not allow us to run a block of code repetitively, and we need to be able to do precisely that to write useful software.\n",
"\n", "\n",
"You might think that the `for` statement shown in some examples before is the missing piece in the puzzle. However, we can actually live without it and postpone its official introduction until the second half of this chapter.\n", "The `for` statement shown in some examples before might be the missing piece in the puzzle. However, we can live without it and postpone its official introduction until the second half of this chapter.\n",
"\n", "\n",
"Instead, we dive into the big idea of **iteration** by studying the concept of **recursion** first. This is quite the opposite of many introductory books on programming that only treat the latter as a nice-to-have artifact, if at all. Yet, understanding recursion sharpens one's mind and contributes to seeing problems from a different angle." "Instead, we dive into the big idea of **iteration** by studying the concept of **recursion** first. This order is opposite to many other introductory books that only treat the latter as a nice-to-have artifact, if at all. Yet, understanding recursion sharpens one's mind and contributes to seeing problems from a different angle."
] ]
}, },
{ {
@ -45,11 +45,11 @@
} }
}, },
"source": [ "source": [
"A function that calls itself is **recursive** and the process of executing such a function it is called **recursion**.\n", "A function that calls itself is **recursive**, and the process of executing such a function is called **recursion**.\n",
"\n", "\n",
"Recursive functions contain some form of a conditional check (e.g., `if` statement) to identify a **base case** that ends the recursion *when* reached. Otherwise, the function would keep calling itself forever.\n", "Recursive functions contain some form of a conditional check (e.g., `if` statement) to identify a **base case** that ends the recursion *when* reached. Otherwise, the function would keep calling itself forever.\n",
"\n", "\n",
"The meaning of the word *recursive* is similar to *circular*. However, a truly circular definition is not very helpful and we think of a recursive function as kind of a \"circular function with a way out at the end\"." "The meaning of the word *recursive* is similar to *circular*. However, a truly circular definition is not very helpful, and we think of a recursive function as kind of a \"circular function with a way out at the end.\""
] ]
}, },
{ {
@ -71,7 +71,7 @@
} }
}, },
"source": [ "source": [
"A rather trivial toy example illustrates the important aspects concretely: If called with any positive integer as its `n` argument, `countdown()` just prints that number and calls itself with the *new* `n` being the *old* `n` minus `1`. This continues until `countdown()` is called with `n=0`. Then, the flow of execution hits the base case and the function calls stop." "A rather trivial toy example illustrates the important aspects concretely: If called with any positive integer as its `n` argument, `countdown()` just prints that number and calls itself with the *new* `n` being the *old* `n` minus `1`. This continues until `countdown()` is called with `n=0`. Then, the flow of execution hits the base case, and the function calls stop."
] ]
}, },
{ {
@ -92,7 +92,7 @@
" n (int): seconds until the party begins\n", " n (int): seconds until the party begins\n",
" \"\"\"\n", " \"\"\"\n",
" if n == 0: # base case\n", " if n == 0: # base case\n",
" print(\"Happy new Year!\")\n", " print(\"Happy New Year!\")\n",
" else:\n", " else:\n",
" print(n)\n", " print(n)\n",
" countdown(n - 1)" " countdown(n - 1)"
@ -114,7 +114,7 @@
"3\n", "3\n",
"2\n", "2\n",
"1\n", "1\n",
"Happy new Year!\n" "Happy New Year!\n"
] ]
} }
], ],
@ -130,7 +130,7 @@
} }
}, },
"source": [ "source": [
"As trivial as this seems at first sight, a lot of complexity is hidden in this implementation. In particular, the order in which objects are created and de-referenced in memory might not be obvious right away as [PythonTutor](http://pythontutor.com/visualize.html#code=def%20countdown%28n%29%3A%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20print%28%22Happy%20new%20Year!%22%29%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print%28n%29%0A%20%20%20%20%20%20%20%20countdown%28n%20-%201%29%0A%0Acountdown%283%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) shows: Each time `countdown()` is called, Python creates a *new* frame in the part of the memory where it manages all the names. This way Python *isolates* all the different `n` variables from each other. As new frames are created until we reach the base case after which the frames are destroyed in the *reversed* order, this is called a **[stack](https://en.wikipedia.org/wiki/Stack_(abstract_data_type))** of frames in computer science terminology. In simple words, a stack is a last-in-first-out (LIFO) task queue. Each frame has a single parent frame, namely the one whose recursive function call created it." "As trivial as this seems, a lot of complexity is hidden in this implementation. In particular, the order in which objects are created and de-referenced in memory might not be apparent right away as [PythonTutor](http://pythontutor.com/visualize.html#code=def%20countdown%28n%29%3A%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20print%28%22Happy%20new%20Year!%22%29%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print%28n%29%0A%20%20%20%20%20%20%20%20countdown%28n%20-%201%29%0A%0Acountdown%283%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) shows: Each time `countdown()` is called, Python creates a *new* frame in the part of the memory where it manages all the names. This way, Python *isolates* all the different `n` variables from each other. As new frames are created until we reach the base case, after which the frames are destroyed in the *reversed* order, this is called a **[stack](https://en.wikipedia.org/wiki/Stack_(abstract_data_type))** of frames in computer science terminology. In simple words, a stack is a last-in-first-out (LIFO) task queue. Each frame has a single parent frame, namely the one whose recursive function call created it."
] ]
}, },
{ {
@ -152,7 +152,7 @@
} }
}, },
"source": [ "source": [
"Recursion plays an important role in mathematics as well and we likely know about it from some introductory course, for example, in [combinatorics](https://en.wikipedia.org/wiki/Combinatorics)." "Recursion plays a vital role in mathematics as well, and we likely know about it from some introductory course, for example, in [combinatorics](https://en.wikipedia.org/wiki/Combinatorics)."
] ]
}, },
{ {
@ -174,7 +174,7 @@
} }
}, },
"source": [ "source": [
"The factorial function, denoted with the symbol $!$, is defined as follows for all non-negative integers:" "The factorial function, denoted with the $!$ symbol, is defined as follows for all non-negative integers:"
] ]
}, },
{ {
@ -199,7 +199,7 @@
"source": [ "source": [
"Whenever we find a recursive way of formulating an idea, we can immediately translate it into Python in a *naive* way (i.e., we create a *correct* program that may *not* be an *efficient* implementation yet).\n", "Whenever we find a recursive way of formulating an idea, we can immediately translate it into Python in a *naive* way (i.e., we create a *correct* program that may *not* be an *efficient* implementation yet).\n",
"\n", "\n",
"Below is a first version of `factorial()`: The `return` statement does not have to be a function's last code line and we may certainly have several `return` statements as well." "Below is a first version of `factorial()`: The `return` statement does not have to be a function's last code line, and we may indeed have several `return` statements as well."
] ]
}, },
{ {
@ -238,9 +238,9 @@
} }
}, },
"source": [ "source": [
"When we read such code, it is often easier not to follow every function call (i.e., `factorial(n - 1)` here) in one's mind but assume we receive a return value as specified in the documentation. Some call this approach **[leap of faith](http://greenteapress.com/thinkpython2/html/thinkpython2007.html#sec75)**. In fact, we practice this anyways whenever we call built-in functions (e.g., [print()](https://docs.python.org/3/library/functions.html#print) or [len()](https://docs.python.org/3/library/functions.html#len)) where we would have to read C code in many cases.\n", "When we read such code, it is often easier not to follow every function call (i.e., `factorial(n - 1)` here) in one's mind but assume we receive a return value as specified in the documentation. Some call this approach a **[leap of faith](http://greenteapress.com/thinkpython2/html/thinkpython2007.html#sec75)**. We practice this already whenever we call built-in functions (e.g., [print()](https://docs.python.org/3/library/functions.html#print) or [len()](https://docs.python.org/3/library/functions.html#len)) where we would have to read C code in many cases.\n",
"\n", "\n",
"To visualize *all* the computational steps of the examplary `factorial(3)`, we use [PythonTutor](http://pythontutor.com/visualize.html#code=def%20factorial%28n%29%3A%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20recurse%20%3D%20factorial%28n%20-%201%29%0A%20%20%20%20%20%20%20%20result%20%3D%20n%20*%20recurse%0A%20%20%20%20%20%20%20%20return%20result%0A%0Asolution%20%3D%20factorial%283%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false): The recursion again creates a stack of frames in memory. In contrast to the previous trivial example, each frame leaves a return value in memory after it is destroyed. This return value is then assigned to the `recurse` variable within the parent frame and used to compute `result`." "To visualize *all* the computational steps of the exemplary `factorial(3)`, we use [PythonTutor](http://pythontutor.com/visualize.html#code=def%20factorial%28n%29%3A%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20recurse%20%3D%20factorial%28n%20-%201%29%0A%20%20%20%20%20%20%20%20result%20%3D%20n%20*%20recurse%0A%20%20%20%20%20%20%20%20return%20result%0A%0Asolution%20%3D%20factorial%283%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false): The recursion again creates a stack of frames in memory. In contrast to the previous trivial example, each frame leaves a return value in memory after it is destroyed. This return value is then assigned to the `recurse` variable within the parent frame and used to compute `result`."
] ]
}, },
{ {
@ -299,7 +299,7 @@
} }
}, },
"source": [ "source": [
"A Pythonista would formulate `factorial()` in a conciser way using the so-called **early exit** pattern: No `else`-clause is needed as reaching a `return` statement immediately ends a function call. Furthermore, we do not really need the temporary variables `recurse` and `result`.\n", "A Pythonista would formulate `factorial()` in a more concise way using the so-called **early exit** pattern: No `else`-clause is needed as reaching a `return` statement ends a function call *immediately*. Furthermore, we do not need the temporary variables `recurse` and `result`.\n",
"\n", "\n",
"As [PythonTutor](http://pythontutor.com/visualize.html#code=def%20factorial%28n%29%3A%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20return%20n%20*%20factorial%28n%20-%201%29%0A%0Asolution%20%3D%20factorial%283%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) shows, this implementation is more efficient as it only requires 18 computational steps instead of 24 to calculate `factorial(3)`, an improvement of 25 percent! " "As [PythonTutor](http://pythontutor.com/visualize.html#code=def%20factorial%28n%29%3A%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20return%20n%20*%20factorial%28n%20-%201%29%0A%0Asolution%20%3D%20factorial%283%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) shows, this implementation is more efficient as it only requires 18 computational steps instead of 24 to calculate `factorial(3)`, an improvement of 25 percent! "
] ]
@ -384,7 +384,7 @@
} }
}, },
"source": [ "source": [
"Note that the [math](https://docs.python.org/3/library/math.html) module in the standard library provides a [factorial()](https://docs.python.org/3/library/math.html#math.factorial) function as well and we should therefore *never* implement it ourselves in a real code base." "Note that the [math](https://docs.python.org/3/library/math.html) module in the [standard library](https://docs.python.org/3/library/index.html) provides a [factorial()](https://docs.python.org/3/library/math.html#math.factorial) function as well, and we should, therefore, *never* implement it ourselves in a real codebase."
] ]
}, },
{ {
@ -554,7 +554,7 @@
} }
}, },
"source": [ "source": [
"Euclid's algorithm is stunningly fast, even for large numbers. Its speed comes from the use of the modulo operator `%`. However, this is *not* true for recusion in general, which may result in very slow programs if not applied correctly." "Euclid's algorithm is stunningly fast, even for large numbers. Its speed comes from the use of the modulo operator `%`. However, this is *not* true for recursion in general, which may result in slow programs if not applied correctly."
] ]
}, },
{ {
@ -624,7 +624,7 @@
} }
}, },
"source": [ "source": [
"The [math](https://docs.python.org/3/library/math.html) module in the standard library provides a [gcd()](https://docs.python.org/3/library/math.html#math.gcd) function as well and therefore we should again *never* implement it on our own." "The [math](https://docs.python.org/3/library/math.html) module in the [standard library](https://docs.python.org/3/library/index.html) provides a [gcd()](https://docs.python.org/3/library/math.html#math.gcd) function as well, and, therefore, we should again *never* implement it on our own."
] ]
}, },
{ {
@ -765,7 +765,7 @@
} }
}, },
"source": [ "source": [
"Let's write a function `fibonacci()` that calculates the $i$th Fibonacci number where $0$ will be the $0$th number. Looking at the numbers in a **backwards** fashion (i.e., from right to left), we realize that the return value for `fibonacci(i)` can be reduced to the sum of the return values for `fibonacci(i - 1)` and `fibonacci(i - 2)` disregarding the *two* base cases." "Let's write a function `fibonacci()` that calculates the $i$th Fibonacci number where $0$ is the $0$th number. Looking at the numbers in a **backward** fashion (i.e., from right to left), we realize that the return value for `fibonacci(i)` can be reduced to the sum of the return values for `fibonacci(i - 1)` and `fibonacci(i - 2)` disregarding the *two* base cases."
] ]
}, },
{ {
@ -841,7 +841,7 @@
"\n", "\n",
"To understand this in detail, we would have to study algorithms and data structures (e.g., with [this book](https://www.amazon.de/Introduction-Algorithms-Press-Thomas-Cormen/dp/0262033844/ref=sr_1_1?__mk_de_DE=%C3%85M%C3%85%C5%BD%C3%95%C3%91&crid=1JNE8U0VZGU0O&qid=1569837169&s=gateway&sprefix=algorithms+an%2Caps%2C180&sr=8-1)), a discipline within computer science, and dive into the analysis of **[time complexity of algorithms](https://en.wikipedia.org/wiki/Time_complexity)**.\n", "To understand this in detail, we would have to study algorithms and data structures (e.g., with [this book](https://www.amazon.de/Introduction-Algorithms-Press-Thomas-Cormen/dp/0262033844/ref=sr_1_1?__mk_de_DE=%C3%85M%C3%85%C5%BD%C3%95%C3%91&crid=1JNE8U0VZGU0O&qid=1569837169&s=gateway&sprefix=algorithms+an%2Caps%2C180&sr=8-1)), a discipline within computer science, and dive into the analysis of **[time complexity of algorithms](https://en.wikipedia.org/wiki/Time_complexity)**.\n",
"\n", "\n",
"Luckily, in the Fibonacci case the inefficiency can be resolved with a **caching** (i.e., \"re-use\") strategy from the field of **[dynamic programming](https://en.wikipedia.org/wiki/Dynamic_programming)**, namely **[memoization](https://en.wikipedia.org/wiki/Memoization)**. We will do so in Chapter 8 after introducing the [dictionaries](https://docs.python.org/3/library/stdtypes.html#dict) data type.\n", "Luckily, in the Fibonacci case, the inefficiency can be resolved with a **caching** (i.e., \"re-use\") strategy from the field of **[dynamic programming](https://en.wikipedia.org/wiki/Dynamic_programming)**, namely **[memoization](https://en.wikipedia.org/wiki/Memoization)**. We do so in Chapter 8, after introducing the [dictionaries](https://docs.python.org/3/library/stdtypes.html#dict) data type.\n",
"\n", "\n",
"Let's measure the average run times for `fibonacci()` and varying `i` arguments with the `%%timeit` [cell magic](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit) that comes with Jupyter." "Let's measure the average run times for `fibonacci()` and varying `i` arguments with the `%%timeit` [cell magic](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit) that comes with Jupyter."
] ]
@ -859,7 +859,7 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"47.3 µs ± 12.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" "39.9 µs ± 5.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n"
] ]
} }
], ],
@ -881,7 +881,7 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"1.63 ms ± 21.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" "1.61 ms ± 8.84 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n"
] ]
} }
], ],
@ -903,7 +903,7 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"211 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" "199 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n"
] ]
} }
], ],
@ -925,7 +925,7 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"2.22 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" "2.21 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n"
] ]
} }
], ],
@ -947,7 +947,7 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"5.81 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" "5.8 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n"
] ]
} }
], ],
@ -975,7 +975,7 @@
} }
}, },
"source": [ "source": [
"If a recursion does not reach its base case, it will theoretically run forever. Luckily, Python detects that and saves the computer from crashing by raising a `RecursionError`.\n", "If a recursion does not reach its base case, it theoretically runs forever. Luckily, Python detects that and saves the computer from crashing by raising a `RecursionError`.\n",
"\n", "\n",
"The simplest possible infinite recursion is generated like so." "The simplest possible infinite recursion is generated like so."
] ]
@ -1031,7 +1031,7 @@
} }
}, },
"source": [ "source": [
"However, even the trivial `countdown()` function from above is not immune to an infinite recursion. Let's call it with `3.1` instead of `3`. What goes wrong here?" "However, even the trivial `countdown()` function from above is not immune to infinite recursion. Let's call it with `3.1` instead of `3`. What goes wrong here?"
] ]
}, },
{ {
@ -4011,9 +4011,9 @@
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mRecursionError\u001b[0m Traceback (most recent call last)", "\u001b[0;31mRecursionError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-30-43e3ad331761>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mcountdown\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m3.1\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-30-43e3ad331761>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mcountdown\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m3.1\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-1-8e67251e237b>\u001b[0m in \u001b[0;36mcountdown\u001b[0;34m(n)\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 11\u001b[0;31m \u001b[0mcountdown\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\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[0m\n\u001b[0m", "\u001b[0;32m<ipython-input-1-6001bec3d961>\u001b[0m in \u001b[0;36mcountdown\u001b[0;34m(n)\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 11\u001b[0;31m \u001b[0mcountdown\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\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[0m\n\u001b[0m",
"... last 1 frames repeated, from the frame below ...\n", "... last 1 frames repeated, from the frame below ...\n",
"\u001b[0;32m<ipython-input-1-8e67251e237b>\u001b[0m in \u001b[0;36mcountdown\u001b[0;34m(n)\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 11\u001b[0;31m \u001b[0mcountdown\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\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[0m\n\u001b[0m", "\u001b[0;32m<ipython-input-1-6001bec3d961>\u001b[0m in \u001b[0;36mcountdown\u001b[0;34m(n)\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 11\u001b[0;31m \u001b[0mcountdown\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\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[0m\n\u001b[0m",
"\u001b[0;31mRecursionError\u001b[0m: maximum recursion depth exceeded while calling a Python object" "\u001b[0;31mRecursionError\u001b[0m: maximum recursion depth exceeded while calling a Python object"
] ]
} }
@ -4069,21 +4069,21 @@
} }
}, },
"source": [ "source": [
"The infinite recursions could easily be avoided by replacing `n == 0` with `n <= 0` in both functions and thereby **generalizing** them. But even then, calling either `countdown()` or `factorial()` with a non-integer number is *semantically* wrong and therefore we better leave the base cases unchanged.\n", "The infinite recursions could easily be avoided by replacing `n == 0` with `n <= 0` in both functions and thereby **generalizing** them. But even then, calling either `countdown()` or `factorial()` with a non-integer number is *semantically* wrong, and, therefore, we better leave the base cases unchanged.\n",
"\n", "\n",
"Errors as above are a symptom of missing **type checking**: By design, Python allows us to pass in not only integers but objects of any type as arguments to the `countdown()` and `factorial()` functions. As long as the arguments \"behave\" like integers, we will not encounter any *runtime* errors. This is the case here as the two example functions only use the `-` and `*` operators internally and, in the context of arithmetic, a `float` object behaves exactly like an `int` object. So, the functions keep calling themselves until Python decides with a built-in heuristic that the recursion is likely not going to end and aborts the computations with a `RecursionError`. Stricly speaking, a `RecursionError` is of course a *runtime* error as well.\n", "Errors as above are a symptom of missing **type checking**: By design, Python allows us to pass in not only integers but objects of any type as arguments to the `countdown()` and `factorial()` functions. As long as the arguments \"behave\" like integers, we do not encounter any *runtime* errors. This is the case here as the two example functions only use the `-` and `*` operators internally, and, in the context of arithmetic, a `float` object behaves like an `int` object. So, the functions keep calling themselves until Python decides with a built-in heuristic that the recursion is likely not going to end and aborts the computations with a `RecursionError`. Strictly speaking, a `RecursionError` is, of course, a *runtime* error as well.\n",
"\n", "\n",
"The missing type checking is *100% intentional* and considered a **[feature of rather than a bug](https://www.urbandictionary.com/define.php?term=It%27s%20not%20a%20bug%2C%20it%27s%20a%20feature)** in Python!\n", "The missing type checking is *100% intentional* and considered a **[feature of rather than a bug](https://www.urbandictionary.com/define.php?term=It%27s%20not%20a%20bug%2C%20it%27s%20a%20feature)** in Python!\n",
"\n", "\n",
"Pythonistas often use the colloquial term **[duck typing](https://en.wikipedia.org/wiki/Duck_typing)** when refering to the same idea and the saying goes \"If it walks like a duck and it quacks like a duck, it must be a duck\". For example, we could call `factorial()` with the `float` object `3.0` and the recursion works out fine. So, as long as the `3.0` \"walks\" like a `3` and \"quacks\" like a `3`, it \"must be\" a `3`.\n", "Pythonistas often use the colloquial term **[duck typing](https://en.wikipedia.org/wiki/Duck_typing)** when referring to the same idea, and the saying goes, \"If it walks like a duck and it quacks like a duck, it must be a duck.\" For example, we could call `factorial()` with the `float` object `3.0`, and the recursion works out fine. So, as long as the `3.0` \"walks\" like a `3` and \"quacks\" like a `3`, it \"must be\" a `3`.\n",
"\n", "\n",
"We see a similar behavior when we mix objects of types `int` and `float` with arithmetic operators. For example, `1 + 2.0` works because Python implicitly views the `1` as a `1.0` at runtime and then knows how to do floating-point arithmetic: Here, the `int` \"walks\" and \"quacks\" like a `float`. Strictly speaking, this is yet another example of operator overloading whereas duck typing refers to the same behavior when passing arguments to function calls.\n", "We see similar behavior when we mix objects of types `int` and `float` with arithmetic operators. For example, `1 + 2.0` works because Python implicitly views the `1` as a `1.0` at runtime and then knows how to do floating-point arithmetic: Here, the `int` \"walks\" and \"quacks\" like a `float`. Strictly speaking, this is yet another example of operator overloading, whereas duck typing refers to the same behavior when passing arguments to function calls.\n",
"\n", "\n",
"The important lesson is that we must expect our functions be called with objects of *any* type at runtime, as opposed to the one type we had in mind when we defined the function.\n", "The important lesson is that we must expect our functions to be called with objects of *any* type at runtime, as opposed to the one type we had in mind when we defined the function.\n",
"\n", "\n",
"Duck typing is possible because Python is a dynamically typed language. On the contrary, in statically typed languages like C we *must* declare (i.e., \"specify\") the data type of every parameter in a function definition. Then, a `RecursionError` as for `countdown(3.1)` or `factorial(3.1)` above could not occur. For example, if we declared the `countdown()` and `factorial()` functions to only accept `int` objects, calling the functions with a `float` argument would immediately fail *syntactically*. As a downside, we would then lose the ability to call `factorial()` with `3.0`, which is *semantically* correct nevertheless.\n", "Duck typing is possible because Python is a dynamically typed language. On the contrary, in statically typed languages like C, we *must* declare (i.e., \"specify\") the data type of every parameter in a function definition. Then, a `RecursionError` as for `countdown(3.1)` or `factorial(3.1)` above could not occur. For example, if we declared the `countdown()` and `factorial()` functions to only accept `int` objects, calling the functions with a `float` argument would immediately fail *syntactically*. As a downside, we would then lose the ability to call `factorial()` with `3.0`, which is *semantically* correct nevertheless.\n",
"\n", "\n",
"So, there is no black or white answer as to which of the two language designs is better. Yet, most professional programmers have very strong opinions with respect to duck typing reaching from \"love\" to \"hate\". This is another example as to how programming is a subjective art rather than an \"objective\" science. Python's design is probably more appealing to beginners who intuitively regard `3` and `3.0` as interchangeable." "So, there is no black or white answer as to which of the two language designs is better. Yet, most professional programmers have strong opinions concerning duck typing, reaching from \"love\" to \"hate.\" This is another example of how programming is a subjective art rather than \"objective\" science. Python's design is probably more appealing to beginners who intuitively regard `3` and `3.0` as interchangeable."
] ]
}, },
{ {
@ -4109,11 +4109,11 @@
"\n", "\n",
"Meanwhile, we also see how we manually raise exceptions with the `raise` [statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement), another way of controlling the flow of execution.\n", "Meanwhile, we also see how we manually raise exceptions with the `raise` [statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement), another way of controlling the flow of execution.\n",
"\n", "\n",
"The first two branches in the revised `factorial()` function act as **guardians** ensuring that the code does not produce *unexpected* runtime errors: Errors may certainly be expected when mentioned in the docstring.\n", "The first two branches in the revised `factorial()` function act as **guardians** ensuring that the code does not produce *unexpected* runtime errors: Errors may be expected when mentioned in the docstring.\n",
"\n", "\n",
"Forcing `n` to be an `int` is a very puritan way of handling the issues discussed above. A more relaxed approach could be to also accept a `float` and use its [is_integer()](https://docs.python.org/3/library/stdtypes.html#float.is_integer) method to check if `n` could be casted as an `int`. After all, being too puritan we cannot take advantage of duck typing.\n", "Forcing `n` to be an `int` is a very puritan way of handling the issues discussed above. A more relaxed approach could be to also accept a `float` and use its [is_integer()](https://docs.python.org/3/library/stdtypes.html#float.is_integer) method to check if `n` could be cast as an `int`. After all, being too puritan, we cannot take advantage of duck typing.\n",
"\n", "\n",
"So in essence, we are doing *two* things here: Besides checking the type, we also enforce **domain-specific** (i.e., mathematical here) rules with respect to the non-negativity of `n`." "So, in essence, we are doing *two* things here: Besides checking the type, we also enforce **domain-specific** (i.e., mathematical here) rules concerning the non-negativity of `n`."
] ]
}, },
{ {
@ -4215,7 +4215,7 @@
} }
}, },
"source": [ "source": [
"Instead of running into an infinite recursion, we now receive very specifc error messages." "Instead of running into a situation of infinite recursion, we now receive specific error messages."
] ]
}, },
{ {
@ -4289,7 +4289,7 @@
} }
}, },
"source": [ "source": [
"With everything *officially* introduced so far (i.e., without the introductary example in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb) that only served as an overview), Python would be what is called **[Turing complete](https://en.wikipedia.org/wiki/Turing_completeness)**. That means that anything that could be formulated as an algorithm could be expressed with all the language features we have seen. Note that, in particular, we have *not* yet formally *introduced* the `for` and `while` statements!" "With everything *officially* introduced so far (i.e., without the introductory example in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb) that only served as an overview), Python would be what is called **[Turing complete](https://en.wikipedia.org/wiki/Turing_completeness)**. That means that anything that could be formulated as an algorithm could be expressed with all the language features we have seen. Note that, in particular, we have *not* yet formally *introduced* the `for` and `while` statements!"
] ]
}, },
{ {
@ -4322,11 +4322,11 @@
} }
}, },
"source": [ "source": [
"Whereas functions combined with `if` statements suffice to model any type of iteration with a recursion, Python comes with a `while` [statement](https://docs.python.org/3/reference/compound_stmts.html#the-while-statement) that often makes it easier to implement iterative ideas.\n", "Whereas functions combined with `if` statements suffice to model any repetitive logic, Python comes with a `while` [statement](https://docs.python.org/3/reference/compound_stmts.html#the-while-statement) that often makes it easier to implement iterative ideas.\n",
"\n", "\n",
"It consists of a header line with a boolean expression followed by an indented code block. Before the first and after every execution of the code block, the boolean expression is evaluated and if it is (still) equal to `True`, the code block runs (again). Eventually, some variable referenced in the boolean expression is changed in the code block such that the condition becomes `False`.\n", "It consists of a header line with a boolean expression followed by an indented code block. Before the first and after every execution of the code block, the boolean expression is evaluated, and if it is (still) equal to `True`, the code block runs (again). Eventually, some variable referenced in the boolean expression is changed in the code block such that the condition becomes `False`.\n",
"\n", "\n",
"If the condition is `False` before the first iteration, the entire code block is *never* executed. As the flow of control keeps **looping** (or **iterating**) back to the beginning of the code block, this concept is also called a `while`-loop and each pass through the loop an **iteration**." "If the condition is `False` before the first iteration, the entire code block is *never* executed. As the flow of control keeps \"**looping**\" (i.e., more formally, **iterating**) back to the beginning of the code block, this concept is also called a `while`-loop and each pass through the loop an **iteration**."
] ]
}, },
{ {
@ -4348,7 +4348,7 @@
} }
}, },
"source": [ "source": [
"Let's rewrite the `countdown()` example in an iterative style. We also build in **input validation** by allowing the function to only be called with strictly positive integers. As any positive integer will hit $0$ at some point in time when iteratively decremented by $1$, `countdown()` is guaranteed to **terminate**. Note also that the base case is now handled at the end of the function, which commonly happens with iterative solutions to problems." "Let's rewrite the `countdown()` example in an iterative style. We also build in **input validation** by allowing the function only to be called with strictly positive integers. As any positive integer hits $0$ at some point when iteratively decremented by $1$, `countdown()` is guaranteed to **terminate**. Also, the base case is now handled at the end of the function, which commonly happens with iterative solutions to problems."
] ]
}, },
{ {
@ -4416,7 +4416,7 @@
} }
}, },
"source": [ "source": [
"As [PythonTutor](http://pythontutor.com/visualize.html#code=def%20countdown%28n%29%3A%0A%20%20%20%20if%20not%20isinstance%28n,%20int%29%3A%0A%20%20%20%20%20%20%20%20raise%20TypeError%28%22...%22%29%0A%20%20%20%20elif%20n%20%3C%3D%200%3A%0A%20%20%20%20%20%20%20%20raise%20ValueError%28%22...%22%29%0A%0A%20%20%20%20while%20n%20!%3D%200%3A%0A%20%20%20%20%20%20%20%20print%28n%29%0A%20%20%20%20%20%20%20%20n%20-%3D%201%0A%0A%20%20%20%20print%28%22Happy%20new%20Year!%22%29%0A%0Acountdown%283%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) shows, there is a subtle but important difference in the way a `while` statement is treated in memory: In short, `while` statements can *not* run into a `RecursionError` as only *one* frame is needed to manage the names. After all, there is only *one* function call to be made. For common day-to-day applications this difference is, however, not important." "As [PythonTutor](http://pythontutor.com/visualize.html#code=def%20countdown%28n%29%3A%0A%20%20%20%20if%20not%20isinstance%28n,%20int%29%3A%0A%20%20%20%20%20%20%20%20raise%20TypeError%28%22...%22%29%0A%20%20%20%20elif%20n%20%3C%3D%200%3A%0A%20%20%20%20%20%20%20%20raise%20ValueError%28%22...%22%29%0A%0A%20%20%20%20while%20n%20!%3D%200%3A%0A%20%20%20%20%20%20%20%20print%28n%29%0A%20%20%20%20%20%20%20%20n%20-%3D%201%0A%0A%20%20%20%20print%28%22Happy%20new%20Year!%22%29%0A%0Acountdown%283%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) shows, there is a subtle but essential difference in the way a `while` statement is treated in memory: In short, `while` statements can *not* run into a `RecursionError` as only *one* frame is needed to manage the names. After all, there is only *one* function call to be made. For typical day-to-day applications, this difference is, however, not so important *unless* a problem instance becomes so big that a large (i.e., $> 3.000$) number of recursive calls must be made."
] ]
}, },
{ {
@ -4438,9 +4438,9 @@
} }
}, },
"source": [ "source": [
"Finding the greatest common divisor of two numbers is still not so obvious when using a `while`-loop instead of a recursion.\n", "Finding the greatest common divisor of two numbers is still not so obvious when using a `while`-loop instead of a recursive formulation.\n",
"\n", "\n",
"The iterative implementation of `gcd()` below accepts any two strictly positive integers. As in any iteration through the loop the smaller number is subtracted from the larger one, the two decremented values of `a` and `b` will eventually be equal. Thus, this algorithm is also guaranteed to terminate. If one of the two numbers were negative or $0$ to begin with, `gcd()` would run forever and not even Python could detect this. Try this out by removing the input validation and running the function with negative arguments!" "The iterative implementation of `gcd()` below accepts any two strictly positive integers. As in any iteration through the loop, the smaller number is subtracted from the larger one, the two decremented values of `a` and `b` eventually become equal. Thus, this algorithm is also guaranteed to terminate. If one of the two numbers were negative or $0$ in the first place, `gcd()` would run forever, and not even Python could detect this. Try this out by removing the input validation and running the function with negative arguments!"
] ]
}, },
{ {
@ -4565,7 +4565,7 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"4.76 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" "4.69 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n"
] ]
} }
], ],
@ -4593,7 +4593,7 @@
} }
}, },
"source": [ "source": [
"As with recursion, we must ensure that the iteration ends. For the above `countdown()` and `gcd()` examples we could \"prove\" (i.e., at least argue in favor) that some pre-defined **termination criterion** will be reached eventually. However, this cannot be done in all cases as the following example shows." "As with recursion, we must ensure that the iteration ends. For the above `countdown()` and `gcd()` examples, we could \"prove\" (i.e., at least argue in favor) that some pre-defined **termination criterion** is reached eventually. However, this cannot be done in all cases, as the following example shows."
] ]
}, },
{ {
@ -4616,10 +4616,10 @@
}, },
"source": [ "source": [
"Let's play the following game:\n", "Let's play the following game:\n",
"- think of any positive integer $n$\n", "- Think of any positive integer $n$.\n",
"- if $n$ is even, the next $n$ is half the old $n$\n", "- If $n$ is even, the next $n$ is half the old $n$.\n",
"- if $n$ is odd, multiply the old $n$ by $3$ and add $1$ to obtain the next $n$\n", "- If $n$ is odd, multiply the old $n$ by $3$ and add $1$ to obtain the next $n$.\n",
"- repeat these steps until you reach $1$\n", "- Repeat these steps until you reach $1$.\n",
"\n", "\n",
"**Do we always reach the final $1$?**" "**Do we always reach the final $1$?**"
] ]
@ -4632,7 +4632,7 @@
} }
}, },
"source": [ "source": [
"The function below implements this game. Does it always reach $1$? No one has proven it so far! We include some input validation as before because `collatz()` would definitely not terminate if we called it with a negative number. Further, the Collatz sequence also works for real numbers but then we would have to study fractals (cf., [this](https://en.wikipedia.org/wiki/Collatz_conjecture#Iterating_on_real_or_complex_numbers)). So we restrict our example to integers only." "The function below implements this game. Does it always reach $1$? No one has proven it so far! We include some input validation as before because `collatz()` would for sure not terminate if we called it with a negative number. Further, the Collatz sequence also works for real numbers, but then we would have to study fractals (cf., [this](https://en.wikipedia.org/wiki/Collatz_conjecture#Iterating_on_real_or_complex_numbers)). So we restrict our example to integers only."
] ]
}, },
{ {
@ -4790,9 +4790,9 @@
} }
}, },
"source": [ "source": [
"Recursion and the `while` statement are two sides of the same coin. Disregarding that in the case of recursion Python internally faces some additional burden for managing the stack of frames in memory, both approaches lead to the *same* computational steps in memory. More importantly, we can re-formulate a recursive implementation in an iterative way and vice verca despite one of the two ways often \"feeling\" a lot more natural given a particular problem.\n", "Recursion and the `while` statement are two sides of the same coin. Disregarding that in the case of recursion Python internally faces some additional burden for managing the stack of frames in memory, both approaches lead to the *same* computational steps in memory. More importantly, we can re-formulate a recursive implementation in an iterative way and vice versa despite one of the two ways often \"feeling\" a lot more natural given a particular problem.\n",
"\n", "\n",
"So how does the `for` [statement](https://docs.python.org/3/reference/compound_stmts.html#the-for-statement) we saw in the very first example in this book fit into this picture? It is really a *redundant* language construct to provide a *shorter* and more *convenient* syntax for common applications of the `while` statement. In programming, such additions to a language are called **syntactic sugar**. Sugar makes a cup of tea taste better but we may drink tea without sugar too.\n", "So how does the `for` [statement](https://docs.python.org/3/reference/compound_stmts.html#the-for-statement) we saw in the very first example in this book fit into this picture? It is a *redundant* language construct to provide a *shorter* and more *convenient* syntax for common applications of the `while` statement. In programming, such additions to a language are called **syntactic sugar**. A cup of tea tastes better with sugar, but we may drink tea without sugar too.\n",
"\n", "\n",
"Consider the following `numbers` list. Without the `for` statement, we have to manage a temporary **index variable** `i` to loop over all the elements and also obtain the individual elements with the `[]` operator in each iteration of the loop." "Consider the following `numbers` list. Without the `for` statement, we have to manage a temporary **index variable** `i` to loop over all the elements and also obtain the individual elements with the `[]` operator in each iteration of the loop."
] ]
@ -4844,7 +4844,7 @@
} }
}, },
"source": [ "source": [
"The `for` statement, on the contrary, makes the actual business logic more apparent by stripping all the **boilerplate code** away." "The `for` statement, on the contrary, makes the actual business logic more apparent by stripping all the **[boilerplate code](https://en.wikipedia.org/wiki/Boilerplate_code)** away."
] ]
}, },
{ {
@ -4877,7 +4877,7 @@
} }
}, },
"source": [ "source": [
"For sequences of integers the [range()](https://docs.python.org/3/library/functions.html#func-range) built-in makes the `for` statement even more convenient: It creates a list-like object of type `range` that generates integers \"on the fly\" and we will look closely at the underlying effects in memory in Chapter 7." "For sequences of integers, the [range()](https://docs.python.org/3/library/functions.html#func-range) built-in makes the `for` statement even more convenient: It creates a list-like object of type `range` that generates integers \"on the fly,\" and we look closely at the underlying effects in memory in Chapter 7."
] ]
}, },
{ {
@ -5000,7 +5000,7 @@
} }
}, },
"source": [ "source": [
"The important difference between the above `list` objects, `[0, 1, 2, 3, 4]` and `[1, 3, 5, 7, 9]`, and the `range` objects, `range(5)` and `range(1, 10, 2)`, is that in the former case *six* objects are created in memory *before* the `for` statement starts running, *one* `list` holding pointers to *five* `int` objects, whereas in the latter case only *one* `range` object is created that **generates** `int` objects one at a time *while* the `for`-loop runs.\n", "The essential difference between the above `list` objects, `[0, 1, 2, 3, 4]` and `[1, 3, 5, 7, 9]`, and the `range` objects, `range(5)` and `range(1, 10, 2)`, is that in the former case *six* objects are created in memory *before* the `for` statement starts running, *one* `list` holding pointers to *five* `int` objects, whereas in the latter case only *one* `range` object is created that **generates** `int` objects one at a time *while* the `for`-loop runs.\n",
"\n", "\n",
"However, we can loop over both of them. So a natural question to ask is why Python treats objects of *different* types in the *same* way when used with a `for` statement.\n", "However, we can loop over both of them. So a natural question to ask is why Python treats objects of *different* types in the *same* way when used with a `for` statement.\n",
"\n", "\n",
@ -5008,13 +5008,13 @@
"\n", "\n",
"Now, just as we classify objects by their types, we also classify these **concrete data types** (e.g., `int`, `float`, `str`, or `list`) into **abstract concepts**.\n", "Now, just as we classify objects by their types, we also classify these **concrete data types** (e.g., `int`, `float`, `str`, or `list`) into **abstract concepts**.\n",
"\n", "\n",
"We actually did this already in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb) when we described a `list` object as \"some sort of container that holds [...] pointers to other objects\". So, abstractly speaking, **containers** are any objects that are \"composed\" of other objects and also \"manage\" how these objects are organized. `list` objects, for example, have the property that they model an internal order associated with its elements. There exist, however, other container types, many of which do *not* come with an order. So, containers primarily \"contain\" other objects and have *nothing* to do with looping.\n", "We did this already in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb) when we described a `list` object as \"some sort of container that holds [...] pointers to other objects\". So, abstractly speaking, **containers** are any objects that are \"composed\" of other objects and also \"manage\" how these objects are organized. `list` objects, for example, have the property that they model an internal order associated with its elements. There exist, however, other container types, many of which do *not* come with an order. So, containers primarily \"contain\" other objects and have *nothing* to do with looping.\n",
"\n", "\n",
"On the contrary, the abstract concept of **iterables** is all about looping: Any object that we can loop over is by definition an iterable. So, `range` objects, for example, are iterables that do *not* contain other objects. Moreover, looping does *not* have to occur in a *predictable* order although this is the case for both `list` and `range` objects.\n", "On the contrary, the abstract concept of **iterables** is all about looping: Any object that we can loop over is, by definition, an iterable. So, `range` objects, for example, are iterables that do *not* contain other objects. Moreover, looping does *not* have to occur in a *predictable* order, although this is the case for both `list` and `range` objects.\n",
"\n", "\n",
"Typically, containers are iterable and iterables are containers. Yet, only because these two concepts coincide often, we must not think of them as the same. Chapter 10 will finally give an explanation as to how abstract concepts are implemented and play together.\n", "Typically, containers are iterables, and iterables are containers. Yet, only because these two concepts coincide often, we must not think of them as the same. Chapter 10 finally gives an explanation as to how abstract concepts are implemented and play together.\n",
"\n", "\n",
"So, `list` objects like `first_names` below are iterable containers. They actually implement even more abstract concepts as we will see in Chapter 7." "So, `list` objects like `first_names` below are iterable containers. They implement even more abstract concepts, as Chapter 7 reveals."
] ]
}, },
{ {
@ -5165,7 +5165,7 @@
} }
}, },
"source": [ "source": [
"If we must have an index variable in the loop's body, we use the [enumerate()](https://docs.python.org/3/library/functions.html#enumerate) built-in that takes an *iterable* as its argument and then generates a \"stream\" of \"pairs\" consisting of an index variable and an object provided by the iterable. There is *no* need to ever revert back to the `while` statement to loop over an iterable object." "If we must have an index variable in the loop's body, we use the [enumerate()](https://docs.python.org/3/library/functions.html#enumerate) built-in that takes an *iterable* as its argument and then generates a \"stream\" of \"pairs\" consisting of an index variable and an object provided by the iterable. There is *no* need to ever revert to the `while` statement to loop over an iterable object."
] ]
}, },
{ {
@ -5290,9 +5290,9 @@
"source": [ "source": [
"In contrast to its recursive counterpart, the iterative `fibonacci()` function below is somewhat harder to read. For example, it is not so obvious as to how many iterations through the `for`-loop we need to make when implementing it. There is an increased risk of making an *off-by-one* error. Moreover, we need to track a `temp` variable along, at least until we have worked through Chapter 7. Do you understand what `temp` does?\n", "In contrast to its recursive counterpart, the iterative `fibonacci()` function below is somewhat harder to read. For example, it is not so obvious as to how many iterations through the `for`-loop we need to make when implementing it. There is an increased risk of making an *off-by-one* error. Moreover, we need to track a `temp` variable along, at least until we have worked through Chapter 7. Do you understand what `temp` does?\n",
"\n", "\n",
"However, one advantage of calculating Fibonacci numbers in a **forwards** fashion with a `for` statement is that we could list the entire sequence in ascending order as we calculate the desired number. To show this, we added `print()` statements in `fibonacci()` below.\n", "However, one advantage of calculating Fibonacci numbers in a **forward** fashion with a `for` statement is that we could list the entire sequence in ascending order as we calculate the desired number. To show this, we added `print()` statements in `fibonacci()` below.\n",
"\n", "\n",
"We do *not* need to store the index variable in the `for`-loop's header line: That is what the underscore variable `_` indicates; we \"throw it away\"." "We do *not* need to store the index variable in the `for`-loop's header line: That is what the underscore variable `_` indicates; we \"throw it away.\""
] ]
}, },
{ {
@ -5439,7 +5439,7 @@
} }
}, },
"source": [ "source": [
"The iterative `factorial()` implementation is comparable to its recursive counterpart when it comes to readability. One advantage of calculating the factorial in a forwards fashion is that we could track the intermediate `product` as it grows." "The iterative `factorial()` implementation is comparable to its recursive counterpart when it comes to readability. One advantage of calculating the factorial in a forward fashion is that we could track the intermediate `product` as it grows."
] ]
}, },
{ {
@ -5528,7 +5528,7 @@
} }
}, },
"source": [ "source": [
"The remainder of this chapter introduces more syntactic sugar. None of the language constructs introduced below are actually needed but contribute to making Python the expressive and easy to read language that it is." "The remainder of this chapter introduces more syntactic sugar: None of the language constructs introduced below are needed but contribute to making Python the expressive and easy to read language that it is."
] ]
}, },
{ {
@ -5629,11 +5629,11 @@
} }
}, },
"source": [ "source": [
"This implementation is *inefficient* as even if the *first* element in `numbers` has a square greater than `100`, we loop until the last element. This could take a long time for a big list.\n", "This implementation is *inefficient* as even if the *first* element in `numbers` has a square greater than `100`, we loop until the last element: This could take a long time for a big list.\n",
"\n", "\n",
"Moreover, we must initialize `is_above` *before* the `for`-loop and write an `if`-`else`-logic *after* it to check for the result. The actual business logic is *not* clear right away.\n", "Moreover, we must initialize `is_above` *before* the `for`-loop and write an `if`-`else`-logic *after* it to check for the result. The actual business logic is *not* clear right away.\n",
"\n", "\n",
"Luckily, Python provides a `break` [statement](https://docs.python.org/3/reference/simple_stmts.html#the-break-statement) that let's us stop the `for`-loop in any iteration of the loop. Conceptually, it is yet another means of controlling the flow of execution." "Luckily, Python provides a `break` [statement](https://docs.python.org/3/reference/simple_stmts.html#the-break-statement) that lets us stop the `for`-loop in any iteration of the loop. Conceptually, it is yet another means of controlling the flow of execution."
] ]
}, },
{ {
@ -5676,7 +5676,7 @@
} }
}, },
"source": [ "source": [
"This is a computational improvement. However, the code still consists *three* logical sections: some initialization *before* the `for`-loop, the loop itself, and some finalizing logic. We would prefer to convey the program's idea in *one* compound statement instead." "This is a computational improvement. However, the code still consists of *three* logical sections: Some initialization *before* the `for`-loop, the loop itself, and some finalizing logic. We prefer to convey the program's idea in *one* compound statement instead."
] ]
}, },
{ {
@ -5698,7 +5698,7 @@
} }
}, },
"source": [ "source": [
"To express the logic in a prettier way, we add an `else`-clause at the end of the `for`-loop. The `else`-branch is executed *only if* the `for`-loop is *not* stopped with a `break` statement **prematurely** (i.e., *before* reaching the *last* iteration in the loop). The word \"else\" implies a rather unintuitive meaning and had better been named a `then`-clause. In most scenarios, however, the `else`-clause logically goes together with some `if` statement in the `for`-loop's body.\n", "To express the logic in a prettier way, we add an `else`-clause at the end of the `for`-loop. The `else`-branch is executed *iff* the `for`-loop is *not* stopped with a `break` statement **prematurely** (i.e., *before* reaching the *last* iteration in the loop). The word \"else\" implies a somewhat unintuitive meaning and had better been named a `then`-clause. In most scenarios, however, the `else`-clause logically goes together with some `if` statement in the `for`-loop's body.\n",
"\n", "\n",
"Overall, the code's expressive power increases. Not many programming languages support a `for`-`else`-branching, which turns out to be very useful in practice." "Overall, the code's expressive power increases. Not many programming languages support a `for`-`else`-branching, which turns out to be very useful in practice."
] ]
@ -5754,7 +5754,7 @@
} }
}, },
"source": [ "source": [
"Lastly, we incorporate the finalizing `if`-`else` logic into the `for`-loop avoiding the `is_above` variable alltogether." "Lastly, we incorporate the finalizing `if`-`else` logic into the `for`-loop, avoiding the `is_above` variable altogether."
] ]
}, },
{ {
@ -5792,7 +5792,7 @@
} }
}, },
"source": [ "source": [
"Of course, if we set the `threshold` an element's square has to pass higher, for example to `200`, we have to loop through the entire `numbers` list. There is *no way* to further optimize this **[linear search](https://en.wikipedia.org/wiki/Linear_search)**, at least as long as we model `numbers` as a `list` object. More advanced data types, however, exist that mitigate that downside." "Of course, if we set the `threshold` an element's square has to pass higher, for example, to `200`, we have to loop through the entire `numbers` list. There is *no way* to optimize this **[linear search](https://en.wikipedia.org/wiki/Linear_search)** further, at least as long as we model `numbers` as a `list` object. More advanced data types, however, exist that mitigate that downside."
] ]
}, },
{ {
@ -5843,7 +5843,7 @@
} }
}, },
"source": [ "source": [
"Often times, we process some iterable with numeric data, for example, a list of numbers as in the introductory example in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb) or, more realistically, data from a CSV file with many rows and columns.\n", "Often, we process some iterable with numeric data, for example, a list of numbers as in the introductory example in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb) or, more realistically, data from a CSV file with many rows and columns.\n",
"\n", "\n",
"Processing numeric data usually comes down to operations that may be grouped into one of the following three categories:\n", "Processing numeric data usually comes down to operations that may be grouped into one of the following three categories:\n",
"\n", "\n",
@ -5851,7 +5851,7 @@
"- **filtering**: throw away individual samples (e.g., statistical outliers)\n", "- **filtering**: throw away individual samples (e.g., statistical outliers)\n",
"- **reducing**: collect individual samples into summary statistics\n", "- **reducing**: collect individual samples into summary statistics\n",
"\n", "\n",
"We will study this **map-filter-reduce** paradigm extensively in Chapter 7 after introducing more advanced data types that are needed to work with \"big\" data.\n", "We study this **map-filter-reduce** paradigm extensively in Chapter 7 after introducing more advanced data types that are needed to work with \"big\" data.\n",
"\n", "\n",
"In the remainder of this section, we focus on *filtering out* some samples within a `for`-loop." "In the remainder of this section, we focus on *filtering out* some samples within a `for`-loop."
] ]
@ -5947,7 +5947,7 @@
"\n", "\n",
"In general, code gets harder to comprehend the more **horizontal space** it occupies. It is commonly considered good practice to grow a program **vertically** rather than horizontally. Code compliant with [PEP 8](https://www.python.org/dev/peps/pep-0008/#maximum-line-length) requires us to use *at most* 79 characters in a line!\n", "In general, code gets harder to comprehend the more **horizontal space** it occupies. It is commonly considered good practice to grow a program **vertically** rather than horizontally. Code compliant with [PEP 8](https://www.python.org/dev/peps/pep-0008/#maximum-line-length) requires us to use *at most* 79 characters in a line!\n",
"\n", "\n",
"Consider the next example, whose implementation in code already starts to look \"unbalanced\"." "Consider the next example, whose implementation in code already starts to look unbalanced."
] ]
}, },
{ {
@ -6050,11 +6050,11 @@
} }
}, },
"source": [ "source": [
"With already three levels of indentation, less horizontal space is available for the actual code block. Of course, one could flatten the two `if` statements with the logical `and` operator as shown in [Chapter 3](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/03_conditionals.ipynb#The-if-Statement). Then, however, we trade off horizontal space against a more \"complex\" `if` logic and this is *not* a real improvement.\n", "With already three levels of indentation, less horizontal space is available for the actual code block. Of course, one could flatten the two `if` statements with the logical `and` operator, as shown in [Chapter 3](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/03_conditionals.ipynb#The-if-Statement). Then, however, we trade off horizontal space against a more \"complex\" `if` logic, and this is *not* a real improvement.\n",
"\n", "\n",
"A Pythonista would instead make use of the `continue` [statement](https://docs.python.org/3/reference/simple_stmts.html#the-continue-statement) that causes a loop to jump into the next iteration skipping the rest of the code block.\n", "A Pythonista would instead make use of the `continue` [statement](https://docs.python.org/3/reference/simple_stmts.html#the-continue-statement) that causes a loop to jump into the next iteration skipping the rest of the code block.\n",
"\n", "\n",
"The revised code fragement below occupies more vertical space and less horizontal space: A *good* trade-off." "The revised code fragment below occupies more vertical space and less horizontal space: A *good* trade-off."
] ]
}, },
{ {
@ -6119,11 +6119,11 @@
} }
}, },
"source": [ "source": [
"This is yet another illustration of why programming is an art. The two preceding code cells do *exactly* the *same* with *identical* time complexity. However, the latter is arguably easier to read for a human, even more so when the business logic grows beyond two nested filters.\n", "This is yet another illustration of why programming is an art. The two preceding code cells do the *same* with *identical* time complexity. However, the latter is arguably easier to read for a human, even more so when the business logic grows beyond two nested filters.\n",
"\n", "\n",
"The idea behind the `continue` statement is conceptually similar to the early exit pattern we saw above.\n", "The idea behind the `continue` statement is conceptually similar to the early exit pattern we saw above.\n",
"\n", "\n",
"Both the `break` and `continue` statements as well as the optional `else`-clause are not only supported with `for`-loops but also `while`-loops." "Both the `break` and `continue` statements, as well as the optional `else`-clause, are not only supported with `for`-loops but also `while`-loops."
] ]
}, },
{ {
@ -6171,9 +6171,9 @@
"\n", "\n",
"Python provides the built-in [input()](https://docs.python.org/3/library/functions.html#input) function that prints a message to the user, called the **prompt**, and reads in what was typed in response as a `str` object. We use it to process a user's \"unreliable\" input to our program (i.e., a user might type in some invalid response). Further, we use the [random()](https://docs.python.org/3/library/random.html#random.random) function in the [random](https://docs.python.org/3/library/random.html) module to model the coin toss.\n", "Python provides the built-in [input()](https://docs.python.org/3/library/functions.html#input) function that prints a message to the user, called the **prompt**, and reads in what was typed in response as a `str` object. We use it to process a user's \"unreliable\" input to our program (i.e., a user might type in some invalid response). Further, we use the [random()](https://docs.python.org/3/library/random.html#random.random) function in the [random](https://docs.python.org/3/library/random.html) module to model the coin toss.\n",
"\n", "\n",
"A popular pattern to approach such **indefinite loops** is to go with a `while True` statement which on its own would cause Python to enter into an infinite loop. Then, once a certain event occurs, we `break` out of the loop.\n", "A popular pattern to approach such **indefinite loops** is to go with a `while True` statement, which on its own would cause Python to enter into an infinite loop. Then, once a particular event occurs, we `break` out of the loop.\n",
"\n", "\n",
"Let's look at a first naive implementation." "Let's look at a first and naive implementation."
] ]
}, },
{ {
@ -6278,7 +6278,7 @@
"\n", "\n",
"First, we divide the business logic into two functions `get_guess()` and `toss_coin()` that are controlled from within a `while`-loop.\n", "First, we divide the business logic into two functions `get_guess()` and `toss_coin()` that are controlled from within a `while`-loop.\n",
"\n", "\n",
"`get_guess()` not only reads in the user's input but also implements a simple input validation pattern in that the [strip()](https://docs.python.org/3/library/stdtypes.html?highlight=__contains__#str.strip) and [lower()](https://docs.python.org/3/library/stdtypes.html?highlight=__contains__#str.lower) methods remove preceeding and trailing whitespace and lower case the input ensuring that the user may spell the input in any possible way (e.g. all upper or lower case). Also, `get_guess()` checks if the user entered one of the two valid options. If so, it returns either `\"heads\"` or `\"tails\"`; if not, it returns `None`." "`get_guess()` not only reads in the user's input but also implements a simple input validation pattern in that the [strip()](https://docs.python.org/3/library/stdtypes.html?highlight=__contains__#str.strip) and [lower()](https://docs.python.org/3/library/stdtypes.html?highlight=__contains__#str.lower) methods remove preceding and trailing whitespace and lower case the input ensuring that the user may spell the input in any possible way (e.g., all upper or lower case). Also, `get_guess()` checks if the user entered one of the two valid options. If so, it returns either `\"heads\"` or `\"tails\"`; if not, it returns `None`."
] ]
}, },
{ {
@ -6351,7 +6351,7 @@
} }
}, },
"source": [ "source": [
"Second, we rewrite the `if`-`else`-logic to explictly handle the case where `get_guess()` returns `None`: Whenever the user enters something invalid, a warning is shown, and another try is granted. We use the `is` operator and not the `==` operator as `None` is a singleton object.\n", "Second, we rewrite the `if`-`else`-logic to handle the case where `get_guess()` returns `None` explicitly: Whenever the user enters something invalid, a warning is shown, and another try is granted. We use the `is` operator and not the `==` operator as `None` is a singleton object.\n",
"\n", "\n",
"The `while`-loop takes on the role of **glue code** that manages how other parts of the program interact with each other." "The `while`-loop takes on the role of **glue code** that manages how other parts of the program interact with each other."
] ]
@ -6411,7 +6411,7 @@
} }
}, },
"source": [ "source": [
"Now, the program's business logic is expressed in a clearer way. More importantly, we can now change it more easily. For example, we could make the `toss_coin()` function base the tossing on a probability distribution other than the uniform (i.e., replace the [random.random()](https://docs.python.org/3/library/random.html#random.random) function with another one). In general, a modular architecture leads to improved software maintenance." "Now, the program's business logic is expressed in a clearer way. More importantly, we can now change it more easily. For example, we could make the `toss_coin()` function base the tossing on a probability distribution other than the uniform (i.e., replace the [random.random()](https://docs.python.org/3/library/random.html#random.random) function with another one). In general, modular architecture leads to improved software maintenance."
] ]
}, },
{ {
@ -6435,13 +6435,13 @@
"source": [ "source": [
"**Iteration** is about **running blocks of code repeatedly**.\n", "**Iteration** is about **running blocks of code repeatedly**.\n",
"\n", "\n",
"There are two redundant approaches of achieving that.\n", "There are two redundant approaches to achieving that.\n",
"\n", "\n",
"First, we combine functions that call themselves with conditional statements. This concept is known as **recursion** and suffices to control the flow of execution in *every* way we desire. For a beginner, this approach of **backwards** reasoning might not be intuitive but it turns out to be a very useful tool to have in one's toolbox.\n", "First, we combine functions that call themselves with conditional statements. This concept is known as **recursion** and suffices to control the flow of execution in *every* way we desire. For a beginner, this approach of **backward** reasoning might not be intuitive, but it turns out to be a handy tool to have in one's toolbox.\n",
"\n", "\n",
"Second, the `while` and `for` statements are alternative and potentially more intuitive ways to express iteration as they correspond to a **forwards** reasoning. The `for` statement is **syntactic sugar** that allows to rewrite common occurences of the `while` statement in a concise way. Python provides the `break` and `continue` statements as well as an optional `else`-clause that make working with the `for` and `while` statements even more convenient.\n", "Second, the `while` and `for` statements are alternative and potentially more intuitive ways to express iteration as they correspond to a **forward** reasoning. The `for` statement is **syntactic sugar** that allows rewriting common occurrences of the `while` statement concisely. Python provides the `break` and `continue` statements as well as an optional `else`-clause that make working with the `for` and `while` statements even more convenient.\n",
"\n", "\n",
"**Iterables** are any **concrete data types** that support being looped over, for example, with the `for` statement. The idea behind iterables is an **abstract concept** that may or may not be implemented by any given concrete data type. For example, both `list` and `range` objects can be looped over. The `list` type is also a **container** as any given `list` objects \"contains\" pointers to other objects in memory. On the contrary, the `range` type does not point to any other objects but rather creates *new* `int` objects \"on the fly\" (i.e., when being looped over)." "**Iterables** are any **concrete data types** that support being looped over, for example, with the `for` statement. The idea behind iterables is an **abstract concept** that may or may not be implemented by any given concrete data type. For example, both `list` and `range` objects can be looped over. The `list` type is also a **container** as any given `list` objects \"contains\" pointers to other objects in memory. On the contrary, the `range` type does not point to any other objects but instead creates *new* `int` objects \"on the fly\" (i.e., when being looped over)."
] ]
} }
], ],

View file

@ -54,7 +54,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"**Q2**: Solving a problem with a **recursion** is not only popular in computer science and math. Name some examples from the fields of business or economics where problems are also solved in a **backwards** fashion!" "**Q2**: Solving a problem by **recursion** is not only popular in computer science and math. Name some examples from the fields of business or economics where problems are also solved in a **backward** fashion!"
] ]
}, },
{ {
@ -68,7 +68,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"**Q3**: Explain what **duck typing** means! Why can it cause problems? Why it is [not a bug but a feature](https://www.urbandictionary.com/define.php?term=It%27s%20not%20a%20bug%2C%20it%27s%20a%20feature)?" "**Q3**: Explain what **duck typing** means! Why can it cause problems? Why is it [not a bug but a feature](https://www.urbandictionary.com/define.php?term=It%27s%20not%20a%20bug%2C%20it%27s%20a%20feature)?"
] ]
}, },
{ {
@ -208,7 +208,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"**Q12**: Whereas a **recursion** may accidently result in a **never ending** program, `while`-loops and `for`-loops are guaranteed to **terminate**." "**Q12**: Whereas a **recursion** may accidentally result in a **never-ending** program, `while`-loops and `for`-loops are guaranteed to **terminate**."
] ]
}, },
{ {
@ -275,7 +275,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"A popular example for a problem that is solved with a recursion art the **[Towers of Hanoi](https://en.wikipedia.org/wiki/Tower_of_Hanoi)**.\n", "A popular example of a problem that is solved by recursion art the **[Towers of Hanoi](https://en.wikipedia.org/wiki/Tower_of_Hanoi)**.\n",
"\n", "\n",
"In its basic version, a tower consisting of, for example, four disks with increasing radii, is placed on the left-most of **three** adjacent spots. In the following, we refer to the number of disks as $n$, so here $n = 4$.\n", "In its basic version, a tower consisting of, for example, four disks with increasing radii, is placed on the left-most of **three** adjacent spots. In the following, we refer to the number of disks as $n$, so here $n = 4$.\n",
"\n", "\n",
@ -304,9 +304,9 @@
"source": [ "source": [
"Watch the following video by [MIT](https://www.mit.edu/)'s professor [Richard Larson](https://idss.mit.edu/staff/richard-larson/) for a comprehensive introduction.\n", "Watch the following video by [MIT](https://www.mit.edu/)'s professor [Richard Larson](https://idss.mit.edu/staff/richard-larson/) for a comprehensive introduction.\n",
"\n", "\n",
"The [MIT Blossoms Initative](https://blossoms.mit.edu/) is primarily aimed at high school students and does not have any prerequisites.\n", "The [MIT Blossoms Initiative](https://blossoms.mit.edu/) is primarily aimed at high school students and does not have any prerequisites.\n",
"\n", "\n",
"The video consists of three segments the last of which is *not* necessary to have watched in order to solve the tasks below. So, watch the video until 37:55." "The video consists of three segments, the last of which is *not* necessary to have watched to solve the tasks below. So, watch the video until 37:55."
] ]
}, },
{ {
@ -393,7 +393,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"As most likely the first couple of tries will result in *semantic* errors, it is advisable to have some sort of **visualization tool** for the progam's output: For example, an online version of the game can be found **[here](https://www.mathsisfun.com/games/towerofhanoi.html)**." "As most likely the first couple of tries will result in *semantic* errors, it is advisable to have some sort of **visualization tool** for the program's output: For example, an online version of the game can be found **[here](https://www.mathsisfun.com/games/towerofhanoi.html)**."
] ]
}, },
{ {
@ -402,7 +402,7 @@
"source": [ "source": [
"Let's first **generalize** the mathematical relationship from above.\n", "Let's first **generalize** the mathematical relationship from above.\n",
"\n", "\n",
"While the first number of $Sol(\\cdot)$ is the number of `disks` $n$, the second and third \"numbers\" are actually the **labels** for the three spots. Instead of spots `1`, `2`, and `3` we could also call them `\"left\"`, `\"center\"`, and `\"right\"` in our Python implementation. When \"passed\" to the $Sol(\\cdot)$ \"function\" they take on the role of an `origin` (= $o$) and `destination` (= $d$) pair.\n", "While the first number of $Sol(\\cdot)$ is the number of `disks` $n$, the second and third \"numbers\" are the **labels** for the three spots. Instead of spots `1`, `2`, and `3`, we could also call them `\"left\"`, `\"center\"`, and `\"right\"` in our Python implementation. When \"passed\" to the $Sol(\\cdot)$ \"function\" they take on the role of an `origin` (= $o$) and `destination` (= $d$) pair.\n",
"\n", "\n",
"So, the expression $Sol(4, 1, 3)$ is the same as $Sol(4, \\text{\"left\"}, \\text{\"right\"})$ and describes the problem of moving a tower consisting of $n = 4$ disks from `origin` `1` / `\"left\"` to `destination` `3` / `right`. As we have seen in the video, we need some `intermediate` (= $i$) spot." "So, the expression $Sol(4, 1, 3)$ is the same as $Sol(4, \\text{\"left\"}, \\text{\"right\"})$ and describes the problem of moving a tower consisting of $n = 4$ disks from `origin` `1` / `\"left\"` to `destination` `3` / `right`. As we have seen in the video, we need some `intermediate` (= $i$) spot."
] ]
@ -420,9 +420,9 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"In words, this means that in order to move a tower consisting of $n$ disks from an `origin` $o$ to a `destination` $d$, three steps must be executed:\n", "In words, this means that to move a tower consisting of $n$ disks from an `origin` $o$ to a `destination` $d$, three steps must be executed:\n",
"\n", "\n",
"1. Move the top most $n - 1$ disks of the tower temporarily from $o$ to $i$ (= sub-problem 1)\n", "1. Move the topmost $n - 1$ disks of the tower temporarily from $o$ to $i$ (= sub-problem 1)\n",
"2. Move the remaining and largest disk from $o$ to $d$\n", "2. Move the remaining and largest disk from $o$ to $d$\n",
"3. Move the the $n - 1$ disks from the temporary spot $i$ to $d$ (= sub-problem 2)\n", "3. Move the the $n - 1$ disks from the temporary spot $i$ to $d$ (= sub-problem 2)\n",
"\n", "\n",
@ -435,9 +435,9 @@
"source": [ "source": [
"$Sol(\\cdot)$ can be written in Python as a function `sol()` that takes three arguments `disks`, `origin`, and `destination` that mirror $n$, $o$, and $d$.\n", "$Sol(\\cdot)$ can be written in Python as a function `sol()` that takes three arguments `disks`, `origin`, and `destination` that mirror $n$, $o$, and $d$.\n",
"\n", "\n",
"Assume that all arguments to `sol()` will be `int` objects!\n", "Assume that all arguments to `sol()` are `int` objects!\n",
"\n", "\n",
"Once completed, `sol()` should print out all the moves in the correct order. With **printing a move**, we simply mean a line like \"1 -> 3\", short for \"Move the top-most disk from spot 1 to spot 3\".\n", "Once completed, `sol()` should print out all the moves in the correct order. With **printing a move**, we mean a line like \"1 -> 3\", short for \"Move the top-most disk from spot 1 to spot 3\".\n",
"\n", "\n",
"Write your answers to **Q15.5** to **Q15.7** into the subsequent code cell and finalize `sol()`! No need to write a docstring or validate the input here." "Write your answers to **Q15.5** to **Q15.7** into the subsequent code cell and finalize `sol()`! No need to write a docstring or validate the input here."
] ]
@ -550,9 +550,9 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"The previous `sol()` implementation does the job but the conditional statement needed in unnecessarily tedious. \n", "The previous `sol()` implementation does the job, but the conditional statement needed in unnecessarily tedious. \n",
"\n", "\n",
"Let's create a more concise `hanoi()` function that in addition to a positional `disks` argument takes three keyword-only arguments `origin`, `intermediate`, and `destination` with default values `\"left\"`, `\"center\"`, and `\"right\"`.\n", "Let's create a more concise `hanoi()` function that, in addition to a positional `disks` argument, takes three keyword-only arguments `origin`, `intermediate`, and `destination` with default values `\"left\"`, `\"center\"`, and `\"right\"`.\n",
"\n", "\n",
"Write your answers to **Q15.9** and **Q15.10** into the subsequent code cell and finalize `hanoi()`! No need to write a docstring or validate the input here." "Write your answers to **Q15.9** and **Q15.10** into the subsequent code cell and finalize `hanoi()`! No need to write a docstring or validate the input here."
] ]
@ -588,7 +588,7 @@
"\n", "\n",
"Figure out how the arguments are passed on in the two recursive `hanoi()` calls!\n", "Figure out how the arguments are passed on in the two recursive `hanoi()` calls!\n",
"\n", "\n",
"Also, write a `print()` statement analogous to the one in `sol()` in between the two recursive function calls. Is it ok to just copy and paste it?" "Also, write a `print()` statement analogous to the one in `sol()` in between the two recursive function calls. Is it ok to copy and paste it?"
] ]
}, },
{ {
@ -638,7 +638,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"We could of course also use **numeric labels** for the three steps like so." "We could, of course, also use **numeric labels** for the three steps like so."
] ]
}, },
{ {
@ -661,13 +661,13 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Let's say, we did not know about the **analytical formula** for the number of **minimal moves** given $n$.\n", "Let's say we did not know about the **analytical formula** for the number of **minimal moves** given $n$.\n",
"\n", "\n",
"In such cases, we could modify a recursive function to return a count value to be passed up the recursion tree.\n", "In such cases, we could modify a recursive function to return a count value to be passed up the recursion tree.\n",
"\n", "\n",
"In fact, this is similar to what we do in the recursive versions of `factorial()` and `fibonacci()` in [Chapter 4](https://github.com/webartifex/intro-to-python/blob/master/04_iteration.ipynb) where we pass up an intermediate result.\n", "This is similar to what we do in the recursive versions of `factorial()` and `fibonacci()` in [Chapter 4](https://github.com/webartifex/intro-to-python/blob/master/04_iteration.ipynb), where we pass up an intermediate result.\n",
"\n", "\n",
"Let's create a `hanoi_moves()` function that follows the same internal logic as `hanoi()` but instead of printing out the moves returns the number of steps done so far in the recursion.\n", "Let's create a `hanoi_moves()` function that follows the same internal logic as `hanoi()`, but, instead of printing out the moves, returns the number of steps done so far in the recursion.\n",
"\n", "\n",
"Write your answers to **Q15.12** to **Q15.14** into the subsequent code cell and finalize `hanoi_moves()`! No need to write a docstring or validate the input here." "Write your answers to **Q15.12** to **Q15.14** into the subsequent code cell and finalize `hanoi_moves()`! No need to write a docstring or validate the input here."
] ]
@ -742,7 +742,7 @@
"source": [ "source": [
"Observe how quickly the `hanoi_moves()` function slows down for increasing `disks` arguments.\n", "Observe how quickly the `hanoi_moves()` function slows down for increasing `disks` arguments.\n",
"\n", "\n",
"With `disks` in the range from `24` through `26` the computation time roughly doubles for each increase of `disks` by 1.\n", "With `disks` in the range from `24` through `26`, the computation time roughly doubles for each increase of `disks` by 1.\n",
"\n", "\n",
"**Q15.16**: Execute the code cells below and see for yourself!" "**Q15.16**: Execute the code cells below and see for yourself!"
] ]
@ -788,7 +788,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"The above `hanoi()` prints the optimal solution's moves in the correct order but fails to label each move with an order number. This can be build in by passing on one more argument `offset` down the recursion tree. As the logic gets a bit \"involved\", `hanoi_ordered()` below is almost finished.\n", "The above `hanoi()` prints the optimal solution's moves in the correct order but fails to label each move with an order number. This can be built in by passing on one more argument `offset` down the recursion tree. As the logic gets a bit \"involved,\" `hanoi_ordered()` below is almost finished.\n",
"\n", "\n",
"Write your answers to **Q15.17** and **Q15.18** into the subsequent code cell and finalize `hanoi_ordered()`! No need to write a docstring or validate the input here." "Write your answers to **Q15.17** and **Q15.18** into the subsequent code cell and finalize `hanoi_ordered()`! No need to write a docstring or validate the input here."
] ]
@ -879,7 +879,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Lastly, it is to be mentioned that for problem instances with a small `disks` argument it is easier to collect all the moves first in a list and then add the order number with the [enumerate()](https://docs.python.org/3/library/functions.html#enumerate) built-in." "Lastly, it is to be mentioned that for problem instances with a small `disks` argument, it is easier to collect all the moves first in a list and then add the order number with the [enumerate()](https://docs.python.org/3/library/functions.html#enumerate) built-in."
] ]
}, },
{ {

File diff suppressed because it is too large Load diff