From 59fe404e0b24327b7271ff66d0c63fd88d9c8e1f Mon Sep 17 00:00:00 2001 From: Saransh Chopra Date: Tue, 13 Aug 2024 16:27:43 +0100 Subject: [PATCH] chore: port basics + variables + functions from doctoral school --- ch01python/00pythons.ipynb.py | 264 ++++++--------- ch01python/015variables.ipynb.py | 195 ++++++----- ...nctions.ipynb.py => 016functions.ipynb.py} | 0 ch01python/016using_functions.ipynb.py | 241 -------------- ch01python/017using_functions.ipynb.py | 310 ++++++++++++++++++ 5 files changed, 527 insertions(+), 483 deletions(-) rename ch01python/{04functions.ipynb.py => 016functions.ipynb.py} (100%) delete mode 100644 ch01python/016using_functions.ipynb.py create mode 100644 ch01python/017using_functions.ipynb.py diff --git a/ch01python/00pythons.ipynb.py b/ch01python/00pythons.ipynb.py index 8d0d63de..df18501b 100644 --- a/ch01python/00pythons.ipynb.py +++ b/ch01python/00pythons.ipynb.py @@ -12,224 +12,178 @@ # --- # %% [markdown] -# # Introduction to Python - -# %% [markdown] -# ## Introduction - -# %% [markdown] -# ### Why teach Python? - -# %% [markdown] +# # Introduction +# +# ## Why teach Python? # # * In this first session, we will introduce [Python](http://www.python.org). # * This course is about programming for data analysis and visualisation in research. # * It's not mainly about Python. # * But we have to use some language. # - -# %% [markdown] # ### Why Python? - -# %% [markdown] # -# * Python is quick to program in -# * Python is popular in research, and has lots of libraries for science -# * Python interfaces well with faster languages +# * Python has a readable [syntax](https://en.wikipedia.org/wiki/Syntax_(programming_languages)) that makes it relatively quick to pick up. +# * Python is popular in research, and has lots of libraries for science. +# * Python interfaces well with faster languages. # * Python is free, so you'll never have a problem getting hold of it, wherever you go. # - -# %% [markdown] +# # ### Why write programs for research? - -# %% [markdown] # -# * Not just labour saving -# * Scripted research can be tested and reproduced +# * Not just labour saving. +# * Scripted research can be tested and reproduced. +# +# ### Sensible input - reasonable output # - -# %% [markdown] -# ### Sensible Input - Reasonable Output - -# %% [markdown] # Programs are a rigorous way of describing data analysis for other researchers, as well as for computers. # -# Computational research suffers from people assuming each other's data manipulation is correct. By sharing codes, -# which are much more easy for a non-author to understand than spreadsheets, we can avoid the "SIRO" problem. The old saw "Garbage in Garbage out" is not the real problem for science: +# Computational research suffers from people assuming each other's data manipulation is correct. By sharing _readable_, _reproducible_ and _well-tested_ code, which makes all of the data processing steps used in an analysis explicit and checks that each of those steps behaves as expected, we enable other researchers to understand and assesss the validity of those analysis steps for themselves. In a research code context the problem is generally not so much _garbage in, garbage out_, but _sensible input, reasonable output_: 'black-box' analysis pipelines that given sensible looking data inputs produce reasonable appearing but incorrect analyses as outputs. # -# * Sensible input -# * Reasonable output +# ## Many kinds of Python # +# ### Python notebooks # - -# %% [markdown] -# ## Many kinds of Python - -# %% [markdown] -# ### The Jupyter Notebook - -# %% [markdown] -# The easiest way to get started using Python, and one of the best for research data work, is the Jupyter Notebook. - -# %% [markdown] -# In the notebook, you can easily mix code with discussion and commentary, and mix code with the results of that code; -# including graphs and other data visualisations. +# A particularly easy way to get started using Python, and one particularly suited to the sort of exploratory work common in a research context, is using [Jupyter](https://jupyter.org/https://jupyter.org/) notebooks. +# +# In a notebook, you can easily mix code with discussion and commentary, and display the results outputted by code alongside the code itself, including graphs and other data visualisations. +# +# For example if we wish to plot a figure-eight curve ([lemniscate](https://en.wikipedia.org/wiki/Lemniscate_of_Gerono)), we can include the parameteric equations +# $x = \sin(2\theta) / 2, y = \cos(\theta), \theta \in [0, 2\pi)$ which mathematically define the curve as well as corresponding Python code to plot the curve and the output of that code all within the same notebook: # %% -### Make plot -# %matplotlib inline -import math - +# Plot lemniscate curve import numpy as np import matplotlib.pyplot as plt -theta = np.arange(0, 4 * math.pi, 0.1) -eight = plt.figure() -axes = eight.add_axes([0, 0, 1, 1]) -axes.plot(0.5 * np.sin(theta), np.cos(theta / 2)) +theta = np.linspace(0, 2 * np.pi, 100) +x = np.sin(2 * theta) / 2 +y = np.cos(theta) +fig, ax = plt.subplots(figsize=(3, 6)) +lines = ax.plot(x, y) # %% [markdown] -# These notes are created using Jupyter notebooks and you may want to use it during the course. However, Jupyter notebooks won't be used for most of the activities and exercises done in class. To get hold of a copy of the notebook, follow the setup instructions shown on the course website, use the installation in Desktop@UCL (available in the teaching cluster rooms or [anywhere](https://www.ucl.ac.uk/isd/services/computers/remote-access/desktopucl-anywhere)), or go clone the [repository](https://github.com/UCL/rsd-engineeringcourse) on GitHub. - -# %% [markdown] -# Jupyter notebooks consist of discussion cells, referred to as "markdown cells", and "code cells", which contain Python. This document has been created using Jupyter notebook, and this very cell is a **Markdown Cell**. +# We will be mainly mainly working with Jupyter notebooks in this course and will be using [Jupyter Lab](https://jupyterlab.readthedocs.io/) to view, edit and run the notebooks. To install Jupyter Lab, follow the setup instructions shown [on the course website](../index.html#what-you-need-for-the-course), or use the installation in [Desktop@UCL](https://my.desktop.ucl.ac.uk/). +# +# #### Notebook cells +# +# Jupyter notebooks consist of sequence of _cells_. Cells can be of two main types: +# +# * _Markdown cells_: Cells containing descriptive text and discussion with rich-text formatting via the [Markdown](https://en.wikipedia.org/wiki/Markdown) text markup language. +# * _Code cells_: Cells containing Python code, which is displayed with syntax highlighting. The results returned by the computation performed when running the cell are displayed below the cell as the cell _output_, with Jupyter having a _rich display_ system allowing embedding a range of different outputs including for example static images, videos and interactive widgets. +# +# The document you are currently reading is a Jupyter notebook, and this text you are reading is a Markdown cell in the notebook. Below we see an example of a code cell. # %% print("This cell is a code cell") -# %% [markdown] -# Code cell inputs are numbered, and show the output below. +# %% [markdown] jp-MarkdownHeadingCollapsed=true +# Code cell inputs are numbered, with the cell output shown immediately below the input. Here the output is the text that we instruct the cell to print to the standard output stream. Cells will also display a representation of the value outputted by the last line in the cell, if any. For example + +# %% +print("This text will be displayed\n") +"This is text will also be displayed\n" # %% [markdown] -# Markdown cells contain text which uses a simple format to achive pretty layout, -# for example, to obtain: +# There is a small difference in the formatting of the output here, with the `print` function displaying the text without quotation mark delimiters and with any _escaped_ special characters (such as the `"\n"` newline character here) processed. +# +# #### Markdown formatting +# +# The Markdown language used in Markdown cells provides a simple way to add basic text formatting to the rendered output while aiming to be retain the readability of the original Markdown source. For example to achieve the following rendered output text # -# **bold**, *italic* +# **bold**, *italic*, ~~striketrough~~, `monospace` # # * Bullet # # > Quote # -# We write: +# [Link to search](https://duckduckgo.com/) # -# **bold**, *italic* +# We can use the following Markdown text # -# * Bullet +# ```Markdown +# **bold**, *italic*, ~~striketrough~~, `monospace` # -# > Quote +# * Bullet # -# See the Markdown documentation at [This Hyperlink](http://daringfireball.net/projects/markdown/) - -# %% [markdown] -# ### Typing code in the notebook - -# %% [markdown] -# When working with the notebook, you can either be in a cell, typing its contents, or outside cells, moving around the notebook. -# -# * When in a cell, press escape to leave it. When moving around outside cells, press return to enter. -# * Outside a cell: -# * Use arrow keys to move around. -# * Press `b` to add a new cell below the cursor. -# * Press `m` to turn a cell from code mode to markdown mode. -# * Press `shift`+`enter` to calculate the code in the block. -# * Press `h` to see a list of useful keys in the notebook. -# * Inside a cell: -# * Press `tab` to suggest completions of variables. (Try it!) - -# %% [markdown] -# *Supplementary material*: Learn more about [Jupyter notebooks](https://jupyter.org/). - -# %% [markdown] -# The `%%` at the beginning of a cell is called *magics*. There's a [large list of them available](https://ipython.readthedocs.io/en/stable/interactive/magics.html) and you can [create your own](http://ipython.readthedocs.io/en/stable/config/custommagics.html). +# > Quote # - -# %% [markdown] -# ### Python at the command line - -# %% [markdown] -# Data science experts tend to use a "command line environment" to work. You'll be able to learn this at our ["Software Carpentry" workshops](http://github-pages.arc.ucl.ac.uk/software-carpentry/), which cover other skills for computationally based research. - -# %% language="bash" -# # Above line tells Python to execute this cell as *shell code* -# # not Python, as if we were in a command line +# [Link to search](https://duckduckgo.com/) +# ``` # -# python -c "print(2 * 4)" - -# %% [markdown] -# ### Python scripts - -# %% [markdown] -# Once you get good at programming, you'll want to be able to write your own full programs in Python, which work just -# like any other program on your computer. Here are some examples: - -# %% language="bash" -# echo "print(2 * 4)" > eight.py -# python eight.py - -# %% [markdown] -# We can make the script directly executable (on Linux or Mac) by inserting a [hashbang](https://en.wikipedia.org/wiki/Shebang_(Unix%29)) and [setting the permissions](http://v4.software-carpentry.org/shell/perm.html) to execute. +# For more information [see this tutorial notebook in the official Jupyter documentation](https://nbviewer.org/github/jupyter/notebook/blob/master/docs/source/examples/Notebook/Working%20With%20Markdown%20Cells.ipynb). # -# Note, the `%%writefile` cell magic will write the contents of the cell to the file `fourteen.py`. - -# %% -# %%writefile fourteen.py -# #! /usr/bin/env python -print(2 * 7) - -# %% language="bash" -# chmod u+x fourteen.py -# ./fourteen.py - -# %% [markdown] -# ### Python Modules +# #### Editing and running cells in the notebook +# +# When working with the notebook, you can either be editing the content of a cell (termed _edit mode_), or outside the cells, navigating around the notebook (termed _command mode_). +# +# * When in _edit mode_ in a cell, press esc to leave it and change to _command mode_. +# * When navigating between cells in _command mode_, press enter to change in to _edit mode_ in the selected cell. +# * When in _command mode_: +# * The currently selected cell will be shown by a
blue highlight
to the left of the cell. +# * Use the arrow keys and to navigate up and down between cells. +# * Press a to add a new cell above the currently selected cell. +# * Press b to add a new cell below the currently selected cell. +# * Press dd to delete the currently selected cell. +# * Press m to change a code cell to a Markdown cell. +# * Press y to change a Markdown cell to a code cell. +# * Press shift+l to toggle displaying line numbers on the currently selected cell. +# * Press shift+enter to run the code in a currently selected code cell and move to the next cell. +# * Press ctrl+enter to run the code in a currently selected code cell and keep the current cell selected. +# * Press ctrl+shift+c to access the command palette and search useful actions in the notebook. +# * When in _edit mode_: +# * Press tab to suggest completions of variable names and object attribute. (Try it!) +# * Press shift+tab when in the argument list of a function to display a pop-up showing documentation for the function. +# +# *Supplementary material*: Learn more about [Jupyter notebooks](https://jupyter-notebook.readthedocs.io/en/stable/notebook.html/). +# +# ### Python interpreters +# +# An alternative to running Python code via a notebook interface is to run commands in a Python _interpreter_ (also known as an _interactive shell_ or _read-eval-print-loop (REPL)_). This is similar in concept to interacting with your operating system via a command-line interface such as the `bash` or `zsh` shells in Linux and MacOS or `Command Prompt` in Windows. A Python interpreter provides a _prompt_ into which we can type Python code corresponding to commands we wish to execute; we then execute this code by hitting enter with any output from the computation being displayed before returning to the prompt again. +# +# We will not further explore using Python via an interpreter in this course but if you wish to learn more about such command-line interfaces we recommend you attend one of the [Software Carpentry](https://software-carpentry.org/lessons/https://software-carpentry.org/lessons/) workshops (sessions are regularly organised by [our group](http://rits.github-pages.ucl.ac.uk/software-carpentry/)), which covers this and other skills for computationally based research. # %% [markdown] -# A Python module is a file that contains a set of related functions or other code. The filename must have a `.py` extension. +# ### Python libraries +# +# A very common requirement in research (and all other!) programming is needing to reuse code in multiple different files. While it may seem that copying-and-pasting is an adequate solution to this problem, this should generally be avoided wherever possible and code which we wish to reuse _factored out_ in to _libraries_ which we we can _import_ in to other files to access the functionality of this code. # -# We can write our own Python modules that we can import and use in other scripts or even in this notebook: +# Compared to copying and pasting code, writing and using libraries has the major advantage of meaning if we spot a bug in the code we only need to fix it once in the underlying library, and we straight away have the fixed code available everywhere the library is used rather than having to separately implement the fix in each file it is used. This similarly applies to for example adding new features to a piece of code. By creating libraries we also make it easier for other researchers to use our code. # +# While it is simple to use libraries within a notebook (and we have already seen examples of this when we imported the Python libraries NumPy and Matplotlib in the figure-eight plot example above), it is non-trivial to use code from one notebook in another without copying-and-pasting. To create Python libraries we therefore generally write the code in to text files with a `.py` extension which in Python terminology are called _modules_ . The code can in these file can then be used in notebooks (or other modules) using the Python `import` statement. For example the cell below creates a file `draw_eight.py` in the same directory as this notebook containing Python code defining a _function_ (we will cover how to define and call functions later in the course) which creates a figure-eight plot and return the figure object. # %% -# %%writefile draw_eight.py -# Above line tells the notebook to treat the rest of this -# cell as content for a file on disk. -import math +# %%writefile draw_eight.py +# The above line tells the notebook to write the rest of the cell content to a file draw_eight.py import numpy as np import matplotlib.pyplot as plt def make_figure(): - """Plot a figure of eight.""" - - theta = np.arange(0, 4 * math.pi, 0.1) - eight = plt.figure() - axes = eight.add_axes([0, 0, 1, 1]) - axes.plot(0.5 * np.sin(theta), np.cos(theta / 2)) - - return eight - + theta = np.linspace(0, 2 * np.pi, 100) + fig, ax = plt.subplots(figsize=(3, 6)) + ax.plot(np.sin(2 * theta) / 2, np.cos(theta)) + return fig # %% [markdown] -# In a real example, we could edit the file on disk -# using a code editor such as [VS code](https://code.visualstudio.com/). +# We can use this code in the notebook by _importing_ the `draw_eight` module and then _calling_ the `make_figure` function defined in the module. # %% -import draw_eight # Load the library file we just wrote to disk - -# %% -image = draw_eight.make_figure() +import draw_eight # Load the library +fig = draw_eight.make_figure() # %% [markdown] -# Note, we can import our `draw_eight` module in this notebook only if the file is in our current working directory (i.e. the folder this notebook is in). -# -# To allow us to import our module from anywhere on our computer, or to allow other people to reuse it on their own computer, we can create a [Python package](https://packaging.python.org/en/latest/). +# We will cover how to import and use functionality from libraries, how to install third-party libraries and how to write your own libraries that can be shared and used by other in this course. # - -# %% [markdown] -# ### Python packages +# ### Python scripts # -# A package is a collection of modules that can be installed on our computer and easily shared with others. We will learn how to create packages later on in this course. +# While Jupyter notebooks are a great medium for learning how to use Python and for exploratory work, there are some drawbacks: # -# There is a huge variety of available packages to do pretty much anything. For instance, try `import antigravity` or `import this`. +# * The require Jupyter Lab (or a similar application) to be installed to run the notebook. +# * It can be difficult to run notebooks non-interactively, for example when scheduling a job on a cluster [such as those offered by UCL Research Computing](https://www.rc.ucl.ac.uk/docs/Background/Cluster_Computing). +# * The flexibility of being able to run the code in cells in any order can also make it difficult to reason how outputs were produced and can lead to non-reproducible analyses. +# +# In some settings it can therefore be preferrable to write Python _scripts_ - that is files (typically with a `.py` extension) which contain Python code which completely describes a computational task to perform and that can be run by passing the name of the script file to the `python` program in a command-line environment. Optionally scripts may also allow passing in arguments from the command-line to control the execution of the script. As scripts are generally run from text-based terminals, non-text outputs such as images will generally be saved to files on disk. # +# Python scripts are well suited to for example for describing computationally demanding simulations or analyses to run as long jobs on a remote server or cluster, or tasks where the input and output is mainly at the file level - for instance batch processing a series of data files. We will not cover how to write Python scripts in this course, however you can learn more about this topics in our [MPHY001: _Research software engineering with Python_ course](http://github-pages.ucl.ac.uk/rsd-engineeringcourse/). diff --git a/ch01python/015variables.ipynb.py b/ch01python/015variables.ipynb.py index 1a2513c0..4d5f8f4c 100644 --- a/ch01python/015variables.ipynb.py +++ b/ch01python/015variables.ipynb.py @@ -13,140 +13,161 @@ # %% [markdown] # ## Variables +# +# ### Variable assignment +# +# Python has built-in support for arithmetic expressions and so can be used as a calculator. When we evaluate an expression in Python, the result is displayed, but not necessarily stored anywhere. -# %% [markdown] -# ### Variable Assignment - -# %% [markdown] -# When we generate a result, the answer is displayed, but not kept anywhere. +# %% +2 + 2 # %% -2 * 3 +4 * 2.5 # %% [markdown] -# If we want to get back to that result, we have to store it. We put it in a box, with a name on the box. This is a **variable**. - -# %% -six = 2 * 3 +# If we want to access the result in subsequent code, we have to store it. We put it in a box, with a name on the box. This is a _variable_. In Python we _assign_ a value to a variable using the _assignment operator_ `=` # %% -print(six) +four = 2 + 2 +four # %% [markdown] -# If we look for a variable that hasn't ever been defined, we get an error. +# As well as numeric [literal values](https://en.wikipedia.org/wiki/Literal_(computer_programming)) Python also has built in support for representing textual data as sequences of characters, which in computer science terminology are termed [_strings_](https://en.wikipedia.org/wiki/String_(computer_science)). Strings in Python are indicated by enclosing their contents in either a pair of single quotation marks `'...'` or a pair of double quotation marks `"..."`, for example # %% -print(seven) +greeting = "hello world" # %% [markdown] -# That's **not** the same as an empty box, well labeled: +# ### Naming variables +# +# We can name variables with any combination of lower and uppercase characters, digits and underscores `_` providing the first character is not a digit and the name is not a [reserved keyword](https://docs.python.org/3/reference/lexical_analysis.html#keywords). # %% -nothing = None +fOuR = 4 # %% -print(nothing) +four_integer = 4 # %% -type(None) +integer_4 = 4 -# %% [markdown] -# (None is the special python value for a no-value variable.) +# %% +# invalid as name cannot begin with a digit +4_integer = 4 -# %% [markdown] -# *Supplementary Materials*: There's more on variables at [Software Carpentry's Python lesson](https://swcarpentry.github.io/python-novice-inflammation/01-intro.html). +# %% +# invalid as for is a reserved word +for = 4 # %% [markdown] -# Anywhere we could put a raw number, we can put a variable label, and that works fine: +# It is good practice to give variables descriptive and meaningful names to help make code self-documenting. As most modern development environments (including Jupyter Lab!) offer _tab completion_ there is limited disadvantage from a keystroke perspective of using longer names. Note however that the names we give variables only have meaning to us: # %% -print(5 * six) +two_plus_two = 5 -# %% -scary = six * six * six +# %% [markdown] +# ### Undefined variables and `None` +# +# If we try to evaluate a variable that hasn't ever been defined, we get an error. # %% -print(scary) +seven # %% [markdown] -# ### Reassignment and multiple labels - -# %% [markdown] -# But here's the real scary thing: it seems like we can put something else in that box: +# In Python names are case-sensitive so for example `six`, `Six` and `SIX` are all different variable names # %% -scary = 25 +six = 6 +six # %% -print(scary) - -# %% [markdown] -# Note that **the data that was there before has been lost**. +Six # %% [markdown] -# No labels refer to it any more - so it has been "Garbage Collected"! We might imagine something pulled out of the box, and thrown on the floor, to make way for the next occupant. - -# %% [markdown] -# In fact, though, it is the **label** that has moved. We can see this because we have more than one label refering to the same box: +# There is a special `None` keyword in Python which can be assigned to variables to indicate a variable with no-value. This is _not_ the same as an undefined variable: # %% -name = "Eric" - -# %% -nom = name +nothing # %% -print(nom) +nothing = None +nothing # %% -print(name) +print(nothing) # %% [markdown] -# And we can move just one of those labels: +# Anywhere we can use a literal value, we can instead use a variable name, for example # %% -nom = "Idle" +5 + four * six # %% -print(name) - -# %% -print(nom) +scary = six * six * six +scary # %% [markdown] -# So we can now develop a better understanding of our labels and boxes: each box is a piece of space (an *address*) in computer memory. -# Each label (variable) is a reference to such a place. +# *Supplementary Materials*: There's more on variables at +# [Software Carpentry](https://swcarpentry.github.io/python-novice-inflammation/01-intro/index.html). # %% [markdown] -# When the number of labels on a box ("variables referencing an address") gets down to zero, then the data in the box cannot be found any more. +# ### Reassignment and multiple labels # %% [markdown] -# After a while, the language's "Garbage collector" will wander by, notice a box with no labels, and throw the data away, **making that box -# available for more data**. +# We can reassign a variable - that is change what is in the box the variable labels. -# %% [markdown] -# Old fashioned languages like C and Fortran don't have Garbage collectors. So a memory address with no references to it -# still takes up memory, and the computer can more easily run out. +# %% +scary = 25 +scary # %% [markdown] -# So when I write: +# The data that was previously labelled by the variable is lost. No labels refer to it any more - so it has been [_garbage collected_](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)). We might imagine something pulled out of the box, and disposed of, to make way for the next occupant. In reality, though, it is the _label_ that has moved. +# +# We can see this more clearly if we have more than one label referring to the same box # %% -name = "Michael" +name = "Grace Hopper" +nom = name +print(name) +print(nom) # %% [markdown] -# The following things happen: +# and we move just one of those labels: + +# %% +nom = "Grace Brewster Murray Hopper" +print(name) +print(nom) # %% [markdown] -# 1. A new text **object** is created, and an address in memory is found for it. -# 1. The variable "name" is moved to refer to that address. -# 1. The old address, containing "James", now has no labels. -# 1. The garbage collector frees the memory at the old address. +# ### Variables and memory +# +# We can now better understand our mental model of variables as labels and boxes: each box is a piece of space (an *address*) in computer memory. Each label (_variable_) is a reference to such a place and the data contained in the memory defines an _object_ in Python. Python objects come in different types - so far we have encountered both numeric (integer) and textual (string) types - more on this later. +# +# When the number of labels on a box (_variables referencing an address_) gets down to zero, then the data in the box cannot be accessed any more. This will trigger Python's garbage collector, which will then 'empty' the box (_deallocated the memory at the address_), making it available again to store new data. +# +# Lower-level languages such as C and Fortran do not have garbage collectors as a standard feature. So a memory address with no references to it and which has not been specifically marked as free remains unavailable for other usage, which can lead to difficult to fix [_memory leak_](https://en.wikipedia.org/wiki/Memory_leak) bugs. +# +# When we execute + +# %% +name = "Grace Hopper" +nom = name +nom = "Grace Brewster Murray Hopper" +name = "Admiral Hopper" # %% [markdown] -# **Supplementary materials**: There's an online python tutor which is great for visualising memory and references. Try the [scenario we just looked at](http://www.pythontutor.com/visualize.html#code=name%20%3D%20%22Eric%22%0Anom%20%3D%20name%0Aprint%28nom%29%0Aprint%28name%29%0Anom%20%3D%20%22Idle%22%0Aprint%28name%29%0Aprint%28nom%29%0Aname%20%3D%20%22Michael%22%0Aprint%28name%29%0Aprint%28nom%29%0A&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false). +# the following happens # -# Labels are contained in groups called "frames": our frame contains two labels, 'nom' and 'name'. +# 1. A new text (_string_) object `"Grace Hopper"` is created at a free address in memory and the variable `name` is set to refer to that address +# 2. The variable `nom` is set to refer to the object at the address referenced by `name` +# 3. A new text (_string_) object `"Grace Brewster Murray Hopper"` is created at a free address in memory and the variable `nom` is set to refer to that address +# 4. A new text (_string_) object `"Admiral Hopper"` is created at a free address in memory, the variable `name` is set to refer to that address and the garbage collector deallocates the memory used to hold `"Grace Hopper"` as this memory is no longer referenced by any variables. + +# %% [markdown] +# _Supplementary materials_: The website [Python Tutor](https://pythontutor.com/) has a great interactive tool for visualizing how memory and references work in Python which is great for visualising memory and references. +# Try the [scenario we just looked at](https://pythontutor.com/visualize.html#code=name%20%3D%20%22Grace%20Hopper%22%0Anom%20%3D%20name%0Anom%20%3D%20%22Grace%20Brewster%20Murray%20Hopper%22%0Aname%20%3D%20%22Admiral%20Hopper%22&cumulative=false&curInstr=4&heapPrimitives=true&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false). + # %% [markdown] # ### Objects and types @@ -223,37 +244,37 @@ # ...tells us something important. Even if we don't understand the rest, this is useful for debugging! # %% [markdown] -# ### Variables and the notebook kernel +# ### Variables in notebooks and kernels # %% [markdown] -# When I type code in the notebook, the objects live in memory between cells. - -# %% -number = 0 +# When code cells are executed in a notebook, the variable names and values of the referenced objects persist between cells # %% -print(number) +number = 1 # %% [markdown] -# If I change a variable: +# There if we change a variable in one cell # %% number = number + 1 -# %% -print(number) - # %% [markdown] # It keeps its new value for the next cell. -# %% [markdown] -# But cells are **not** always evaluated in order. - -# %% [markdown] -# If I now go back to Input 31, reading `number = number + 1`, I can run it again, with Shift-Enter. The value of `number` will change from 2 to 3, then from 3 to 4 - but the output of the next cell (containing the `print` statement) will not change unless I rerun that too. Try it! - -# %% [markdown] -# So it's important to remember that if you move your cursor around in the notebook, it doesn't always run top to bottom. +# %% +number # %% [markdown] -# **Supplementary material**: (1) [Jupyter notebook documentation](https://jupyter-notebook.readthedocs.io/en/latest/). +# In Jupyter terminology the Python process in which we run the code in a notebook in is termed a _kernel_. The _kernel_ stores the variable names and referenced objects created by any code cells in the notebook that have been previously run. The `Kernel` menu in the menu bar at the top of the JupyterLab interface has an option `Restart kernel...`. Running this will restart the kernel currently being used by the notebook, clearing any currently defined variables and returning the kernel to a 'clean' state. As you cannot restore a kernel once restarted a warning message is displayed asking you to confirm you wish to restart. +# +# ### Cell run order +# +# Cells do **not** have to be evaluated in the order they appear in a notebook. +# +# If we go back to the code cell above with contents `number = number + 1`, and run it again, with shift+enter then `number` will change from 2 to 3, then from 3 to 4. Try it! +# +# However, running cells out of order like this can make it hard to keep track of what values are currently assigned to variables. It also makes it difficult to reproduce computations as getting the same output requires rerunning the cells in the same order, and it will not always be possible to reconstruct what the order used was. +# +# The number in square brackets in the prompt to the left of code cells, for example `[1]:` indicates the position in the overall cell run order of the last run of the cell. While this allows establishing if a cell last ran before or after another cell, if some cells are run multiple times then their previous run counter values will be overwritten so we lose information about the run order. +# +# In general if you are using notebooks in your own research you should try to make sure the notebook run and produce the desired outputs when the cells are executed sequentially from top to bottom. The `Kernel` menu provides an option to restart the current kernel and run all cells in order from top to bottom. If you just want to run a subset of the cells there is also an option to restart and run all cells from the top to the currently selected cell. The commands are useful for checking that a notebook will produce the expected output and run without errors when the cells are executed in order. diff --git a/ch01python/04functions.ipynb.py b/ch01python/016functions.ipynb.py similarity index 100% rename from ch01python/04functions.ipynb.py rename to ch01python/016functions.ipynb.py diff --git a/ch01python/016using_functions.ipynb.py b/ch01python/016using_functions.ipynb.py deleted file mode 100644 index dd176f96..00000000 --- a/ch01python/016using_functions.ipynb.py +++ /dev/null @@ -1,241 +0,0 @@ -# --- -# jupyter: -# jekyll: -# display_name: Using Functions -# jupytext: -# notebook_metadata_filter: -kernelspec,jupytext,jekyll -# text_representation: -# extension: .py -# format_name: percent -# format_version: '1.3' -# jupytext_version: 1.15.2 -# --- - -# %% [markdown] -# ## Using Functions - -# %% [markdown] -# ### Calling functions - -# %% [markdown] -# We often want to do things to our objects that are more complicated than just assigning them to variables. - -# %% -len("pneumonoultramicroscopicsilicovolcanoconiosis") - -# %% [markdown] -# Here we have "called a function". - -# %% [markdown] -# The function `len` takes one input, and has one output. The output is the length of whatever the input was. - -# %% [markdown] -# Programmers also call function inputs "parameters" or, confusingly, "arguments". - -# %% [markdown] -# Here's another example: - -# %% -sorted("Python") - -# %% [markdown] -# Which gives us back a *list* of the letters in Python, sorted alphabetically (more specifically, according to their [Unicode order](https://www.ssec.wisc.edu/~tomw/java/unicode.html#x0000)). - -# %% [markdown] -# The input goes in brackets after the function name, and the output emerges wherever the function is used. - -# %% [markdown] -# So we can put a function call anywhere we could put a "literal" object or a variable. - -# %% -len('Jim') * 8 - -# %% -x = len('Mike') -y = len('Bob') -z = x + y - -# %% -print(z) - -# %% [markdown] -# ### Using methods - -# %% [markdown] -# Objects come associated with a bunch of functions designed for working on objects of that type. We access these with a dot, just as we do for data attributes: - -# %% -"shout".upper() - -# %% [markdown] -# These are called methods. If you try to use a method defined for a different type, you get an error: - -# %% -x = 5 - -# %% -type(x) - -# %% -x.upper() - -# %% [markdown] -# If you try to use a method that doesn't exist, you get an error: - -# %% -x.wrong - -# %% [markdown] -# Methods and properties are both kinds of **attribute**, so both are accessed with the dot operator. - -# %% [markdown] -# Objects can have both properties and methods: - -# %% -z = 1 + 5j - -# %% -z.real - -# %% -z.conjugate() - -# %% -z.conjugate - -# %% [markdown] -# ### Functions are just a type of object! - -# %% [markdown] -# Now for something that will take a while to understand: don't worry if you don't get this yet, we'll -# look again at this in much more depth later in the course. -# -# If we forget the (), we realise that a *method is just a property which is a function*! - -# %% -z.conjugate - -# %% -type(z.conjugate) - -# %% -somefunc = z.conjugate - -# %% -somefunc() - -# %% [markdown] -# Functions are just a kind of variable, and we can assign new labels to them: - -# %% -sorted([1, 5, 3, 4]) - -# %% -magic = sorted - -# %% -type(magic) - -# %% -magic(["Technology", "Advanced"]) - -# %% [markdown] -# ### Getting help on functions and methods - -# %% [markdown] -# The 'help' function, when applied to a function, gives help on it! - -# %% -help(sorted) - -# %% [markdown] -# The 'dir' function, when applied to an object, lists all its attributes (properties and methods): - -# %% -dir("Hexxo") - -# %% [markdown] -# Most of these are confusing methods beginning and ending with __, part of the internals of python. - -# %% [markdown] -# Again, just as with error messages, we have to learn to read past the bits that are confusing, to the bit we want: - -# %% -"Hexxo".replace("x", "l") - -# %% -help("FIsh".replace) - -# %% [markdown] -# ### Operators - -# %% [markdown] -# Now that we know that functions are a way of taking a number of inputs and producing an output, we should look again at -# what happens when we write: - -# %% -x = 2 + 3 - -# %% -print(x) - -# %% [markdown] -# This is just a pretty way of calling an "add" function. Things would be more symmetrical if add were actually written -# -# x = +(2, 3) -# -# Where '+' is just the name of the name of the adding function. - -# %% [markdown] -# In python, these functions **do** exist, but they're actually **methods** of the first input: they're the mysterious `__` functions we saw earlier (Two underscores.) - -# %% -x.__add__(7) - -# %% [markdown] -# We call these symbols, `+`, `-` etc, "operators". - -# %% [markdown] -# The meaning of an operator varies for different types: - -# %% -"Hello" + "Goodbye" - -# %% -[2, 3, 4] + [5, 6] - -# %% [markdown] -# Sometimes we get an error when a type doesn't have an operator: - -# %% -7 - 2 - -# %% -[2, 3, 4] - [5, 6] - -# %% [markdown] -# The word "operand" means "thing that an operator operates on"! - -# %% [markdown] -# Or when two types can't work together with an operator: - -# %% -[2, 3, 4] + 5 - -# %% [markdown] -# To do this, put: - -# %% -[2, 3, 4] + [5] - -# %% [markdown] -# Just as in Mathematics, operators have a built-in precedence, with brackets used to force an order of operations: - -# %% -print(2 + 3 * 4) - -# %% -print((2 + 3) * 4) - -# %% [markdown] -# *Supplementary material*: [Python operator precedence](https://docs.python.org/3/reference/expressions.html#operator-precedence). diff --git a/ch01python/017using_functions.ipynb.py b/ch01python/017using_functions.ipynb.py new file mode 100644 index 00000000..2755d823 --- /dev/null +++ b/ch01python/017using_functions.ipynb.py @@ -0,0 +1,310 @@ +# --- +# jupyter: +# jekyll: +# display_name: Using Functions +# jupytext: +# notebook_metadata_filter: -kernelspec,jupytext,jekyll +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.15.2 +# --- + +# %% [markdown] +# ## Using functions +# +# Functions in Python (and other programming languages) are modular units of code which specify a set +# of instructions to perform when the function is _called_ in code. Functions may take one or more +# input values as _arguments_ and may _return_ an output value. Functions can also have _side-effects_ +# such as printing information to a display. +# +# ### Calling functions in Python +# +# Python provides a range of useful [built-in functions](https://docs.python.org/3/library/functions.html) +# for performing common tasks. For example the `len` function returns the length of a sequence object +# (such as a string) passed as input argument. To _call_ a function in Python we write the name of the +# function followed by a pair of parentheses `()`, with any arguments to the function being put inside +# the parentheses: + +# %% +len("pneumonoultramicroscopicsilicovolcanoconiosis") + +# %% [markdown] +# If a function can accept more than one argument they are separated by commas. For example the +# built-in `max` function when passed a pair of numeric arguments returns the larger value from +# the pair: + +# %% +max(1, 5) + +# %% [markdown] +# Another built-in function which we have already seen several times is `print`. Unlike `len` and +# `max`, `print` does not have an explicit return value as its purpose is to print a string representation +# of the argument(s) to a text display such as the output area of a notebook code cell. For functions +# like `print` which do not have an explicit return value, the special null value `None` we encountered +# previously will be used as the value of a call to the function if used in an expression or assigned to +# a variable: + +# %% +return_value = print("Hello") +print(return_value) + +# %% [markdown] +# Function calls can be placed anywhere we can use a literal value or a variable name, for example + +# %% +name = "Jim" +len(name) * 8 + +# %% +total_length = len("Mike") + len("Bob") +print(total_length) + +# %% [markdown] +# ### Using methods +# +# Objects come associated with a bunch of functions designed for working on objects of that type. We access these with a +# dot, just as we do for data attributes: + +# %% +"shout".upper() + +# %% [markdown] +# These are called methods. If you try to use a method defined for a different type, you get an error: + +# %% +x = 5 + +# %% +type(x) + +# %% +x.upper() + +# %% [markdown] +# If you try to use a method that doesn't exist, you get an error: + +# %% +x.wrong + +# %% [markdown] +# Methods and properties are both kinds of **attribute**, so both are accessed with the dot operator. +# +# Objects can have both properties and methods: + +# %% +z = 1 + 5j + +# %% +z.real + +# %% +z.conjugate() + +# %% +z.conjugate + +# %% [markdown] +# ### Getting help on functions +# +# The built-in `help` function, when passed a function, prints documentation for the function, which typically +# includes a description of the what arguments can be passed and what the function returns. For example + +# %% +help(max) + +# %% [markdown] +# In Jupyter notebooks an alternative way of displaying the documentation for a function is to write the function +# names followed by a question mark character `?` + +# %% +# max? + +# %% [markdown] +# The 'dir' function, when applied to an object, lists all its attributes (properties and methods): + +# %% +dir("Hexxo") + +# %% [markdown] +# Most of these are confusing methods beginning and ending with __, part of the internals of python. +# +# Again, just as with error messages, we have to learn to read past the bits that are confusing, to the bit we want: + +# %% +"Hexxo".replace("x", "l") + +# %% +help("FIsh".replace) + +# %% [markdown] +# ### Positional and keyword arguments and default values +# +# There are two ways of passing arguments to function in Python. In the examples so far the function arguments have +# only been identified by the position they appear in the argument list of the function. An alternative is to use +# named or _keyword_ arguments, by prefixing some or all of the arguments with the argument name followed by an equals +# sign. For example, there is a built-in function `round` which rounds decimal numbers to a specified precision. Using +# the `help` function we can read the documentation for `round` to check what arguments it accepts +# + +# %% +help(round) + +# %% [markdown] +# We see that `round` accepts two arguments, a `number` argument which specifies the number to round and a `ndigits` +# argument which specifies the number of decimal digits to round to. One way to call `round` is by passing positional +# arguments in the order specified in function signature `round(number, ndigits=None)` with the first argument +# corresponding to `number` and the second `ndigits`. For example + +# %% +pi = 3.14159265359 +round(pi, 2) + +# %% [markdown] +# To be more expicit about which parameters of the function the arguments we are passing correspond to, we can instead +# however pass the arguments by name (as _keyword arguments_) + +# %% +round(number=pi, ndigits=2) + +# %% [markdown] +# We can in-fact mix and match position and keyword arguments, _providing that all keyword arguments come after any positional arguments_ + +# %% +round(pi, ndigits=2) + +# %% +round(number=pi, 2) + +# %% [markdown] +# Unlike positional arguments the ordering of keyword arguments does not matter so for example the following is also valid +# and equivalent to the calls above + +# %% +round(ndigits=2, number=pi) + +# %% [markdown] +# In the documentation for `round` we see that the second argument in the function signature is written `ndigits=None`. +# This indicates that `ndigits` is an _optional_ argument which takes the default value `None` if it is not specified. +# The documentation further states that +# +# > The return value is an integer if `ndigits` is omitted or `None` +# +# which indicates that when `ndigits` is left as its default value (that is the argument is omitted or explicitly set +# to `None`) the `round` function returns the value of `number` rounded to the nearest integer. The following are all +# equivalent therefore + +# %% +round(pi) + +# %% +round(number=pi) + +# %% +round(number=pi, ndigits=None) + +# %% [markdown] +# ### Functions are just a type of object! +# +# Now for something that will take a while to understand: don't worry if you don't get this yet, we'll +# look again at this in much more depth later in the course. +# +# If we forget the (), we realise that a *method is just a property which is a function*! + +# %% +z.conjugate + +# %% +type(z.conjugate) + +# %% +somefunc = z.conjugate + +# %% +somefunc() + +# %% [markdown] +# Functions are just a kind of variable, and we can assign new labels to them: + +# %% +sorted([1, 5, 3, 4]) + +# %% +magic = sorted + +# %% +type(magic) + +# %% +magic(["Technology", "Advanced"]) + +# %% [markdown] +# ### Operators +# +# Now that we know that functions are a way of taking a number of inputs and producing an output, we should look again at +# what happens when we write: + +# %% +x = 2 + 3 + +# %% +print(x) + +# %% [markdown] +# This is just a pretty way of calling an "add" function. Things would be more symmetrical if add were actually written +# +# x = +(2, 3) +# +# Where '+' is just the name of the name of the adding function. +# +# In python, these functions **do** exist, but they're actually **methods** of the first input: they're the mysterious `__` functions we saw earlier (Two underscores.) + +# %% +x.__add__(7) + +# %% [markdown] +# We call these symbols, `+`, `-` etc, "operators". +# +# The meaning of an operator varies for different types: + +# %% +"Hello" + "Goodbye" + +# %% +[2, 3, 4] + [5, 6] + +# %% [markdown] +# Sometimes we get an error when a type doesn't have an operator: + +# %% +7 - 2 + +# %% +[2, 3, 4] - [5, 6] + +# %% [markdown] +# The word "operand" means "thing that an operator operates on"! +# +# Or when two types can't work together with an operator: + +# %% +[2, 3, 4] + 5 + +# %% [markdown] +# To do this, put: + +# %% +[2, 3, 4] + [5] + +# %% [markdown] +# Just as in Mathematics, operators have a built-in precedence, with brackets used to force an order of operations: + +# %% +print(2 + 3 * 4) + +# %% +print((2 + 3) * 4) + +# %% [markdown] +# *Supplementary material*: [Python operator precedence](https://docs.python.org/3/reference/expressions.html#operator-precedence).