{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "([Home](https://mannymoo.github.io/IntroductionToPython/))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# SUPAPYT - Introduction to" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "python logo" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Welcome to this introductory course in the Python programming language!\n", "\n", "This course will take you through the basics of Python and hopefully demonstrate it to be a powerful, intuitive, and generally useful means of programming.\n", "\n", "It's intended to be suitable for novice programmers while still being useful for those with previous experience of other languages, or even of python itself." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This course is presented in the form of a [Jupyter notebook](https://jupyter.org/), which gives full access to the interactive python interpreter. We'll actually be running real python code as we go through the lectures. \n", "\n", "All the material is available [online](https://mannymoo.github.io/IntroductionToPython/), in static form. You can also download the [source](https://github.com/MannyMoo/IntroductionToPython). If you then follow these [Jupyter installation instructions](https://mannymoo.github.io/IntroductionToPython/SUPAPYT-Installation-Instructions.html) you can use the interactive form of the notes. I encourage you to do so, and, if you have a laptop, follow the material interactively during the lectures, play about, and ask questions as we go. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Schedule" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There will be 4 lectures and 2 hands-on sessions:\n", "\n", "- Lecture 1: 24/01/22, 3pm\n", "- Lecture 2: 26/01/22, 3pm\n", "- Hands-on 1: 28/01/22, 11am - 1 pm\n", "- Lecture 3: 31/01/22, 3pm \n", "- Lecture 4: 02/02/22, 3pm\n", "- Hands-on 2: 04/02/22, 11am - 1pm\n", "\n", "The lectures are webcast and recordings are accessible through [MySUPA](http://my.supa.ac.uk/course/view.php?id=129). " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The lab sessions allow you to work through [examples](https://mannymoo.github.io/IntroductionToPython/SUPAPYT-LabProblems.html), experiment with Python, and discuss any queries with me and fellow students. \n", "\n", "You can also get help setting up python and Jupyter on your laptop/desktop." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Assessment" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- There will be an assignment set by the time of the second lab session, which is all that's required to get credit.\n", "- Details TBC. The deadline will be ~2 weeks after the second lab. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Contents" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " - [Introduction](#Introduction)\n", " - [What is Python?](#What-is-Python?)\n", " - [What does Object Oriented mean?](#What-does-Object-Oriented-mean?)\n", " - [What are the benefits of python?](#What-are-the-benefits-of-python?)\n", " - [Which python?](#Which-python?)\n", " - [The Python Interpreter](#The-Python-Interpreter)\n", " - [Simple Calculations](#Simple-Calculations)\n", " - [Assignment to variables](#Assignment-to-variables)\n", " - [Basic Data Types](#Basic-Data-Types)\n", " - [Numbers](#Numbers)\n", " - [Booleans](#Booleans)\n", " - [Strings](#Strings)\n", " - [None](#None)\n", " - [Casting between types](#Casting-between-types)\n", " - [Flow Control](#Flow-Control)\n", " - [Functions](#Functions)\n", " - [Sequences](#Sequences)\n", " - [Lists](#Lists)\n", " - [Tuples](#Tuples)\n", " - [Dictionaries](#Dictionaries)\n", " - [Sets](#Sets)\n", " - [Looping](#Looping)\n", " - [`while` Statements](#`while`-Statements)\n", " - [`for` Loops](#`for`-Loops)\n", " - [Introspection](#Introspection)\n", " - [`dir`](#`dir`)\n", " - [`help`](#`help`)\n", " - [Type checking](#Type-checking)\n", " - [Classes](#Classes)\n", " - [The Empty Class](#The-Empty-Class)\n", " - [A Basic Class](#A-Basic-Class)\n", " - [Inheritance](#Inheritance)\n", " - [Using `__slots__` to specify attribute names](#Using-`__slots__`-to-specify-attribute-names)\n", " - [Class attributes](#Class-attributes)\n", " - [Modules](#Modules)\n", " - [Importing Modules](#Importing-Modules)\n", " - [Writing Your Own Modules](#Writing-Your-Own-Modules)\n", " - [Importing or Executing as Main](#Importing-or-Executing-as-Main)\n", " - [Packages](#Packages)\n", " - [Files, Input & Output](#Files,-Input-&-Output)\n", " - [Reading & Writing Files](#Reading-&-Writing-Files)\n", " - [The `with` Statement](#The-`with`-Statement)\n", " - [File Parsing](#File-Parsing)\n", " - [Data Persistency](#Data-Persistency)\n", " - [Pickling](#Pickling)\n", " - [Commandline Arguments](#Commandline-Arguments)\n", " - [Exceptions](#Exceptions)\n", " - [More Useful Builtin Functionality](#More-Useful-Builtin-Functionality)\n", " - [Lambda Methods](#Lambda-Methods)\n", " - [Sequence Manipulation](#Sequence-Manipulation)\n", " - [More Ways to Iterate.](#More-Ways-to-Iterate.)\n", " - [OS Interface](#OS-Interface)\n", " - [A Few Tips](#A-Few-Tips)\n", " - [Further Reading](#Further-Reading)\n", " - [The End](#The-End)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Introduction" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### What is Python?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Python is an [Object Oriented](https://en.wikipedia.org/wiki/Object-oriented_programming) (OO) scripting language. \n", " - Everything in Python is an \"object\"!\n", "- Invented by Guido van Rossum.\n", " - First release in 1991, still under constant development.\n", " - Now managed by the not-for-profit [Python Software Foundation](http://www.python.org/psf/).\n", " - Named after [Monty Python](https://en.wikipedia.org/wiki/Monty_Python).\n", "- The official homepage is [http://www.python.org](http://www.python.org/).\n", " - It has a detailed [tutorial](https://docs.python.org/3/tutorial/index.html), which this course roughly follows, in condensed form." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### What does Object Oriented mean?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- [This](https://medium.freecodecamp.org/object-oriented-programming-concepts-21bb035f7260) gives a decent explanation of the main concepts.\n", "- Mainly a means of organising and structuring code.\n", "- Data and functionality are contained in \"objects\", which are instances of \"classes\".\n", "- A class can be defined to contain whatever data you need (\"attributes\") and perform operations on those data (using \"member functions\").\n", "- You can write classes for each task you need to perform, or use classes written by others.\n", "- Objects normally interact via their functions, and don't directly access each other's attributes." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Each instance of a class then has the same data attributes, but can take different values for them.\n", "- An example from particle physics:\n", " - A particle passes through the detector and leaves a series of energy deposits (\"hits\") in various detector elements.\n", " - The hits are read out from the detector and stored in instances of the `Hit` class.\n", " - Each `Hit` has a 3D position which is saved in `x`, `y`, & `z` attributes.\n", " - A `Track` object has as its attributes a set of `Hit` instances as well as an origin point (`x`, `y`, `z`) and gradients `tx` & `ty`. These are calculate from a straight line fitted to the set of hits.\n", " - The `Track` class has `add_hit` and `remove_hit` member methods to add or remove `Hit` instances from its set, and an `update_direction` method to recalculate the track origin & gradient from the set of `Hit` instances.\n", " - You can have different types of `Hit` and `Track`, depending on which subdetector they're from, but they all have the same interface (through their member functions)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- The benefits of object orientation are:\n", " - Classes provide an interface for manipulating data (through member functions) without the user needing to know the implementation specifics.\n", " - A change to the class definition is propagated to every instance of that class.\n", " - Eg, attributes for the uncertainties on the position could be added to the `Hit` class. \n", " - Similary, the `update_direction` method in `Track` could be modified to take into account these uncertainties in finding the best-fit trajectory.\n", " - Classes can be as simple or complicated as necessary.\n", " - This allows you to break down a given problem into its most basic components, write a class for each, then build greater complexity on top using instances of these classes.\n", " - You can structure your code so it's maintainable, flexible and extensible (and consequently less prone to bugs)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### What are the benefits of python?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Syntax is simple and easy to learn.\n", "- It's interpreted - no compilation step needed.\n", "- Runs identically on almost any machine!\n", "- Interactive - more intuitive & versatile than shell scripts (eg, bash), ideal for interactive data analysis.\n", "- Introspective - python objects can tell you a lot about themselves!\n", "- Dynamically typed (no explicit type declarations needed). \n", "- Extensive standard library provides lots of functionality.\n", "- Easily extensible.\n", "- It's a \"high-level\" language - complex operations often executed by a few simple commands.\n", "- Normally no need to worry about memory management.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Which python?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Two versions of python exist: v2 and v3.\n", "- Python v2 is still used in some places but is [no longer supported](https://pythonclock.org/), so we'll use v3.\n", "- v3 has various improvements and additional features, and is still being developed.\n", "- The latest release is v3.10.2 from 2021-10-04.\n", "- It's not fully backwards compatible with v2, but it's quite easy to convert v2 code to v3 using [2to3](https://docs.python.org/3.1/library/2to3.html).\n", "- See [here](https://wiki.python.org/moin/Python2orPython3) for more info on the differences between v2 and v3." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The Python Interpreter" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In a jupyter notebook, an interpreter cell looks like this:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Any valid python statement can then be entered for immediate evaluation.\n", "- The canonical example is a programme that simply outputs the statement \"Hello World!\".\n", "- In python this is just:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(\"Hello World!\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Here's your first python function: \"print\", which converts objects to text and outputs them to the console.\n", "\n", "- You can pass several arguments to `print` by separating them with commas within the brackets, then they're printed on the same line with spaces between them:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(1, 2, 3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- At the interactive prompt you can actually omit the \"print\", which is more like asking the interpreter \"what is this object?\", rather than necessarily outputing it in human readable format:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"Hello World!\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Note the quotes in this case.\n", "- More on what exactly the difference is later.\n", "- This means the interactive prompt is also a handy calculator:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "10+3" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "2**10" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# A hash symbol precedes a comment, which is ignored\n", "# by the interpreter, so this does nothing.\n", "# Comments are just used to annotate code to explain\n", "# the code to future readers" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Jupyter notebooks are good for interactive work and main code, but for more involved projects you'll likely want to put your code into scripts (plain text files), possibly divided into modules and packages (more on those later), and run them via the command line.\n", "- For more information on running the python interpreter from the command line (outside of a jupyter notebook) and writing python scripts, see \"[Getting started](Getting-started.html)\"." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Simple Calculations" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "All the standard operators are available:\n", "\n", " + add\n", "\n", " - subtract\n", "\n", " * multiply\n", "\n", " / divide\n", " \n", " // divide and round down\n", "\n", " % remainder\n", "\n", " ** exponentiate\n", "\n", "As said, the interactive prompt is handy as a simple calculator:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Addition\n", "2+2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Division\n", "3/2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Floor division\n", "3//2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "-3/2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "-3//2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The usual operator precedence applies (BODMAS)\n", "1 + 2 * 3" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# As do parenthesis rules\n", "(1 + 2) * 3" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Remainder\n", "15 % 10" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Remainder also works on numbers with decimal points,\n", "# but always be careful of rounding errors!\n", "print(5.4 % 2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Exponentiate\n", "(6-2)**2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# At the interactive prompt the last value that was \n", "# output is assigned to the variable \"_\", which can \n", "# be useful for repeated calculations. \n", "_" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "_ + 7.5" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "_ * 1.204" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Note that the variable \"`_`\" only exists at the interactive prompt, and not in scripts." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Assignment to variables" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This allows you to create named variables with given values rather than just using fixed values.\n", "\n", "You then use the name of the variable in place of the value in calculations and can assign new variables from the values of those calculations." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# As simple as this, no type declaration is needed. Just name the\n", "# variable and give it a value with =.\n", "\n", "width = 5\n", "length = 2\n", "area = width * length\n", "area" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is particularly relevant when writing functions for repeated operations where the input values are provided by the user.\n", "\n", "Functions are defined with the `def` keyword and the return value with the `return` keyword:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Put the calculation above into a function:\n", "def get_area(width, length):\n", " return width * length" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then call the function similarly to `print` by passing arguments in brackets and assign a variable from its return value:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "area = get_area(5, 2)\n", "print(area)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You can pass named variables as arguments to functions\n", "w = 6.5\n", "l = 12.6\n", "area = get_area(w, l)\n", "print(area)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "More on functions [later](#Functions)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You can assign variables from other variables.\n", "\n", "area2 = area\n", "area2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The same value can be assigned to several variables simultaneously \n", "# like this:\n", "\n", "x = y = z = 0.\n", "x, y, z" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Or to give different values separate them with commas\n", "\n", "x, y, z = 1, 2, 3\n", "x, y, z" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You can delete a variable with the del keyword\n", "\n", "del x" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- There are several reserved keywords in python, which can't be used as variable names.\n", "- You can get the full list of keywords like so:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import keyword\n", "\n", "print(keyword.kwlist)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- You see that `import` is another keyword.\n", "- More on importing [later](#Importing-Modules)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# If you try to access a variable that doesn't exist,\n", "# you get an exception.\n", "\n", "x" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# If you try to use a keyword as a variable name,\n", "# you also get an exception\n", "\n", "del = 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Exceptions are raised when python fails to evaluate an expression and can't continue.\n", "- There are various types of Exception for different problems. \n", "- Normally they give some information on what the problem is.\n", "- More on exceptions [later](#Exceptions)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "harry potter python" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Basic Data Types" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There're a few basic types of object in python (and most programming languages) on top of which arbitrarily complex algorithms can be built." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Numbers" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# A number without a decimal point is an integer,\n", "# (a whole number or \"int\")\n", "\n", "x = 123" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You can check the type of any variable using the \"type\" function.\n", "# As seen with \"print\", a function is called (excuted) using \n", "# brackets which contain the arguments that're passed to the function.\n", "\n", "print(x)\n", "print(type(x))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Python can interpret integers in any basis. There're a few standard ones with special syntax:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# A 0o prefix causes a number to be interpreted as an octal number,\n", "# though it's converted to base 10 and stored as a regular int.\n", "\n", "x = 0o10\n", "print(x)\n", "print(type(x))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Similarly for hexidecimal numbers prefix with 0x\n", "\n", "x = 0xF\n", "print(x)\n", "print(type(x))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Or for binary, use 0b.\n", "\n", "x = 0b10\n", "print(x)\n", "print(type(x))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Numbers with a fractional part are known as \"floating point\" numbers, or floats." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Anything with a decimal point or in scientific notation is a float.\n", "# In python all floats are \"double\" precision (normally 16 d.p.), \n", "# \"single\" precision float (as in some other languages) doesn't exist\n", "\n", "x = 123.\n", "print(x, type(x))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Scientific notation can use \"e\" or \"E\" and makes a float\n", "\n", "x = 1e6\n", "print(x, type(x))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Python also has a builtin complex number class." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# \"j\" or \"J\" is valid to give the imaginary part\n", "\n", "x = 1 + 2j\n", "print(x, type(x))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You can also use the complex class constructor,\n", "# which is called like a function with the real\n", "# and imaginary parts as arguments.\n", "\n", "x = complex(3, 4)\n", "print(x, type(x))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Real and imaginary parts are stored as floats.\n", "# Access member \"attributes\" of an object with '.'\n", "\n", "print(x.real, type(x.real))\n", "print(x.imag, type(x.imag))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Also use '.' for member functions.\n", "# You need brackets following a function in order\n", "# to call (execute) it, even if there are no \n", "# arguments.\n", "\n", "y = x.conjugate()\n", "print(y, type(y))\n", "print(x + y)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Here're the first examples of two key concepts in object oriented programming:\n", " - Member attributes (`real` and `imag`), which contain the data for the object.\n", " - Member functions (`conjugate`), which can access, modify or perform other operations on the attributes.\n", "- The `complex` class describes the structure for complex numbers: every instance has a value for the `real` and `imag` attributes, can call methods like `conjugate`, and perform basic numerical operations." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Booleans" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- These are simply `True` or `False`.\n", "- They're used for [flow control](#Flow-Control) (which we'll discuss a little later), comparing objects, as a flag, and various other applications." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Boolean types start with capital letters\n", "\n", "x = True\n", "y = False\n", "print(x, type(x))\n", "print(y, type(y))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Particularly relevant for comparisons\n", "# Check if variables have the same value with ==\n", "\n", "a = 1\n", "b = 0\n", "c = 1\n", "print(a==c)\n", "print(a==b)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You can also assign from comparisons\n", "\n", "x = (a==c)\n", "print(x, type(x))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Other comparison operations include:\n", "- Greater than: `>`\n", "- Greater than or equal: `>=`\n", "- Less than: `<`\n", "- Less than or equal: `<=`\n", "- Not equal: `!=`\n", "\n", "More info [here](https://docs.python.org/3/reference/expressions.html#comparisons)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Booleans can be combined using 'and' or 'or'\n", "\n", "x = 123.\n", "print(x > 100 and x < 200)\n", "print(x < 100 or x > 200)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# They can be inverted with 'not'\n", "print(not (x > 100 and x < 200))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Any number of `and` or `or` statements can be strung together.\n", "- For more complicated expressions, keep in mind that `and` operations have higher precedence than `or` operations (`and` operations are evaluated first).\n", "- For a (much) more detailed description, see [here](https://thomas-cokelaer.info/tutorials/python/boolean.html)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Strings" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- These are sequences of characters.\n", "- Python has lots of useful functionality for manipulating strings.\n", "- Whenever you `print` something, it's converted to a string (if it isn't one already)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# We already saw a string in 'Hello world!'\n", "# Strings are enclosed by quote marks.\n", "# They can be defined using single or double quotes\n", "# (open and close quotes must be the same)\n", "\n", "name = 'Sir Gallahad'\n", "print(name)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "name = \"Brave Sir Robin\"\n", "print(name)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# A string can contain quotation marks.\n", "# If they're the same as the enclosing quotation marks they need to be \n", "# \"escaped\" with a backslash, if they're different they don't need to \n", "# be escaped.\n", "\n", "\"He's running away\"" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "'He\\'s chickening out'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You can continue a string onto the next line with a backslash\n", "\n", "lyric = 'Brave Sir Robin \\\n", "ran away'\n", "lyric" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Multi-line strings use triple quotes - can also use '''\n", "\n", "lyric = \"\"\"Boldly ran\n", " away\n", " away ...\"\"\"\n", "\n", "# \\n is the newline character\n", "lyric" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Spaces at the start of lines are kept\n", "\n", "print(lyric)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# If not assigned to a variable (and not the only thing in a script)\n", "# a multi-line string can be used as a multi-line comment - python \n", "# ignores it and not allocate any memory to it.\n", "\n", "'''This is a \n", "multi-line comment'''\n", "\n", "# In a Jupyter notebook, there has to be something else in the cell\n", "# for the string to be interpreted as a comment.\n", "lyric = ''" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You can concatenate strings with +\n", "\n", "'Spam ' + 'and eggs'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You can optionally omit the + as python implicitly concatenates \n", "# two strings declared side-by-side (the case above is explicit\n", "# concatenation).\n", "\n", "'Spam ' 'and eggs'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# This provides another way of writing a string across \n", "# several lines, which can make code easier to read.\n", "\n", "menu = ('Spam, spam, spam, spam, spam, spam, spam,'\n", " ' and eggs')\n", "print(menu)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You can also multiply strings by integers\n", "\n", "'Spam ' * 3" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Strings have lots of member methods, eg, make all\n", "# letters uppercase.\n", "\n", "menu = 'Spam and eggs'\n", "menu.upper()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Note that these methods return new strings, and don't \n", "# change the original.\n", "# The replace method replaces a substring for another.\n", "\n", "menu2 = menu.replace('eggs', 'spam')\n", "menu2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The original string is unchanged\n", "menu" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You can access single characters in a string using indexing\n", "# with square brackets.\n", "# Access the 4th character from the start (indices start at 0)\n", "\n", "menu[3]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Python also allows negative indices, which count from the end \n", "# of the string (or other indexable object).\n", "# eg, access the 4th character from the end:\n", "\n", "menu[-4]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You can also access slices of a string like so:\n", "\n", "menu[1:3]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Omitting the first index is equivalent to it being 0\n", "\n", "menu[:3]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Omitting the second index is the same as it being equal to \n", "# the number of characters in the string.\n", "\n", "menu[3:]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# So this returns the original string:\n", "\n", "menu[:3] + menu[3:]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Negative indices are also allowed when slicing.\n", "# They count backwards from the end.\n", "# This gives the last 3 characters:\n", "\n", "menu[-3:]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# This gives everything but the last 3 characters.\n", "\n", "menu[:-3]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# If the index range is negative, or beyond the length of \n", "# the string, you get an empty string.\n", "\n", "menu[10:2]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "menu[23:45]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You can use the 'in' keyword to check if a string\n", "# contains a character or substring.\n", "\n", "print('m' in menu)\n", "print('Spam' in menu)\n", "print('spam' in menu)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Strings have various formatting methods that're very useful for making output more easily readable.\n", "- By calling the `format` member method of a string, expressions in braces `{}` are replaced with the arguments of `format`.\n", "- Flags can be used inside the braces to define how to format the arguments." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# No flags: the braces are replaced with string versions\n", "# of the format arguments.\n", "\n", "print('Result: {} +/- {}'.format(1.435, 0.035))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# If you put indices in the curly brackets they're replaced by the \n", "# argument of the same index. Indices count from zero.\n", "# This is particulary useful if you use an argument more than once.\n", "\n", "print('{0} and {1} and {0}'.format('spam', 'eggs'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- A colon inside the braces is followed by the formatting flags.\n", "- The first number is the minimum width of the resulting string.\n", "- The second number is the precision used to round the number.\n", "- The 'f' means floating point representation should be used." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print('Result: {:8.2f} +/- {:4.2f}'.format(1.435, 0.035))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Formatting flags can be combined with indices in front of the\n", "# colon.\n", "\n", "print('Result: {1:8.2f} +/- {0:4.2f}'.format(0.035, 1.435))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Alternatively, you can use 'keywords' to label the arguments\n", "# rather than indices.\n", "\n", "print('Result: {value:8.2f} +/- {error:4.2f}'.format(value = 1.435, \n", " error = 0.035))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You don't need to format a string at initialisation. The format method \n", "# actually returns a new string, which you can assign to a new variable.\n", "\n", "genericResult = 'Result: {:8.2f} +/- {:4.2f}'\n", "result1 = genericResult.format(3.2432, 0.2234)\n", "result2 = genericResult.format(2.8982, 0.0879)\n", "print(genericResult)\n", "print(result1)\n", "print(result2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- As said, this is particularly useful for making output more readable - you can, eg, round numbers when they're printed, and arrange output in columns of constant width.\n", "- For a full description of the formatting syntax see [here](http://docs.python.org/3/library/string.html#formatstrings), and [here](http://docs.python.org/3/library/string.html#formatspec) for a description of the various flags available.\n", "- Various other useful formatting methods exist for strings, eg: `rjust`, `ljust`, `center`, `rstrip`, `lstrip`, `zfill` ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### None" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`None` is the null type, and is useful for comparisons, as a default value for something that may be assigned later, a return value in the event of a failure, or various other applications." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "result = None\n", "print(result)\n", "print(result==None)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "result = 10.\n", "result==None" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Casting between types" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Since python allows you to change the type of a variable, variables aren't implicitly cast in assignment." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "a = 1\n", "print(a, type(a))\n", "a = 'spam'\n", "print(a, type(a))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- To switch types you need to call the constructor of your desired type." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Everything can be converted to a string in some way, using str(), this \n", "# is done when an object is printed.\n", "\n", "str(123), str(True), str(4.), str(None)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Many things can also be converted to ints.\n", "# When converting floats to ints, the fractional part is just\n", "# discarded (it doesn't round down)\n", "\n", "print(int('1'))\n", "print(int(4.6), int(-4.6))\n", "print(int(True), int(False))\n", "print(int(1e12))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# When casting from string to int you can define the base to \n", "# use by passing a second argument to int()\n", "\n", "print(int('11'), int('11', 8), int('11', 2))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Hexadecimal notation is also allowed.\n", "\n", "print(int('F', 16))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Some strings can't be interpreted as ints though, and then int() raises \n", "# an exception.\n", "\n", "int('spam')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Casting to float works similarly.\n", "\n", "print(float(123))\n", "print(float('34.2'))\n", "print(float(True), float(False))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# And fails under similar conditions.\n", "\n", "float('eggs')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The complex class can also cast from strings.\n", "\n", "complex('1+2j')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The default bool is False.\n", "# Casting None to a bool gives False.\n", "# Any non-zero number is cast to a bool as True.\n", "\n", "print(bool())\n", "print(bool(None))\n", "print(bool(0), bool(1))\n", "print(bool(23.2), bool(-6), bool(3.3+5.4j), bool(0 + 0j))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Any non-empty string (or other iterable object) is also cast to True, \n", "# regradless of content.\n", "\n", "print(bool(''))\n", "print(bool('spam'), bool('True'), bool('False'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Flow Control" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- An `if` statement allows you to evaluate a boolean expression and perform certain actions accordingly.\n", "- The statement is followed by a colon `:` and then an indented block of code which is evaluated if the boolean evaluates to `True`.\n", "- This can be followed by an arbitrary number of `elif` statements, and finally an optional `else` statement, each with their own boolean expression and indented block of code.\n", "- Each boolean expression is evaluated in turn until one is found to be `True`. \n", "- Then the associated code block is evaluated and the sequence terminates. \n", "- If all booleans evaluate to `False` then the optional `else` block is evaluated." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "yesNo = True\n", "if yesNo:\n", " print('Yes')\n", "else:\n", " print('No')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x = 10 \n", "if x < 0:\n", " print('Negative')\n", "elif x == 0:\n", " print('Zero')\n", "else:\n", " print('Positive')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- As seen earlier, boolean statements can be combined using `and` and `or`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x = 3456.\n", "if x > 2000. and x < 4000.:\n", " print('x is in the signal region')\n", "elif x < 1000. or x > 5000.:\n", " print('x is in the background region')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Indentation is python's way of defining code blocks (groups of statements). \n", "- Note that spaces aren't the same as tabs (though most text editors convert tabs to spaces) - if you use a mix of spaces and tabs in a script then python will raise an exception.\n", "- Groups of statements with the same amount of indentation make a code block.\n", "- A colon `:` is (almost) always followed by an increase in indentation.\n", "- Blocks can also be nested:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x = -324\n", "if x < 0 :\n", " if x < -100 :\n", " print('x < -100')\n", " else :\n", " print('-100 <= x < 0')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- The standard convention is 4 spaces indentation for a block.\n", "- This makes code easily readable by other users.\n", "- The commonly prescribed to coding convention for python is [PEP-8](http://www.python.org/dev/peps/pep-0008/).\n", "- It includes other guidelines for writing easily human readable code.\n", "- Python accepts any level of indentation though, provided it's consistent within a block, so it's a matter of personal preference." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Eg, using 2 spaces.\n", "\n", "x = 1e6\n", "if x < 0 :\n", " print('Negative')\n", "elif x == 0 :\n", " print('Zero')\n", "else :\n", " print('Positive')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- A common comment is that it might be better to enclose code blocks in braces, `{ ... }`, rather than just using whitespace, but to get the developers' opinion just try \n", "\n", "`from __future__ import braces`\n", "\n", "and\n", "\n", "`import this`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Functions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- For any repeated operations you want to define a function.\n", "- This is done with the `def` keyword followed by the name of the function.\n", "- Arguments to the function are put in brackets following the name of the function.\n", "- There's no need for a separate header file (like many other languages) - declare and implement functions literally anywhere as needed.\n", "- No return type needs to be specified, nor argument types (if there are any arguments).\n", "- In fact, no need to specify if the function even returns anything!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Put \"Hello world!\" into a function.\n", "# It doesn't take any arguments.\n", "\n", "def hello_world() :\n", " print('Hello world!')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Then call it - remember the brackets even if \n", "# there are no arguments\n", "\n", "hello_world()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- The return value of a function is specified with the `return` keyword." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# A simple function with two arguments.\n", "\n", "def sum_of_squares(x, y) :\n", " return x**2 + y**2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Call the function & assign a variable from its\n", "# return value\n", "\n", "a, b = 2, 3\n", "c = sum_of_squares(a, b)\n", "print(c)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# A function with no return value actually returns None\n", "\n", "var = hello_world()\n", "print(var)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Functions are also objects, and so can be assigned to variables." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(type(sum_of_squares))\n", "radius_sq = sum_of_squares\n", "print(radius_sq == sum_of_squares)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- With any calculation, the type returned depends on the types of the arguments - the highest precision type of the inputs will be the type of the return value." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Call the variable assigned to the function just\n", "# as you do the original function.\n", "\n", "x = radius_sq(1, 2)\n", "print(type(x))\n", "x = radius_sq(3, 4.)\n", "print(type(x))\n", "x = radius_sq(3+4j, 6.)\n", "print(type(x))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Recursive functions (functions that call themselves) work as well - just make sure there's some end to the recursion." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def fibonacci(n) :\n", " if n < 3 :\n", " return 1\n", " return fibonacci(n-1) + fibonacci(n-2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# When printing, the default value of 'end' is '\\n',\n", "# so each call to print outputs on a new line.\n", "# Using end=' ' means they're printed on the same\n", "# line with spaces between them.\n", "\n", "print(fibonacci(1), end=' ')\n", "print(fibonacci(2), end=' ')\n", "print(fibonacci(3), end=' ')\n", "print(fibonacci(4), end=' ')\n", "print(fibonacci(5), end=' ')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# If you induce an infinite recursion, an exception\n", "# is raised.\n", "\n", "def infinite():\n", " infinite()\n", " \n", "infinite()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Functions can be written to accept variable numbers of arguments in very flexible ways using \"`*args`\" and \"`**kwargs`\" as arguments.\n", "- See the [hands-on exercises](SUPAPYT-LabProblems.html) for more details." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "xkcd python\n", "\n", "- Try `import antigravity` from the python prompt!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Sequences" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Sequences contain other objects.\n", "- Strings are just sequences of characters.\n", "- Elements of a sequence can be iterated over in order.\n", "- There are several different types of sequences in python, each with different functionality and applications.\n", "- Almost every script will use a sequence in some way." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Lists" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- \"Lists\" are like __mutable__ arrays, ie, their contents can be changed.\n", "- They can contain objects of different types." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Lists are represented by square brackets.\n", "# Create an empty list:\n", "\n", "L1 = []\n", "# Or use the list constructor:\n", "L2 = list()\n", "print(L1, L1==L2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Lists can contain objects of any type:\n", "\n", "x = 3.4 + 6.8j\n", "# Create a list with predefined content:\n", "L1 = [1, 2, 3.5, 'spam', x, True, None]\n", "print(L1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Get the length of a sequence with len()\n", "\n", "print(len(L1))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Add a single element to a list using append.\n", "\n", "L = [1, 2, 3]\n", "L.append(4)\n", "print(L)\n", "L.append('spam')\n", "print(L)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# An element of a list can be another list.\n", "\n", "L.append([5, 6])\n", "print(L)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "# Add one or more elements to a list using extend.\n", "# extend iterates over the object passed to it and adds each \n", "# element to the list.\n", "\n", "L = [1, 2, 3]\n", "L.extend([4, 5])\n", "print(L)\n", "L.extend([6])\n", "print(L)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Since a string is a sequence, each character is appended to\n", "# the list in turn.\n", "\n", "L.extend('spam')\n", "print(L)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# If you pass something that's not iterable to extend then\n", "# you get an exception.\n", "\n", "L.extend(5)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Similarly to strings, lists can be concatenated with +\n", "\n", "L1 = [3, 2, 1, 0]\n", "L2 = L1 + [-1, -2]\n", "print(L2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The += operator behaves similarly to extend, \n", "# but only works when adding on another list,\n", "# not just any iterable object.\n", "\n", "L1 += [-1, -2, -3]\n", "print(L1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Lists can also be multplied by integers to repeat them.\n", "\n", "L1 = [1, 2] * 3\n", "print(L1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Elements or slices can be accessed by index,\n", "# just like strings.\n", "\n", "L = [3, 2, 1, 0, -1, -2, -3]\n", "print(L[0])\n", "print(L[1:3])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Lists are \"mutable\", meaning you can change their elements.\n", "\n", "print(L)\n", "# Access an element and assign a new value\n", "L[0] = 5\n", "print(L)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You can even change slices of the list for another list\n", "\n", "print(L)\n", "# Access a slice and change its values\n", "L[-3:] = [1,2,3]\n", "print(L)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# This is not so for strings, they're \"immutable\", so you can't\n", "# change an element. If you try you get an exception.\n", "\n", "name = 'Sir Lancelot'\n", "name[0] = 's'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The pop method removes an element and returns its value.\n", "\n", "L = [3, 2, 1, 0, -1, -2, -3]\n", "\n", "# By default it's the last element in the list.\n", "\n", "elm1 = L.pop()\n", "print(L)\n", "print(elm1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# pop can also take an index as argument to remove a specific element.\n", "\n", "elm2 = L.pop(0)\n", "print(L)\n", "print(elm2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You can simply delete elements or slices from the list using del\n", "\n", "L = [3, 2, 1, 0, -1, -2, -3]\n", "del L[0]\n", "print(L)\n", "del L[:2]\n", "print(L)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You can check if a list contains an object with the 'in' \n", "# keyword\n", "\n", "print(0 in L)\n", "print(3 in L)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Tuples" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- \"Tuples\" are very similar to lists but are __immutable__.\n", "- Like strings, this means you can't modify the elements of a tuple.\n", "- So if you intend to change a sequence later, use a list; if you want it to be impossible to change, use a tuple." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Tuples are represented by regular brackets ().\n", "# An empty tuple:\n", "\n", "t1 = ()\n", "t2 = tuple()\n", "print(t1)\n", "print(t1==t2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Otherwise elements of a tuple are separated by commas.\n", "# You can put previously assigned variables into any\n", "# sequence.\n", "\n", "x = [3,4]\n", "t = (1, 2, 3.5, 'a', x, True, None)\n", "print(t)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# To create a single element tuple you need to add a comma\n", "# This just assigns zero to t1\n", "\n", "t1 = (0)\n", "\n", "# While this makes a single element tuple\n", "\n", "t2 = (0,)\n", "print(t1, type(t1))\n", "print(t2, type(t2))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# If you omit the brackets any sequence of objects separated by\n", "# commas is made into a tuple on-the-fly.\n", "\n", "t1 = 1, 2, 3, 4\n", "print(t1, type(t1))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You can get the number of elements using len as with a list.\n", "\n", "len(t1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Concatenation work in the same way as lists.\n", "\n", "t2 = t1 + (5, 6)\n", "print(t2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# So does multiplication.\n", "\n", "t3 = (1, 2) * 3\n", "print(t3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# As with access to elements or slices.\n", "\n", "print(t2[0]) \n", "print(t2[1:3]) \n", "print(t2[-1])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Check if an element is in the tuple with 'in', like lists.\n", "\n", "print(1 in t3)\n", "print(5 in t3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Though, as with strings, if you try to change an element \n", "# you get an exception\n", "\n", "t2[0] = 'eggs'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Similarly, no pop method exists, and you can't delete \n", "# elements or slices of a tuple using del. Though you \n", "# can delete the whole tuple as with any variable\n", "# like so:\n", "\n", "del t2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You can \"unpack\" a tuple or list, or any other iterable, \n", "# and assign their elements to individual variables, like so:\n", "\n", "t = 1, 2, 3\n", "x, y, z = t\n", "print(x, y, z)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# This also works for strings.\n", "\n", "s = 'abc'\n", "c1, c2, c3 = s\n", "print(c1)\n", "print(c2)\n", "print(c3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Swapping the contents of variables is then trivial:\n", "a, b = 1, 2\n", "print(a, b)\n", "b, a = a, b\n", "print(a, b)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Dictionaries" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- \"Dictionaries\" store elements in pairs of (key, element).\n", "- Elements are then accessed by keys, whereas lists and tuples access elements by index (integers).\n", "- A key can be any type of object (so long as it's immutable), as can the elements." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Dictionaries are represented by curly brackets {}.\n", "# Make an empty dictionary like so:\n", "\n", "d1 = {}\n", "d2 = dict()\n", "print(d1)\n", "print(d1==d2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- The syntax for creating a dictionary is: \n", "\n", "`d = {key1 : element1, key2 : element2, ...}`.\n", "\n", "- Strings are often used as keys." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "d = {'nothing' : 0, \n", " 'a' : 1, \n", " 'b' : 'something'}\n", "print(d)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The value of an element is then retrieved using the \n", "# relevant key.\n", "\n", "print(d['a'])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You can also store a key in a variable and use the \n", "# variable as index:\n", "\n", "key = 'b'\n", "print('Key:', key, ', element:', d[key])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# len works as on lists and tuples.\n", "\n", "print('Length of d is', len(d))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You don't have to use strings as keys, you can\n", "# use dicts to map pairs of (almost) any types\n", "\n", "d = {None : 'nothing', \n", " 2+3j : 84, \n", " 3.4 : 6}\n", "print(d)\n", "print(d[3.4])\n", "print(d[None])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "phonebook = {'Me' : 1000, \n", " 'Sir Robin' : 2000, \n", " 'Sir Gallahad' : 3000}\n", "\n", "# Get a sequence of keys with the keys() member method.\n", "# Convert it to a list just so it's printed nicely.\n", "print(list(phonebook.keys()))\n", "\n", "# Get a sequence of the element values with values()\n", "print(list(phonebook.values()))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Check if a dictionary has a given key with 'in' keyword.\n", "\n", "print('Sir Robin' in phonebook)\n", "print('Sir Lancelot' in phonebook)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Dictionaries are mutable, so you can change the elements \n", "# as with lists:\n", "\n", "phonebook['Sir Robin'] = 2345" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Or you can add a new (key, element) pair by assigning a \n", "# value to a key that isn't in the dict:\n", "\n", "phonebook['Sir Lancelot'] = 8734\n", "print(phonebook)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# As you might expect, if you try to access a key that doesn't \n", "# exist you get an exception.\n", "\n", "phonebook['spam']" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You can use the dict.get method to return a default\n", "# value if the key isn't in the dict\n", "\n", "print(phonebook.get('Me', 1234))\n", "print(phonebook.get('spam', 4567))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Again, similarly to lists, you can delete elements by key:\n", "\n", "del phonebook['Me']\n", "print(phonebook)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Or clear it using the clear() method:\n", "\n", "phonebook.clear()\n", "print(phonebook)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Or delete it entirely.\n", "\n", "del phonebook" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Sets" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- The last kind of builtin sequence in python, sets are __unordered__ sequences of unique elements." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You can declare them like a list or tuple using \n", "# curly brackets\n", "\n", "s = {1,2,3}\n", "print(s)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Or you can use the set constructor, which takes any \n", "# iterable object as argument, from which unique elements\n", "# are added.\n", "\n", "s = set([4,5,6,4,5,6])\n", "print(s)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# If you make a set from a string it selects unique \n", "# characters from it, though their order isn't \n", "# retained.\n", "\n", "s = set('spam and eggs')\n", "print(s)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Note that to make an empty set you need to use the set\n", "# constructor as {} gives you an empty dictionary.\n", "\n", "print(set(), type(set()))\n", "print({}, type({}))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Unlike a list or tuple, you can't access elements by index\n", "\n", "s = set('spam and eggs')\n", "s[0]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# But you can check if a set contains an object in \n", "# the same way.\n", "\n", "print('e' in s)\n", "print(42 in s)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You can add or remove elements using the add and remove\n", "# member methods.\n", "\n", "s.add('k')\n", "print(s)\n", "print('k' in s)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "s.remove('e')\n", "print(s)\n", "print('e' in s)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Sets support many other operations like mathematical sets, eg intersection, union, difference, etc. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Compare to the original set\n", "soriginal = set('spam and eggs')\n", "# This gives the set of elements in 's' that aren't in 'soriginal'\n", "print(s.difference(soriginal))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Looping" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Loops allow you to repeatedly evaluate a block of code with different variable values.\n", "- Since most programs are designed for repetitive tasks (eg, iterating over entries in a dataset), you'll likely make wide use of loops and sequences." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### `while` Statements" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- A `while` statement takes a boolean expression followed by an indented block of code.\n", "- If the boolean expression is `True` the indented code block is evaluated.\n", "- The boolean is then re-evaluated and the process repeats until the expression is found to be `False`, at which point the loop terminates." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "i = 0\n", "while i < 10 :\n", " print(i, end = ' ')\n", " i += 1\n", "# Printing an empty string means the newline is now\n", "# printed.\n", "print('')\n", "# Confirm that the expression now evaluates to False\n", "print(i, i < 10)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Use the fact that a non-empty list evaluates to True\n", "# and an empty one to False.\n", "# Loops backwards over the list.\n", "\n", "L = [1, 2, 3, 4, 5]\n", "while L:\n", " print(L.pop(), end = ' ')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### `for` Loops" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- These loop over the elements in a sequence.\n", "- The \"`range`\" built-in method returns a sequence of integers, which is very useful for looping. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# If only one argument is given to range the sequence goes\n", "# from 0 up to the argument -1.\n", "\n", "# This makes a 'range' object\n", "print(range(10))\n", "\n", "# Convert the sequence to a list so we can see its contents.\n", "print(list(range(10)))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# If two are given range returns a list of integers\n", "# with values between the two.\n", "\n", "print(list(range(1, 4)))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# A third argument gives the step size between elements.\n", "\n", "print(list(range(-8, 10, 2)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- The syntax for looping over any sequence is \n", "\n", "`for element in sequence :`\n", "\n", "- Within the indented block of clode of the loop, the `element` variable (you can call it whatever you want) then takes the values of each element in the sequence in turn.\n", "- The block of code is evaluated for each value in the sequence (each iteration).\n", "\n", "Eg:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Iterate over the sequence of integers returned by range:\n", "\n", "for i in range(1, 4) :\n", " print(i, end=' ')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Iterate over characters in a string:\n", "\n", "for char in 'eggs' :\n", " print(char)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Iterate over a tuple:\n", "\n", "my_tuple = (1, 2, 3, 'a', (8,9), True, None)\n", "for elm in my_tuple :\n", " print(elm, end = ' ')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Iterate over indices of the tuple.\n", "\n", "for i in range(len(my_tuple)) :\n", " print(i, my_tuple[i])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- `continue`, `break` and `pass` can be used to control loop behaviour." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "for i in range(10, -100, -1) :\n", " if i == 0 :\n", " # Continue onto the next iteration.\n", " continue\n", " elif i % 2 == 1 :\n", " # The pass statement means \"do nothing\".\n", " pass\n", " else :\n", " print(i, end=' ')\n", " \n", " if i < -10 :\n", " # Stop the enclosing loop entirely.\n", " break" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Introspection" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- We've now seen a few more complicated objects that have various attributes and member functions: `str`, `complex`, `list`, `tuple`, `dict`, `set`.\n", "- In other languages, you normally have to look up a reference library or examine source code to find the full list of attributes and functions for a given class.\n", "- In python, however, documentation is built in, and an object can provide almost all the info you'll need on it interactively!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### `dir`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Firstly the `dir()` method.\n", "- Called without an argument it returns a list of all variables that're available in the current scope.\n", "- Called on an object it returns a list of attributes of that object." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- On a fresh python prompt the same variables are always available:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`>>> dir()`\n", "\n", "`['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__']`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Any variable prefixed with underscores is normally not meant to be accessed directly, only internally, though you can still access them should you have need.\n", "- __D__ouble __under__scored variables are called \"dunders\".\n", "- `builtins` contains all the default classes and functions available in python.\n", "- `doc` contains the documentation on the current module. \n", "- `name` and `package` are the name of the current module and the package to which it belongs." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# As we've declared many things in the process of this course dir \n", "# returns rather more now.\n", "\n", "print(dir())" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You can see the full list of built-in functionality \n", "# available by doing\n", "\n", "print(dir(__builtins__))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You can call dir on a class type or an instance of a class.\n", "\n", "print(dir(complex))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Again we see lots of variables with underscores before and after their names.\n", "- We also see the `real` and `imag` members we used before, as well as the `conjugate` method." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Many of the 'hidden' (dunder) methods are called behind the scenes when operators are used. \n", "- Eg, the `__add__` method is what's used by the + operator." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# These are all equivalent:\n", "\n", "print(1 + 2)\n", "print((1).__add__(2))\n", "x, y = 1, 2\n", "print(x.__add__(y))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- This is relevant when it comes to writing your own classes, which we'll discuss later." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### `help`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- So `dir` is good for finding names of attributes, but doesn't tell you anything about them.\n", "- For that you need the `help` method, which accesses the internal documentation." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# For instance, the dir method has its own built-in documentation:\n", "\n", "help(dir)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Or for the conjugate method of the complex class:\n", "\n", "help(complex.conjugate)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You can also enter interactive help mode by calling help without an \n", "# argument. You can then enter any class or method name for help on it, \n", "# or search for a specific word, etc.\n", "\n", "#help()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- This looks slightly different on the interactive prompt, rather than in this notebook, but the functionality is all the same." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- The built-in documentation accessed by the `help` method is stored in a function's \"doc string\" as part of the function definition.\n", "- Any string declared at the top of a function/class/module definition is taken as being the doc string." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Redefine the method with a doc string.\n", "\n", "def sum_of_squares(x, y) :\n", " '''Returns the sum of the squares of the two arguments.'''\n", " \n", " return x**2 + y**2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "help(sum_of_squares)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The doc string is stored in the __doc__ attribute \n", "# of an object.\n", "print(sum_of_squares.__doc__)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Always add a doc string where possible!\n", "- It's easily done - write doc as you go.\n", "- Allows someone else (or yourself in future) to find out what your code does easily.\n", "- Without it `help` is much less useful." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Type checking" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- We already saw that you can determine the type of an object using the `type` method.\n", "- You can also check if an object is of a certain type using `isinstance`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(isinstance(1, int))\n", "print(isinstance(3., int))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- This is useful if you want to handle different types of objects differently.\n", "- You can also pass a tuple as the second argument to `isinstance`, in which case it returns `True` if the object is of any of the types in the tuple:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Check if something is a numerical type:\n", "\n", "print(isinstance(.234, (int, float)))\n", "print(isinstance('spam', (int, float)))\n", "print(isinstance('spam', str))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- For further info try `dir` or `help` on any object, class name, method, type specification, or indeed anything!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Classes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- As mentioned at the beginning of the course, classes are a means of sensibly organising and grouping data and functionality.\n", "- A class contains member data (\"attributes\") and member functions.\n", "- Every instance of a class has the same data structure and can call the same functions." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The Empty Class" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- As with everything in python, classes can be declared and used in a very versatile manner.\n", "- They're declared with the `class` keyword, followed by the class name and an indented block of code defining the class.\n", "- The most basic class is just an empty one - you don't have to declare a constructor, data members or functions." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class Minimal :\n", " pass" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Then make an instance of the class by calling the constructor:\n", "\n", "m = Minimal()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Attributes can then be assigned dynamically.\n", "\n", "m.spam = 'eggs'\n", "\n", "# They're accessed in the usual way.\n", "print(m.spam)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You can check if an object has an attribute with \"hasattr\"\n", "\n", "print(hasattr(m, 'spam'))\n", "print(hasattr(m, 'bla'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# \"getattr\" retrieves an attribute.\n", "# This is the same as m.spam\n", "\n", "print(getattr(m, 'spam'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Attributes can be removed using del\n", "\n", "del m.spam\n", "print(hasattr(m, 'spam'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Accessing attributes of a class instance is thus a lot like accessing elements of a dictionary, but with slightly different syntax.\n", "- Also, the attribute names must always be strings." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### A Basic Class" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Now for a class designed for a specific purpose.\n", "- Member functions are defined like other functions, using `def`, within the `class` code block.\n", "- The first argument to any member function must be `self`. This represents the instance of the class on which the function is being called. It may be the only argument to a member function.\n", "- Within member functions, `self` is used to access attributes or call other member functions.\n", "- The constructor is defined by the `__init__` function. This is called when a new instance of the class is created. It can initialise data members." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class Pokemon:\n", " '''Basic class describing a Pokemon'''\n", " def __init__(self, name, category, level,\n", " strongAgainst, weakAgainst):\n", " '''Takes the category of the Pokemon (eg, fire/water),\n", " level, strengths and weaknesses.'''\n", " self.name = name\n", " self.category = category\n", " self.level = level\n", " self.experience = 0\n", " # Copy the lists of strengths/weaknesses into new tuples\n", " self.strongAgainst = tuple(strongAgainst)\n", " self.weakAgainst = tuple(weakAgainst)\n", " \n", " def is_strong_against(self, other):\n", " '''Check if this Pokemon is strong against\n", " another Pokemon.'''\n", " return other.category in self.strongAgainst\n", " \n", " def is_weak_against(self, other):\n", " '''Check if this Pokemon is weak against\n", " another Pokemon.'''\n", " return other.category in self.weakAgainst\n", " \n", " def increase_exp(self, exp):\n", " '''Increase the experience of this Pokemon and\n", " possibly level up.'''\n", " # Gradually increase experience and level up as\n", " # necessary.\n", " for i in range(exp):\n", " self.experience += 1\n", " if self.experience >= self.level * 10:\n", " self.level_up()\n", " \n", " def level_up(self):\n", " '''Level up this Pokemon.'''\n", " self.experience -= self.level * 10\n", " self.level += 1\n", " print(self.name, 'grew to level', self.level)\n", " \n", " def print_info(self):\n", " '''Print info on this Pokemon.'''\n", " print(('{0}:\\n'\n", " '- Type: {1}\\n'\n", " '- Level: {2}\\n'\n", " '- Strong against: {3}\\n'\n", " '- Weak against: {4}\\n').format(self.name,\n", " self.category,\n", " self.level,\n", " ', '.join(self.strongAgainst),\n", " ', '.join(self.weakAgainst)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- An instance of the class can then be created by calling the name of the class like a function (the constructor).\n", "- When the constructor/member methods are called then `self` argument is omitted as it's implicit." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Make a Charmander\n", "charmander = Pokemon('Charmander', 'fire', 1,\n", " # Fire is strong against grass & ice\n", " ('grass', 'ice'),\n", " # Fire is weak against water & rock\n", " ('water', 'rock'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Introspection works on user defined classes just like builtin classes." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(dir(charmander))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# help(Pokemon) would also work.\n", "\n", "help(charmander)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Use help on a specific function\n", "help(Pokemon.is_weak_against)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# As said before, the doc is stored in the __doc__\n", "# attribute\n", "print(Pokemon.is_weak_against.__doc__)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Make another instance with different attributes,\n", "# eg, a water type Pokemon\n", "squirtle = Pokemon('Squirtle', 'water', 1,\n", " # Water is strong against fire\n", " ('fire',),\n", " # Water is weak against electric\n", " ('electric',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Call some functions\n", "squirtle.print_info()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "squirtle.increase_exp(10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Inheritance" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- This is another key component of object oriented programming.\n", "- A derived class inherits from the base class, retains all its attributes and functionality, and can add more, or redefine functions.\n", "- This is particularly useful if you want a group of different classes to have the same interface (attributes and functions) but with different implementations.\n", "- To define a derived class, the name of the base class goes in brackets after the name of the derived class on the `class` line." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- A function definition in the derived class with the same name as a function in the base class overrides the base class definition.\n", "- The definition in the most derived class is always the one that's used.\n", "- If you need to call the version from the base class, you can do so by calling `super()` to access the base class.\n", "- See [here](https://docs.python.org/3/library/functions.html#super) for more info on `super`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Write a derived class for Charmander\n", "class Charmander(Pokemon):\n", " '''Describes all Charmanders.'''\n", " def __init__(self, level, name='Charmander'):\n", " '''Initialise a Charmander. Just needs the level\n", " and optionally a name.'''\n", " # Use super to call __init__ of the Pokemon\n", " # base class.\n", " super(Charmander, self).__init__('Charmander', 'fire', level,\n", " ('grass', 'ice'),\n", " ('water', 'rock'))\n", " # Assign a new attribute\n", " self.petname = name\n", " \n", " def level_up(self):\n", " '''Level up Charmander.'''\n", " super(Charmander, self).level_up()\n", " if self.level == 4:\n", " print('Charmander learned \"ember\"!')\n", " elif self.level == 16:\n", " print('Charmander evolved into Charmeleon!')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Instantiate as before.\n", "charmander = Charmander(3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Check its attributes\n", "print(dir(charmander))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- You can see that the derived class has all the attributes of the base class, and the additional `petname` attribute." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Now call some functions.\n", "# This uses the definition in the Pokemon base class.\n", "\n", "charmander.is_strong_against(squirtle)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# When level_up is called, it uses the version\n", "# defined in the Charmander derived class.\n", "\n", "charmander.increase_exp(30)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Using `__slots__` to specify attribute names" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Python's dynamic assignment of variables can be useful, but can lead to bugs if you're not careful." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Eg, this typo passes silently:\n", "\n", "charmander.naem = 'Bob'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "charmander.print_info()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(dir(charmander))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- You can restrict attribute names in a class by defining a `__slots__` attribute.\n", "- This goes in the main body of the class definition, not in `__init__`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Redefine the class with __slots__.\n", "\n", "class Pokemon:\n", " '''Basic class describing a Pokemon'''\n", " \n", " # Define the allowed attributes\n", " __slots__ = ('name', 'category', 'level',\n", " 'experience', 'strongAgainst',\n", " 'weakAgainst')\n", " \n", " def __init__(self, name, category, level,\n", " strongAgainst, weakAgainst):\n", " '''Takes the category of the Pokemon (eg, fire/water),\n", " level, strengths and weaknesses.'''\n", " self.name = name\n", " self.category = category\n", " self.level = level\n", " self.experience = 0\n", " # Copy the lists of strengths/weaknesses into new tuples\n", " self.strongAgainst = tuple(strongAgainst)\n", " self.weakAgainst = tuple(weakAgainst)\n", " \n", " def is_strong_against(self, other):\n", " '''Check if this Pokemon is strong against\n", " another Pokemon.'''\n", " return other.category in self.strongAgainst\n", " \n", " def is_weak_against(self, other):\n", " '''Check if this Pokemon is weak against\n", " another Pokemon.'''\n", " return other.category in self.weakAgainst\n", " \n", " def increase_exp(self, exp):\n", " '''Increase the experience of this Pokemon and\n", " possibly level up.'''\n", " # Gradually increase experience and level up as\n", " # necessary.\n", " for i in range(exp):\n", " self.experience += 1\n", " if self.experience >= self.level * 10:\n", " self.level_up()\n", " \n", " def level_up(self):\n", " '''Level up this Pokemon.'''\n", " self.experience -= self.level * 10\n", " self.level += 1\n", " print(self.name, 'grew to level', self.level)\n", " \n", " def print_info(self):\n", " '''Print info on this Pokemon.'''\n", " print(('{0}:\\n'\n", " '- Type: {1}\\n'\n", " '- Level: {2}\\n'\n", " '- Strong against: {3}\\n'\n", " '- Weak against: {4}\\n').format(self.name,\n", " self.category,\n", " self.level,\n", " ', '.join(self.strongAgainst),\n", " ', '.join(self.weakAgainst)))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Similarly for the derived class.\n", "\n", "class Charmander(Pokemon):\n", " '''Describes all Charmanders.'''\n", " \n", " # Note that you only need to list the attributes\n", " # added in the derived class\n", " __slots__ = ('petname',)\n", " \n", " def __init__(self, level, name='Charmander'):\n", " '''Initialise a Charmander. Just needs the level\n", " and optionally a name.'''\n", " # Use super to call __init__ of the Pokemon\n", " # base class.\n", " super(Charmander, self).__init__('Charmander', 'fire', level,\n", " ('grass', 'ice'),\n", " ('water', 'rock'))\n", " # Assign a new attribute\n", " self.petname = name\n", " \n", " def level_up(self):\n", " '''Level up Charmander.'''\n", " super(Charmander, self).level_up()\n", " if self.level == 4:\n", " print('Charmander learned \"ember\"!')\n", " elif self.level == 16:\n", " print('Charmander evolved into Charmeleon!')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Now we get an exception from the typo.\n", "\n", "charmander = Charmander(1)\n", "charmander.naem = 'Bob'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- See [here](https://docs.python.org/3/reference/datamodel.html#slots) for more info on `__slots__` and other aspects of the data model in python." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Class attributes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- An attribute declared outside a member method is a \"class attribute\" (similar to a static member in C++ or Java), rather than an instance attribute - its value is shared between all instances of the class.\n", "- `__slots__` is an example of this." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class Charmander(Pokemon):\n", " '''Describes all Charmanders.'''\n", " \n", " # Note that you only need to list the attributes\n", " # added in the derived class\n", " __slots__ = ('petname',)\n", " \n", " # Define a class attribute\n", " verbose = False\n", " \n", " def __init__(self, level, name='Charmander'):\n", " '''Initialise a Charmander. Just needs the level\n", " and optionally a name.'''\n", " # Use super to call __init__ of the Pokemon\n", " # base class.\n", " super(Charmander, self).__init__('Charmander', 'fire', level,\n", " ('grass', 'ice'),\n", " ('water', 'rock'))\n", " # Assign a new attribute\n", " self.petname = name\n", " \n", " if Charmander.verbose:\n", " print('Made a new Charmander')\n", " self.print_info()\n", " \n", " def level_up(self):\n", " '''Level up Charmander.'''\n", " super(Charmander, self).level_up()\n", " if self.level == 4:\n", " print('Charmander learned \"ember\"!')\n", " elif self.level == 16:\n", " print('Charmander evolved into Charmeleon!')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You can access class attributes through the class itself\n", "# or through instances of the class, they all refer to the\n", "# same object.\n", "charmander1 = Charmander(1)\n", "charmander2 = Charmander(1)\n", "print(charmander1.verbose)\n", "print(id(charmander1.verbose) == id(charmander2.verbose)\n", " == id(Charmander.verbose))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Change the value of the class attribute like so:\n", "# (to change the value for all class instances this\n", "# has to be done via the class itself)\n", "Charmander.verbose = True\n", "charmander3 = Charmander(1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The value of the attribute is the same for all instances \n", "# of the class.\n", "print(Charmander.verbose)\n", "print(charmander1.verbose)\n", "print(charmander2.verbose)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- There are also functions for more versatile and efficient attribute access through \"descriptors\" using [`property`](https://docs.python.org/3/library/functions.html#property), as well as static and class function definitions through [`staticmethod`](https://docs.python.org/3/library/functions.html#staticmethod) and [`classmethod`](https://docs.python.org/3/library/functions.html#classmethod)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ancient code" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Modules" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Modules are python's equivalent of libraries in other languages.\n", "- They provide a simple way of grouping related functionality.\n", "- They're the main means of extending python (adding functionality)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Modules define their namespace.\n", " - Variables defined in one module don't interfere with variables of the same name in another module.\n", " - That is, unless they're imported into the same namespace." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- There're different types of modules:\n", " - Built-in modules:\n", " - Always available.\n", " - Popular examples are: `sys`, `math`, `time`.\n", " - Standard library modules:\n", " - Come with a standard python install.\n", " - Eg: `os`, `urllib`.\n", " - Third-party modules:\n", " - Written & maintained by someone other than the python foundation.\n", " - Eg: PyRoot (a python port of the ROOT c++ library), `numpy`, `matplotlib`.\n", " - User defined modules:\n", " - Whatever code you need!\n", " - Easily distributed." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Importing Modules" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Importing modules is simply done using the `import` keyword:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The built-in math module\n", "\n", "import math\n", "print(dir(math))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Call a method or access a variable contained in a module\n", "# in the same way as you'd access methods & attributes of\n", "# any other python object.\n", "\n", "print(math.pi)\n", "print(math.sin(math.pi/2.))\n", "print(math.sin(math.pi))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- There're various ways of importing." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# import a specific function/variable from a module.\n", "\n", "from math import sin\n", "from math import cos, pi\n", "\n", "# They're then accessible in the \"local namespace\"\n", "# so you can drop the math. prefix\n", "\n", "print(sin(pi/4.), cos(pi/4.))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You can also rename a variable at import using the\n", "# 'as' keyword.\n", "# Sometimes desirable to avoid confusion over variables.\n", "\n", "from math import sqrt as my_sqrt\n", "print(my_sqrt(4))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Import everything from a module into the current\n", "# namespace.\n", "# Use with care! For large modules this is slow &\n", "# memory intensive. It's much more efficient to \n", "# import only what you need.\n", "\n", "from math import *\n", "print(e)\n", "print(log(e))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Import a sub-module from a module.\n", "\n", "from os import path\n", "print(dir(path))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Import a method/variable from a sub-module.\n", "\n", "from os.path import exists\n", "print(exists('./Hello_world.py'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- The standard `import` actually calls the built-in `__import__` method in the background.\n", "- `__import__` takes a string as argument, which allows you to import programmatically." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The __import__ method actually returns the module.\n", "# Modules are objects too!\n", "\n", "math_module = __import__('math')\n", "print(dir(math_module))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# If you alternatively want to use the cmath module,\n", "# which supports maths with complex numbers.\n", "\n", "math_module = __import__('cmath')\n", "print(dir(math_module))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Be careful of importing variables with the same name, as the latest import can overwrite previous ones.\n", "- Eg, both `math` and `cmath` modules contain `sin`, `cos`, etc, methods.\n", "- Both `array` and `numpy` modules contain an `array` class." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from array import array\n", "myArray = array('d', [0.] * 3)\n", "print(myArray)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Executing the same code after importing the numpy array \n", "# class crashes, as the constructors take different \n", "# arguments.\n", "\n", "from numpy import array\n", "myArray = array('d', [0.] * 3)\n", "print(myArray)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- The [Standard Library](https://docs.python.org/3/tutorial/stdlib.html) contains many different modules.\n", "- Some popular ones are:\n", " - `sys`, `glob`\n", " - `os`, `commands`, `shutil`\n", " - `math`, `cmath`, `array`\n", " - `datetime`\n", " - `StringIO`, `re`\n", " - `getopt`, `argparse`\n", " - `webbrowser`, `urllib2`\n", " - `timeit`\n", "- Check them out by importing and trying `help` on them.\n", "- You can normally find out which module you want for any given task via a quick web search." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Writing Your Own Modules" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Any python script can be imported as a module.\n", "- The module name is simply the name of the file.\n", "- Eg, putting the `Pokemon` and `Charmander` class definitions in a file called Pokemon.py.\n", "- In a Jupyter notebook, putting `%%writefile ` at the top of a cell will write the contents of the cell to a file named ``:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%writefile Pokemon.py \n", "\n", "'''A set of classes to describe different Pokemon.'''\n", "\n", "\n", "class Pokemon:\n", " '''Basic class describing a Pokemon'''\n", " \n", " # Define the allowed attributes\n", " __slots__ = ('name', 'category', 'level',\n", " 'experience', 'strongAgainst',\n", " 'weakAgainst')\n", " \n", " def __init__(self, name, category, level,\n", " strongAgainst, weakAgainst):\n", " '''Takes the category of the Pokemon (eg, fire/water),\n", " level, strengths and weaknesses.'''\n", " self.name = name\n", " self.category = category\n", " self.level = level\n", " self.experience = 0\n", " # Copy the lists of strengths/weaknesses into new tuples\n", " self.strongAgainst = tuple(strongAgainst)\n", " self.weakAgainst = tuple(weakAgainst)\n", " \n", " def is_strong_against(self, other):\n", " '''Check if this Pokemon is strong against\n", " another Pokemon.'''\n", " return other.category in self.strongAgainst\n", " \n", " def is_weak_against(self, other):\n", " '''Check if this Pokemon is weak against\n", " another Pokemon.'''\n", " return other.category in self.weakAgainst\n", " \n", " def increase_exp(self, exp):\n", " '''Increase the experience of this Pokemon and\n", " possibly level up.'''\n", " # Gradually increase experience and level up as\n", " # necessary.\n", " for i in range(exp):\n", " self.experience += 1\n", " if self.experience >= self.level * 10:\n", " self.level_up()\n", " \n", " def level_up(self):\n", " '''Level up this Pokemon.'''\n", " self.experience -= self.level * 10\n", " self.level += 1\n", " print(self.name, 'grew to level', self.level)\n", " \n", " def print_info(self):\n", " '''Print info on this Pokemon.'''\n", " print(('{0}:\\n'\n", " '- Type: {1}\\n'\n", " '- Level: {2}\\n'\n", " '- Strong against: {3}\\n'\n", " '- Weak against: {4}\\n').format(self.name,\n", " self.category,\n", " self.level,\n", " ', '.join(self.strongAgainst),\n", " ', '.join(self.weakAgainst)))\n", " \n", "class Charmander(Pokemon):\n", " '''Describes all Charmanders.'''\n", " \n", " # Note that you only need to list the attributes\n", " # added in the derived class\n", " __slots__ = ('petname',)\n", " \n", " # Define a class attribute\n", " verbose = False\n", " \n", " def __init__(self, level, name='Charmander'):\n", " '''Initialise a Charmander. Just needs the level\n", " and optionally a name.'''\n", " # Use super to call __init__ of the Pokemon\n", " # base class.\n", " super(Charmander, self).__init__('Charmander', 'fire', level,\n", " ('grass', 'ice'),\n", " ('water', 'rock'))\n", " # Assign a new attribute\n", " self.petname = name\n", " \n", " if Charmander.verbose:\n", " print('Made a new Charmander')\n", " self.print_info()\n", " \n", " def level_up(self):\n", " '''Level up Charmander.'''\n", " super(Charmander, self).level_up()\n", " if self.level == 4:\n", " print('Charmander learned \"ember\"!')\n", " elif self.level == 16:\n", " print('Charmander evolved into Charmeleon!')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Then we can import it.\n", "\n", "import Pokemon" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The string at the top of the file is the doc string\n", "# for the module itself.\n", "\n", "print(Pokemon.__doc__)\n", "print(dir(Pokemon))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- So by putting variable/method/class definitions into separate files you create modules that can be imported and re-used.\n", "- The environment variable `PYTHONPATH` is a list of directories where python looks for modules.\n", " - We can import the `Pokemon` module because the file `Pokemon.py` is in our current working directory.\n", " - If we wanted to `import Pokemon` when working in another directory we'd need to add the directory containing `Pokemon.py` to `PYTHONPATH`.\n", " - Any python modules in directories contained in `PYTHONPATH` are thus available system wide." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Within python, `PYTHONPATH` is stored in the `path` variable in the `sys` module.\n", "- You can also access and edit this at runtime:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import sys\n", "print(sys.path)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- As an example, we can move `Pokemon.py` to another directory, say called `PokemonRed`, and rename it to `NewPokemon.py`.\n", "- We can do this using the `os` and `shutil` modules:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import os, shutil\n", "\n", "dirname = 'PokemonRed'\n", "if not os.path.exists(dirname) :\n", " os.mkdir(dirname)\n", "shutil.copy('Pokemon.py', os.path.join(dirname, 'NewPokemon.py'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# This doesn't currently work.\n", "import NewPokemon" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Add the PokemonRed directory to sys.path\n", "\n", "sys.path.append('./PokemonRed')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Then we can import NewPokemon\n", "\n", "import NewPokemon" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# It just contains the same functionality as \n", "# Pokemon.\n", "\n", "print(dir(NewPokemon))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- This can be useful if you want to only make some modules available at runtime, eg, if they're still being debugged, or are not often used." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Importing or Executing as Main" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- When a module is imported, all code in it is executed.\n", "- This means you normally want to keep only definitions (functions/classes/constants) in module files, and no \"main\" code (which uses the definitions to do something).\n", "- One trick to get around this is that whatever script is executed as the main code will have name (contained in the `__name__` variable) set to `'__main__'`, rather than the file name.\n", "- So if we add, at the end of Pokemon.py, the lines:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%writefile -a Pokemon.py\n", "\n", "if __name__ == '__main__' :\n", " charmander = Charmander(1)\n", " charmander.print_info()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- This section of code is only executed in the case that Pokemon.py is the main file passed to the python interpreter.\n", "- In a Jupyter notebook, you can make system calls (as at the commandline) by prefixing a statement with `!`.\n", "- So we can execute `Pokemon.py` as the main file by doing:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!python Pokemon.py" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- However, when importing `Pokemon` into another script the `__name__` variable within `Pokemon` is set to `'Pokemon'`, not `'__main__'`, and we get no output.\n", "- Having imported a module, a second `import` of the same module does nothing. If the module has changed, you have to use `importlib.reload` (though this is very rare):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import importlib\n", "\n", "# This gives no output.\n", "importlib.reload(Pokemon)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- This is very handy for testing code, as you can put test code after the \"`if __name__ == '__main__'`\" statement, and call the script directly only when testing.\n", "- Even if your script is only intended to be executed as the main programme it's a good idea to use this trick to separate any other functionality defined in the script from the main code." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Packages" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Packages are a means of organising groups of modules.\n", "- Modules are placed in a package directory, along with a `__init__.py` file.\n", "- The presence of the `__init__.py` file signals to python that the directory is a package. \n", "- `__init__.py` contains any code you want executed when the package is imported (it can be empty). " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- We can turn the `PokemonRed` directory into a package by adding a `__init__.py` file.\n", "- Let's add the `Pokemon.py` module first:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import os, shutil\n", "\n", "# Make the directory.\n", "dirname = 'PokemonRed'\n", "if not os.path.exists(dirname) :\n", " os.mkdir(dirname)\n", " \n", "# Copy Pokemon.py to the directory.\n", "shutil.copy('Pokemon.py', os.path.join(dirname, 'Pokemon.py'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Next we make an empty `__init__.py` file. More on manipulating files [in the next section](#Files,-Input-&-Output)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "with open(os.path.join(dirname, '__init__.py'), 'w') as finit :\n", " pass" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Check the contents of PokemonRed\n", "\n", "os.listdir(dirname)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Now we can import the PokemonRed package.\n", "\n", "import PokemonRed" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Packages are treated identically to modules. \n", "\n", "type(PokemonRed)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Import the Pokemon module from the PokemonRed package\n", "\n", "from PokemonRed import Pokemon\n", "print(dir(Pokemon))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Import the Charmander class from the NewPokemon submodule of \n", "# the PokemonRed package.\n", "\n", "from PokemonRed.Pokemon import Charmander\n", "print(dir(Charmander))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Just like modules, any package that resides in a directory included in the `PYTHONPATH` environment variable can be imported. \n", "- Since they're just simple directory structures you can easily compress a package into a single file and share it." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Files, Input & Output" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Reading & Writing Files" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- You'll likely need to read in data from a file, or write output to a file often when programming.\n", "- You can open a text file using the `open` built-in method.\n", "- The open method takes a name for the file and a mode in which to open it.\n", "- The mode can be `'r'` for read, `'w'` for write, or `'a'` for append.\n", "- `'r'` is default.\n", "- If `'w'` or `'a'` is used for a file that doesn't exist then the file is created.\n", "- For non-text files, you can open the file in binary mode by adding `'b'` to any of these modes (eg, `'wb'` to write in binary mode). \n", "- A file written in binary mode should be read in binary mode (`'rb'`)." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "<_io.TextIOWrapper name='movie_titles.txt' mode='w' encoding='UTF-8'>\n", "\n" ] } ], "source": [ "# Open a file in write mode.\n", "\n", "fmovies = open('movie_titles.txt', 'w')\n", "print(fmovies) \n", "print(type(fmovies))" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "10" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# You can write to a file like so, for a single string.\n", "# Note you have to explicitly add the newline character.\n", "\n", "fmovies.write('The Quest\\n')" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "# Or several strings at once, contained in a sequence:\n", "\n", "fmovies.writelines(['for the\\n', 'Holy Grail\\n'])" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "# Then close the file:\n", "\n", "fmovies.close()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- You always have to close a file when you're done with it so that it's written to disc." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['The Quest\\n', 'for the\\n', 'Holy Grail\\n']\n" ] } ], "source": [ "# Then open the file and read its contents.\n", "\n", "fmovies = open('movie_titles.txt')\n", "lines = fmovies.readlines()\n", "print(lines)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[]\n" ] } ], "source": [ "# By reading all lines in the file the \"cursor\" \n", "# is at the end of the file, and another call to \n", "# readlines returns an empty list.\n", "\n", "lines = fmovies.readlines()\n", "print(lines)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['The Quest\\n', 'for the\\n', 'Holy Grail\\n']\n" ] } ], "source": [ "# You can skip to a specific place in a file using \n", "# the \"seek\" method. Though it's rare to need to read\n", "# the same file more than once.\n", "\n", "fmovies.seek(0)\n", "lines = fmovies.readlines()\n", "print(lines)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'The Quest\\nfor the\\nHoly Grail\\n'" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Another way to read the whole file is using 'read',\n", "# which returns a single string.\n", "\n", "fmovies.seek(0)\n", "contents = fmovies.read()\n", "contents" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['The Quest\\n', 'for the\\n', 'Holy Grail\\n']\n" ] } ], "source": [ "# Alternatively, you can read one line at a time\n", "# with 'readline'. When you reach the end of the \n", "# file, this returns a blank string.\n", "\n", "fmovies.seek(0)\n", "line = fmovies.readline()\n", "lines = []\n", "while line:\n", " lines.append(line)\n", " line = fmovies.readline()\n", "print(lines)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "# Again, close the file when you're done with it.\n", "\n", "fmovies.close()" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The Quest\n", "for the\n", "Holy Grail\n" ] } ], "source": [ "# You can also iterate directly over the lines \n", "# in a file - similar to using 'readline' with\n", "# a while loop as above.\n", "\n", "fmovies = open('movie_titles.txt')\n", "for line in fmovies :\n", " print(line, end='')\n", "fmovies.close()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- It's often more efficient to loop over each line in a file in turn rather reading the whole file into memory with `read` or `readlines`, particularly if the file is large." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The `with` Statement" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- The `with` keyword is useful when dealing with files.\n", "- It enables automatic cleanup actions to be performed.\n", "- In the case of files this simply closes the file.\n", "- Cleanup actions are performed after the block of code following the `with` statement is executed." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "# Open a file using 'with' and write to it.\n", "\n", "with open('movie_titles.txt', 'w') as fmovies :\n", " fmovies.writelines(['The Quest\\n', 'for the\\n', 'Holy Grail\\n'])" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "True\n" ] } ], "source": [ "# Note that the variable f is still in scope, but is\n", "# a closed file.\n", "\n", "print(type(fmovies))\n", "print(fmovies.closed)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- For files, this just means you don't have to remember to always call `f.close()`.\n", "- Cleanup actions are performed even if the code following the `with` statement raises an exception." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "ename": "ValueError", "evalue": "invalid literal for int() with base 10: 'spam'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m# Raise an exception inside a with block\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mopen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'test.txt'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'w'\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mftest\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0mint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'spam'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mValueError\u001b[0m: invalid literal for int() with base 10: 'spam'" ] } ], "source": [ "# Raise an exception inside a with block\n", "with open('test.txt', 'w') as ftest:\n", " int('spam')" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# We see that the file is still closed.\n", "ftest.closed" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- This is particularly useful if dealing with files in loops, as open files can eat up memory, and there's generally a limit on how many files a system can have open at once." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### File Parsing" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- The ease of manipulating files and strings in python makes it ideal for parsing files.\n", "- Eg, parsing the contents of a text file into a `dict`:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "# Lets make a file.\n", "\n", "with open('phonebook.txt', 'w') as fphone :\n", " fphone.write('''Sir Lancelot 2343\n", "Sir Robin 8945\n", "Sir Gallahad 2302''')" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'Sir Lancelot': 2343, 'Sir Robin': 8945, 'Sir Gallahad': 2302}\n" ] } ], "source": [ "# Now open it.\n", "\n", "phonebook = {}\n", "with open('phonebook.txt') as fphone :\n", "\n", " # Loop over lines in the file.\n", " for line in fphone :\n", "\n", " # The 'split' method divides a string into a list\n", " # of words.\n", " splitLine = line.split()\n", " name = splitLine[0] + ' ' + splitLine[1]\n", " number = int(splitLine[2])\n", " phonebook[name] = number\n", "print(phonebook)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- This way you can parse text into python objects which can be used elsewhere in your code.\n", "- The applications of this are limitless!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Data Persistency" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- It's very easy to read and write strings from files, but what about python objects?\n", "- All built-in types support casting to string.\n", "- One way to go is simply to convert to a string and write it to a file in some python readable format.\n", "- There are two methods for converting to strings in python: `str()` and `repr()`.\n", "- Their behaviour can be different:\n", " - `repr` should yield an unambiguous representation of an object which can be understood by the python interpreter.\n", " - `str` yields a string that's more easily read by humans.\n", "- For instance, look at the `datetime.date` class:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "str : 2022-02-02\n", "repr: datetime.date(2022, 2, 2)\n" ] } ], "source": [ "import datetime\n", "\n", "today = datetime.date.today()\n", "\n", "print('str :', str(today))\n", "print('repr:', repr(today))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- By default, `print` uses `str`, while output at the interactive prompt uses `repr`." ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2022-02-02\n" ] } ], "source": [ "print(today)" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "datetime.date(2022, 2, 2)" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "today" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- The `eval` built-in function takes a string and evaluates it with the python interpreter.\n", "- In principle, `eval(repr(obj)) == obj`." ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "eval(repr(today)) == today" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- The string returned by `str` isn't necessarily valid python:" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "ename": "SyntaxError", "evalue": "leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers (, line 1)", "output_type": "error", "traceback": [ "Traceback \u001b[0;36m(most recent call last)\u001b[0m:\n", " File \u001b[1;32m\"/Users/michaelalexander/cernbox/teaching/SUPAPYT/env/lib/python3.9/site-packages/IPython/core/interactiveshell.py\"\u001b[0m, line \u001b[1;32m3418\u001b[0m, in \u001b[1;35mrun_code\u001b[0m\n exec(code_obj, self.user_global_ns, self.user_ns)\n", "\u001b[0;36m File \u001b[0;32m\"\"\u001b[0;36m, line \u001b[0;32m1\u001b[0;36m, in \u001b[0;35m\u001b[0;36m\u001b[0m\n\u001b[0;31m eval(str(today)) == today\u001b[0m\n", "\u001b[0;36m File \u001b[0;32m\"\"\u001b[0;36m, line \u001b[0;32m1\u001b[0m\n\u001b[0;31m 2022-02-02\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers\n" ] } ], "source": [ "eval(str(today)) == today" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- So for data persistency, you should use `repr()` rather than `str()`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- To then save to a file you can do something like:" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [], "source": [ "with open('savethedate.py','w') as fdate :\n", " # The module must import datetime to be able to \n", " # make date objects.\n", " fdate.write('import datetime\\n')\n", " fdate.write('today = ' + repr(today) + '\\n')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Check the contents of `savethedate.py`:" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "import datetime\n", "today = datetime.date(2022, 2, 2)\n", "\n" ] } ], "source": [ "with open('savethedate.py') as fdate :\n", " print(fdate.read())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- As `savethedate.py` is then a python readable file, it can be imported as a module:" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2022-02-02\n", "True\n" ] } ], "source": [ "import savethedate\n", "print(savethedate.today)\n", "print(savethedate.today == today)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- This has the advantage that the saved file is python readable.\n", "- However, the technique of writing files in a python readable manner is prone to error.\n", "- Also, while `repr()` works on all built-in types, it doesn't work on everything.\n", "- For user-defined types you'd need to implement a `__repr__` member method which returns a string representation of the object that can be understood by the python interpreter and will reproduce exactly the same object.\n", "- So a more versatile method is desirable." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Pickling" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- The `pickle` module provides such functionality.\n", "- It can write all built-in and most user defined types to a string or file.\n", "- When using `pickle` to write to a file, it must be opened in binary mode (`'wb'`).\n", "- The `dump` and `dumps` functions can be used to save objects, while `load` and `loads` can be used to retrieve them." ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [], "source": [ "# First write to a file with pickle.dump\n", "\n", "import pickle\n", "\n", "with open('savethedate.pkl', 'wb') as fdate :\n", " pickle.dump(today, fdate)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Check the contents of `savethedate.pkl`:" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "b'\\x80\\x04\\x95 \\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x08datetime\\x94\\x8c\\x04date\\x94\\x93\\x94C\\x04\\x07\\xe6\\x02\\x02\\x94\\x85\\x94R\\x94.'\n" ] } ], "source": [ "with open('savethedate.pkl', 'rb') as fdate :\n", " print(fdate.read())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- You can also dump the pickled object to a string rather than a file using `dumps`.\n", "- This string is what's written to the file when using `dump`." ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "b'\\x80\\x04\\x95 \\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x08datetime\\x94\\x8c\\x04date\\x94\\x93\\x94C\\x04\\x07\\xe6\\x02\\x02\\x94\\x85\\x94R\\x94.'" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "today_str = pickle.dumps(today)\n", "today_str" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Then recover the object with `load` from a file - remember to open in binary mode - or `loads` from a string:" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "True\n" ] } ], "source": [ "# Load from the file.\n", "\n", "with open('savethedate.pkl', 'rb') as fdate :\n", " today_load = pickle.load(fdate)\n", "print(today_load == today)" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "True\n" ] } ], "source": [ "# Load from a string.\n", "\n", "today_load = pickle.loads(today_str)\n", "print(today_load == today)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- For a user defined type:" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [], "source": [ "from PokemonRed.Pokemon import Charmander\n", "\n", "charmander = Charmander(34)\n", "with open('PickledCharmander.pkl', 'wb') as fpickle :\n", " pickle.dump(charmander, fpickle)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- One advantage of pickling is that you don't necessarily need to know the type of the object you're unpickling - the relevant class will be automatically imported (though obviously it must be possible to import the class)." ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Charmander:\n", "- Type: fire\n", "- Level: 34\n", "- Strong against: grass, ice\n", "- Weak against: water, rock\n", "\n", "True\n" ] } ], "source": [ "# Again, open the file in binary mode.\n", "with open('PickledCharmander.pkl', 'rb') as fpickle :\n", " charmander2 = pickle.load(fpickle)\n", "charmander2.print_info()\n", "print(charmander2.level == charmander.level)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- You can dump and load several objects to and from the same file.\n", "- This lets you save, store & send objects in a very generic manner." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Commandline Arguments" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- In addition to reading in data from a file, data can be passed at execution of a script via commandline arguments.\n", "- This is done by following your script name with whatever arguments you want to pass to it:" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "python my_script.py arg1 arg2 arg3 ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Arguments are then stored as a list of strings in the `argv` variable of the `sys` module." ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['/Users/michaelalexander/cernbox/teaching/SUPAPYT/env/lib/python3.9/site-packages/ipykernel_launcher.py', '-f', '/Users/michaelalexander/Library/Jupyter/runtime/kernel-36080b52-e40b-4895-9313-156b65449bcb.json']\n" ] } ], "source": [ "# Check what arguments were passed when the python interpreter\n", "# was started for this notebook\n", "import sys\n", "print(sys.argv)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- We see that when the python interpreter was started for this notebook various arguments were passed to it.\n", "- The first element in `sys.argv` is always the name of the script that was passed to the python interpreter.\n", "- You can access `sys.argv` like you would any other `list`.\n", "- This allows you to write more generic and useful scripts, which can be configured by commandline arguments at execution." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Eg, a simple script to list a directory's contents:" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Overwriting listdir.py\n" ] } ], "source": [ "%%writefile listdir.py\n", "\n", "import os, sys\n", "\n", "print(os.listdir(sys.argv[1]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- This can then be called like:" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['personal', 'Music', '.DS_Store', 'LogBook', 'papers', 'projects', 'Pictures', 'Contacts', '.owncloudsync.log', 'admin', '._sync_fe4d1d92c5ed.db', 'Calibre Library', 'R12PWG', 'presentations', 'stripping', 'teaching', 'dast', 'keys', 'Documents', 'WINDOWS', '.owncloudsync.log.1', 'travel', 'thinking-rock', 'reviews']\r\n" ] } ], "source": [ "!python listdir.py ~/cernbox" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Which prints a list containing the contents of ~/cernbox." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- The `ArgumentParser` class in the [`argparse`](https://docs.python.org/2/library/argparse.html#module-argparse) module provides useful functionality for parsing commandline arguments.\n", "- I use it in almost every script I write!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "----" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "new pet" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exceptions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- We've seen a few types of exception already.\n", "- These are raised when python can't evaluate an expression, and terminates execution of the script." ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "ename": "NameError", "evalue": "name 'roderick' is not defined", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m# Access a variable that doesn't exist\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mroderick\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mNameError\u001b[0m: name 'roderick' is not defined" ] } ], "source": [ "# Access a variable that doesn't exist\n", "print(roderick)" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "ename": "ValueError", "evalue": "invalid literal for int() with base 10: 'a'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m# Convert something that doesn't make sense\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'a'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mValueError\u001b[0m: invalid literal for int() with base 10: 'a'" ] } ], "source": [ "# Convert something that doesn't make sense\n", "int('a')" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "must be real number, not str", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;31m# a number\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mmath\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0mmath\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msqrt\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'a'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mTypeError\u001b[0m: must be real number, not str" ] } ], "source": [ "# Perform maths operations on something that isn't\n", "# a number\n", "import math\n", "math.sqrt('a')" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "ename": "ZeroDivisionError", "evalue": "division by zero", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mZeroDivisionError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m# Divide by zero\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;36m1\u001b[0m\u001b[0;34m/\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mZeroDivisionError\u001b[0m: division by zero" ] } ], "source": [ "# Divide by zero\n", "1/0" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "ename": "SyntaxError", "evalue": "invalid syntax (, line 2)", "output_type": "error", "traceback": [ "\u001b[0;36m File \u001b[0;32m\"\"\u001b[0;36m, line \u001b[0;32m2\u001b[0m\n\u001b[0;31m 'Don't forget to escape!'\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid syntax\n" ] } ], "source": [ "# Forgetting to escape quote marks in strings\n", "'Don't forget to escape!'" ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "ename": "ModuleNotFoundError", "evalue": "No module named 'spam'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m# Import a module that doesn't exist\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;32mimport\u001b[0m \u001b[0mspam\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mModuleNotFoundError\u001b[0m: No module named 'spam'" ] } ], "source": [ "# Import a module that doesn't exist\n", "import spam" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Exceptions are actually instances of exception classes as well.\n", "- Users can thus define their own exception classes.\n", "- All standard exceptions are kept in `builtins`." ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '__IPYTHON__', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'breakpoint', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'display', 'divmod', 'enumerate', 'eval', 'exec', 'filter', 'float', 'format', 'frozenset', 'get_ipython', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']\n" ] } ], "source": [ "# This is the same as dir(__builtins__) like we saw before\n", "import builtins\n", "print(dir(builtins))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- This includes an `Exception` base class. You can inherit from this to define your own exception classes." ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Help on class Exception in module builtins:\n", "\n", "class Exception(BaseException)\n", " | Common base class for all non-exit exceptions.\n", " | \n", " | Method resolution order:\n", " | Exception\n", " | BaseException\n", " | object\n", " | \n", " | Built-in subclasses:\n", " | ArithmeticError\n", " | AssertionError\n", " | AttributeError\n", " | BufferError\n", " | ... and 15 other subclasses\n", " | \n", " | Methods defined here:\n", " | \n", " | __init__(self, /, *args, **kwargs)\n", " | Initialize self. See help(type(self)) for accurate signature.\n", " | \n", " | ----------------------------------------------------------------------\n", " | Static methods defined here:\n", " | \n", " | __new__(*args, **kwargs) from builtins.type\n", " | Create and return a new object. See help(type) for accurate signature.\n", " | \n", " | ----------------------------------------------------------------------\n", " | Methods inherited from BaseException:\n", " | \n", " | __delattr__(self, name, /)\n", " | Implement delattr(self, name).\n", " | \n", " | __getattribute__(self, name, /)\n", " | Return getattr(self, name).\n", " | \n", " | __reduce__(...)\n", " | Helper for pickle.\n", " | \n", " | __repr__(self, /)\n", " | Return repr(self).\n", " | \n", " | __setattr__(self, name, value, /)\n", " | Implement setattr(self, name, value).\n", " | \n", " | __setstate__(...)\n", " | \n", " | __str__(self, /)\n", " | Return str(self).\n", " | \n", " | with_traceback(...)\n", " | Exception.with_traceback(tb) --\n", " | set self.__traceback__ to tb and return self.\n", " | \n", " | ----------------------------------------------------------------------\n", " | Data descriptors inherited from BaseException:\n", " | \n", " | __cause__\n", " | exception cause\n", " | \n", " | __context__\n", " | exception context\n", " | \n", " | __dict__\n", " | \n", " | __suppress_context__\n", " | \n", " | __traceback__\n", " | \n", " | args\n", "\n" ] } ], "source": [ "help(Exception)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- As ever, you can use `help` on any exception for more info." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- In some cases you don't want your code to terminate (at least immediately) on encountering an exception.\n", "- You may want to examine what went wrong, or try something else instead.\n", "- To catch exceptions python has the `try/except` syntax.\n", "- It's fairly similar to that of `if/elif/else`.\n", "- The code block following `try` is first evaluated.\n", "- If this raises an exception, it can be handled by an `except` statement and following block of code.\n", "- An exception type can follow the `except` keyword to handle exceptions of that specific type.\n", "- Any number of `except` statements with different exception types can follow a `try` statement." ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [], "source": [ "def print_inverse(x) :\n", " try :\n", " print(1./x)\n", " # Catch when x is of the wrong type.\n", " except TypeError :\n", " print(x, 'is a', type(x), \\\n", " ', not a number!')\n", " # Catch when x is zero.\n", " except ZeroDivisionError :\n", " print(\"Can't divide by zero!\")" ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.16666666666666666\n" ] } ], "source": [ "# No problems here\n", "print_inverse(6.)" ] }, { "cell_type": "code", "execution_count": 48, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "6. is a , not a number!\n" ] } ], "source": [ "# Here a TypeError is raised and caught by the first\n", "# except statement\n", "print_inverse('6.')" ] }, { "cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Can't divide by zero!\n" ] } ], "source": [ "# Here a ZeroDivisionError is raised and caught by\n", "# the second except statement\n", "print_inverse(0.)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- To catch any exception you can use `except` without a type specified:" ] }, { "cell_type": "code", "execution_count": 50, "metadata": {}, "outputs": [], "source": [ "def print_inverse(x) :\n", " try :\n", " print(1./x)\n", " except :\n", " print(\"That didn't work!\")" ] }, { "cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.16666666666666666\n", "That didn't work!\n", "That didn't work!\n" ] } ], "source": [ "print_inverse(6.)\n", "print_inverse('6.')\n", "print_inverse(0.)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- You can raise an exception with the `raise` keyword followed by an exception instance.\n", "- Most exception classes take a string as argument to the constructor. This is printed when the exception is raised." ] }, { "cell_type": "code", "execution_count": 52, "metadata": {}, "outputs": [], "source": [ "def print_inverse(x) :\n", " if not isinstance(x, (int, float)) :\n", " # The TypeError constructor takes a string \n", " # message that's printed when raised.\n", " raise TypeError('{0} is a {1}, not a number!'\\\n", " .format(x, type(x)))\n", " else :\n", " print(1./x)" ] }, { "cell_type": "code", "execution_count": 53, "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "spam is a , not a number!", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mprint_inverse\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'spam'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;32m\u001b[0m in \u001b[0;36mprint_inverse\u001b[0;34m(x)\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;31m# The TypeError constructor takes a string\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;31m# message that's printed when raised.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 5\u001b[0;31m raise TypeError('{0} is a {1}, not a number!'\\\n\u001b[0m\u001b[1;32m 6\u001b[0m .format(x, type(x)))\n\u001b[1;32m 7\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[0;31mTypeError\u001b[0m: spam is a , not a number!" ] } ], "source": [ "print_inverse('spam')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- The keywords `as`, `finally`, and `else` can also be used when handling exceptions.\n", "- You can also write your own exception classes, normally inheriting from the generic `Exception` built-in class.\n", "- To find out more see [here](http://docs.python.org/3/tutorial/errors.html)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## More Useful Builtin Functionality" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Lambda Methods" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- These allow for slick, inline declaration of simple functions, using the `lambda` keyword." ] }, { "cell_type": "code", "execution_count": 54, "metadata": {}, "outputs": [], "source": [ "# So this:\n", "def inverse(x) :\n", " return 1./x" ] }, { "cell_type": "code", "execution_count": 55, "metadata": {}, "outputs": [], "source": [ "# Becomes this:\n", "inverse = lambda x : 1./x" ] }, { "cell_type": "code", "execution_count": 56, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.125\n" ] } ], "source": [ "# Then call it like any other function\n", "print(inverse(8.))" ] }, { "cell_type": "code", "execution_count": 57, "metadata": {}, "outputs": [], "source": [ "# A lambda function with two arguments:\n", "sum_of_squares = lambda x, y : x**2 + y**2" ] }, { "cell_type": "code", "execution_count": 58, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "25" ] }, "execution_count": 58, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sum_of_squares(3, 4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- The main advantage of this over functions declared with `def` is that they can be declared inline." ] }, { "cell_type": "code", "execution_count": 59, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "25" ] }, "execution_count": 59, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Declare and call a lambda method in the\n", "# same expression:\n", "(lambda x, y : x**2 + y**2)(3, 4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- This is particularly useful when used in conjunction with methods that expect functions as arguments.\n", "- Eg, the `filter` and `map` builtin methods." ] }, { "cell_type": "code", "execution_count": 60, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Help on class filter in module builtins:\n", "\n", "class filter(object)\n", " | filter(function or None, iterable) --> filter object\n", " | \n", " | Return an iterator yielding those items of iterable for which function(item)\n", " | is true. If function is None, return the items that are true.\n", " | \n", " | Methods defined here:\n", " | \n", " | __getattribute__(self, name, /)\n", " | Return getattr(self, name).\n", " | \n", " | __iter__(self, /)\n", " | Implement iter(self).\n", " | \n", " | __next__(self, /)\n", " | Implement next(self).\n", " | \n", " | __reduce__(...)\n", " | Return state information for pickling.\n", " | \n", " | ----------------------------------------------------------------------\n", " | Static methods defined here:\n", " | \n", " | __new__(*args, **kwargs) from builtins.type\n", " | Create and return a new object. See help(type) for accurate signature.\n", "\n" ] } ], "source": [ "help(filter)" ] }, { "cell_type": "code", "execution_count": 61, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[-10, -8, -6, -4, -2, 0, 2, 4, 6, 8, 10]" ] }, "execution_count": 61, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# So, to select even numbers from a sequence, we could do:\n", "\n", "def test(x) :\n", " return x%2 == 0\n", "\n", "list(filter(test, list(range(-10, 11))))" ] }, { "cell_type": "code", "execution_count": 62, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[-10, -8, -6, -4, -2, 0, 2, 4, 6, 8, 10]" ] }, "execution_count": 62, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Or, using a lambda method in just one line:\n", "\n", "list(filter(lambda x : x%2 == 0, list(range(-10, 11))))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Sequence Manipulation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Many other builtin methods are useful for manipulating sequences." ] }, { "cell_type": "code", "execution_count": 63, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]" ] }, "execution_count": 63, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Map one sequence to another, element by element\n", "# using the given method.\n", "\n", "list(map(lambda x : x**2, list(range(10))))" ] }, { "cell_type": "code", "execution_count": 64, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "45" ] }, "execution_count": 64, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Get the sum of elements in a sequence.\n", "\n", "sum(range(10))" ] }, { "cell_type": "code", "execution_count": 65, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3628800" ] }, "execution_count": 65, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Reduce a sequence via recursive method calls.\n", "\n", "from functools import reduce\n", "\n", "reduce(lambda x, y : x * y, list(range(1, 11)))" ] }, { "cell_type": "code", "execution_count": 66, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'The Life of Brian'" ] }, "execution_count": 66, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# sum only works for numerical types, but reduce\n", "# can be used to concatenate sequences of strings.\n", "\n", "from functools import reduce\n", "\n", "reduce(lambda x, y : x + ' ' + y, \n", " ('The', 'Life', 'of', 'Brian'))" ] }, { "cell_type": "code", "execution_count": 67, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'The Life of Brian'" ] }, "execution_count": 67, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Alternatively you can use the str.join method:\n", "\n", "' '.join(('The', 'Life', 'of', 'Brian'))" ] }, { "cell_type": "code", "execution_count": 68, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[(0, 5), (1, 6), (2, 7), (3, 8), (4, 9)]" ] }, "execution_count": 68, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Merge two or more sequences into a list of tuples.\n", "\n", "list(zip(range(5), range(5,10)))" ] }, { "cell_type": "code", "execution_count": 69, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "False\n", "True\n" ] } ], "source": [ "# 'any' and 'all' builtins are useful to test a condition\n", "# on all elements in a sequence.\n", "# 'any' requires any of the elements to evaluate to True\n", "# while 'all' requires all of them to be True\n", "# They're particularly useful in conjunction with 'map'\n", "# along with a test function that returns a boolean.\n", "\n", "l = list(range(10))\n", "print(all(map(lambda x : x < 5, l)))\n", "print(any(map(lambda x : x < 5, l)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Many other useful builtin methods exist.\n", "- See what's available with `import builtins` and `dir(builtins)`, and use `help` to find out more." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### More Ways to Iterate." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- You can unpack as you iterate:" ] }, { "cell_type": "code", "execution_count": 70, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 5\n", "1 6\n", "2 7\n", "3 8\n", "4 9\n" ] } ], "source": [ "pairs = list(zip(range(5), range(5,10)))\n", "for a, b in pairs :\n", " print(a, b)" ] }, { "cell_type": "code", "execution_count": 71, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 5\n", "1 6\n", "2 7\n", "3 8\n", "4 9\n" ] } ], "source": [ "# Which is equivalent to :\n", "for elm in pairs :\n", " a, b = elm[0], elm[1]\n", " print(a, b)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Sequences can also be constructed using the `for` syntax." ] }, { "cell_type": "code", "execution_count": 72, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']" ] }, "execution_count": 72, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# For a list:\n", "\n", "list(str(i) for i in range(10))" ] }, { "cell_type": "code", "execution_count": 73, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(0, 1, 2, 0, 1, 2, 0, 1, 2, 0)" ] }, "execution_count": 73, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Also for tuples:\n", "\n", "tuple(i%3 for i in range(10))" ] }, { "cell_type": "code", "execution_count": 74, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]" ] }, "execution_count": 74, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# For lists this can also be put in square brackets\n", "# for the same result.\n", "\n", "[i**2 for i in range(10)]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- You can also add an optional `if`:" ] }, { "cell_type": "code", "execution_count": 75, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(0, 2, 4, 6, 8)" ] }, "execution_count": 75, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tuple(i for i in range(10) if i%2 == 0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- So python provides many slick ways of constructing and iterating over sequences!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- For more ways of using fast, memory efficient iteration check out [generator expressions and methods](http://docs.python.org/2/tutorial/classes.html#generators)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### OS Interface" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- The `os` module provides lots of functionality for communicating with the operating system, eg:\n", " - `listdir` - list a directory's contents.\n", " - `mkidr` and `makedirs` - make a directory/directories.\n", " - `getcwd`, `chdir` - get or change the current working directory.\n", " - `fchmod`, `fchown`- change a file's permissions & ownership.\n", "- The `os.path` submodule is great for file path manipulations.\n", " - `exists`, `isdir`, `islink` - check if a file exists, is a directory or a link.\n", " - `abspath` - get the absolute path of a file.\n", " - `split` - split directory and file name parts of a path.\n", " - `join` - join file/directory names, adding separators as needed." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- The `subprocess` module allows you to call system commands.\n", " - `call` - call a command and wait for it to complete.\n", " - `Popen` - call a command in the background." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- As ever, you can find out more with `dir` and `help`.\n", "- Many other useful modules and methods exist - find them with a quick web search!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## A Few Tips" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Before you write anything, think through how to structure your code.\n", "- Don't reinvent the wheel - code may already exist that fits your needs (Google is always a programmer's friend). \n", "- Break the problem into its most basic components and write/use a class or function for each.\n", "- Don't copy & paste. Use functions, classes, modules & packages to reuse code. \n", "- Avoid hard coding anything - make it configurable, ie, an argument to a function/class constructor.\n", "- Always annotate and add doc strings!\n", "- Use version control like [github](https://github.com/).\n", "- Don't be afraid to rewrite if necessary - better that than continue to build on poorly structured code. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Further Reading" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Official python homepage - [www.python.org](http://www.python.org)\n", " - Tutorial: [docs.python.org/3/tutorial](http://docs.python.org/3/tutorial/)\n", "\n", "- Jupyter - [jupyter.org](http://jupyter.org/)\n", " - Built on [`ipython`](http://ipython.org/) - the enhanced interactive python interpreter.\n", " - Lots of handy additional functionality built on the standard python interpreter.\n", " - Notebook interface, like we've been using." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- PyPI - [pypi.python.org/pypi](https://pypi.python.org/pypi)\n", " - The Python Package Index. Official repository of 3rd party python packages.\n", " - A huge number of packages for all purposes are available.\n", " - You can use the python package manager, [pip](https://pypi.python.org/pypi/pip) to easily install packages from PyPI (though if you're already using a different package manager for your python install stick with that).\n", " - Use of `pip` is shown in the [installation instructions](https://mannymoo.github.io/IntroductionToPython/SUPAPYT-Installation-Instructions.html#Adding-packages-to-your-installation).\n", "- Eg, `uncertainties` - [pypi.python.org/pypi/uncertainties](https://pypi.python.org/pypi/uncertainties)\n", " - An extremely useful package for handling the propagation of uncertainties, as is often performed in scientific analyses." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- NumPy - [www.numpy.org](http://www.numpy.org/) \n", " - Mathematical package.\n", " - Not Standard Library, but does come as part of most python installs.\n", " - Arrays & matrices, linear algebra, fourier transforms ...\n", "- MatPlotLib - [www.matplotlib.org](http://www.matplotlib.org)\n", " - 2D plotting package for publication quality figures.\n", "- SciPy - [www.scipy.org](http://scipy.org/)\n", " - SciPy package for scientific computing - numerical integration, optimisation ...\n", " - Contains Sympy package for symbolic maths.\n", " - Also includes NumPy and MatPlotLib.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- PyX - [https://pyx-project.org](https://pyx-project.org/)\n", " - Produce PDF and PostScript documents.\n", " - Latex integration.\n", "- Sage - [www.sagemath.org](http://www.sagemath.org)\n", " - Free, open source Mathematica/Maple/Matlab alternative with a python interface.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Software carpentry - [software-carpentry.org](http://software-carpentry.org/)\n", " - Some excellent resources on building and using software (generally not python specific, though there is a [python tutorial](http://swcarpentry.github.io/python-novice-inflammation/)).\n", " - There's also a [SUPA course](https://my.supa.ac.uk/course/view.php?id=400) which I suggest you enrol on (and is based on python).\n", " \n", " " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- And many others! \n", "- Python has a very rich and active community, which is yours to exploit!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# The End" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[Happy Pythonising!](https://www.youtube.com/watch?v=T8XeDvKqI4E)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "([Home](https://mannymoo.github.io/IntroductionToPython/))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.1" } }, "nbformat": 4, "nbformat_minor": 1 }