Python for Mathematicians

Video

Introduction

Python, a relatively new programming language (only a few months older than I am), has taken the world by storm - especially the scientific and mathematical world. While specialized tools and institutional knowledge exist in C, C++, Fortran, R, and Matlab - and while languages with more specialized philosophies such as Julia, Go, and whatever toy project your office mate is working on do have advantages - Python increasingly dominates the scientific computing world.

This comes in large part from the philosophy of Python. Included in every Python install is the statement (printed by the meta statement import this):

  1. Beautiful is better than ugly.
  2. Explicit is better than implicit.
  3. Simple is better than complex.
  4. Complex is better than complicated.
  5. Flat is better than nested.
  6. Sparse is better than dense.
  7. Readability counts.
  8. Special cases aren't special enough to break the rules.
  9. Although practicality beats purity.
  10. Errors should never pass silently.
  11. Unless explicitly silenced.
  12. In the face of ambiguity, refuse the temptation to guess.
  13. There should be one -- and preferably only one -- obvious way to do it.
  14. Although that way may not be obvious at first unless you're Dutch.
  15. Now is better than never.
  16. Although never is often better than right now.
  17. If the implementation is hard to explain, it's a bad idea.
  18. If the implementation is easy to explain, it may be a good idea.
  19. NameSpaces are one honking great idea -- let's do more of those!

Put simply, the goal of Python is to make it easy for you to:

  • Do what you want/need to do
  • Read code (your own and others)

And it turns out that those are two very important principles. (Compare the philosophy of Perl - summarized, "To replicate all of the expressivity (and ambiguity) of human language" - to see how wrong you can get this fundamental first step.)

We are going to be using Python 3.7+ in this course. If you do not have a modern version of Python on your system, then you should likely install one - go back to day 1 for advice on that. If you wish to use an older version of Python, translation is up to you.

I highly recommend Python 3.8 for mathematicians moving forward. Installation is a bit trickier, but the language is undergoing good improvements that make me excited for 3.9 and beyond.

Hello World

A classic of computing, and a common benchmark for learning a language, is the "Hello World" - a minimal script that prints text in some way to the user.

Console

In your local bash, type ipython - short for "Interactive Python" - and hit enter.

This will launch an interactive python shell. To exit it, simply run the command exit.

In this console, you can enter Python line by line and see the results in (close to) real time.

We are going to learn about our first datatype - the string - and our first function - print.

In Python, the vast majority of the text you are going to manipulate is held in objects called strings. When you write them, they are contained in "" or '' characters. (In Python, unlike some other languages, these quotes are pretty much interchangeable - the main difference being you can use one style of quotes within the other, without escaping; 'I "am" here' is the same in Python as "I \"am\" here".)

In Python, functions are going to be your main tool and interface. You call them with their name and open parentheses - in this case, run the command

print("Hello World!")

and observe the result.

Python Files

Exit out of the interactive session, and create a new file titled helloworld.py. You can run python on any file, and it will do its best to interpret it, but file extensions help the Operating System know what you intend to do with the file - in this case, .py means it is a file to run in python.

Another hint you can give the operating system is called a "shebang", which stands for the #! characters at the start of the line. This tells unix-based systems what you would like to execute the file with if you don't specify - and is a good way to communicate to people reading what version(s) of Python your program works in. In this case, we want to run it with python 3, so we tell it:

#!/usr/bin/env python3
print("Hello World!")

Now open bash in the same directory and run the command:

python helloworld.py

and it will successfully run.

Python Within Atom

Back on day 1, we installed the python package jupyter and the atom package Hydrogen. These allow us to run snippets of python code right within Atom.

Go back to your helloworld.py file in atom, and click on the print command. Press the key combination ctrl-enter. The first time you do this each session, it will spin up a new interactive python session in the background, connect Atom to it. This lets you run your files - or small portions of them - at your own speed, which is very useful for writing new programs one bit at a time, iterating on ideas, figuring out when something goes wrong, or just keeping track of what is happening.

You should see it print you a "Hello World" in the Atom window. Congratulations!

Datatypes

We have already talked briefly about the string, but there are a few other core datatypes you will need to learn. We divide them into two broad categories:

Values

type example notes
bool True Equivalent to a 1 or 0 (depending on truthfullness), but more explicit.
int 21 Arbitrary precision integer - slow with really big numbers
float 21.0 Floating precision - like in science class, keep track of how many digits you can trust. Be afraid of subtraction.
str "21" 0 or more characters. Unicode - the number of characters might not match the length, especially when dealing with many Asian languages, some diacritical marks, and Emoji.

Containers

type example 1 example 2 notes
list [1,2,3] list((1,2,3)) Your basic collection of objects, you can add to it and remove things from it. A great workhorse of Python programming.
tuple (1,2,3) tuple([1,2,3]) "Immutable" - once it is created, can never be modified, only replaced. Great for performance improvements over list, or when you want to protect your variable.
set {1,2,3} set([1,2,3]) A fairly simple one for mathematicians, sets contain elements without replacement. Supports unions, intersections, and a variety of other useful mathematical behaviors.
str "30" str(30) Strings are also containers, containing little strings. You can often treat them like a list of 1-length str objects.
dict {1:"a",2:1.5} dict([[1,"a"],[2,1.5]]) The python version of the "key-value" store, dictionaries are a remarkable and potent tool that some programming languages are built completely around. However, the keys must be "hashable" - for you, this mostly means they must be "immutable" - dictionaries and lists are not allowed. Notably, dictionaries as values is very common.

Get a feel for creating each of these.

Values can be accessed from (most) of these with keys; for the ordered containers list and tuple, the keys are their integers, 0-indexed. For example, "Bananas"[0] returns B. ["a","b","c"][1] returns b.

In dictionaries, the same notation can be used with the set key- {"a":1}["a"] returns 1.

Variables

Python, like many (but not all) programming languages, has variables.

variables are defined with a single = sign:

message = "Hello World"
print(message)

Unlike many languages, you don't initialize variables or tell python what types they are. This is because python is "Dynamically Typed" - variables can be assigned any type - even multiple different types over their life. That's why python has the type function; for example, type(2) returns int.

Sometimes you have one datatype - say, a string - and you want an integer int. That's where "typecasting" comes in. int("3") returns 3, allowing you to turn a string which represents an integer into its integer value.

Variables - when used for more than a line or two - should be given descriptive names. Remember that you - and others - will not be reading the program top to bottom in depth; you will read the parts that you are working on, or that are relevant to you. Give your future self a break and tell them what's up. (Especially, avoid ambiguous-looking names such as l, I, or O.)

Python style suggests descriptive variable names in lowercase, with words connected by underscores. Follow this unless you have a very compelling reason not to - such as implementing an algorithm with well-known names for the variables, or contributing to a code base with a different style.

Operators

There are a number of functions that are very common, and as such are assigned operators - single characters which perform the function.

We are hopefully already familiar with operators - and their relationship to functions - from our mathematical backgrounds, so I will skip over the theory of it. Python supports prefix, infix, and postfix operators of various kinds.

Comparators

A very useful class of operators are comparators. They are mostly infix operators that return booleans.

operator example notes
== a==1 Note the double equal sign. Value equality - e.g. 1.0 == 1 is True.
> a>1
>= a>=1 Should be equivalent to (a>1) or (a==1)
< a<1 Should be equivalent to 1>a
<= a<=1 Should be equivalent to (a<1) or (a==1)
not not True Negates - that is, returns the opposite "Truthiness". Everything in Python has a "Truthiness", and sometimes an unintuitive one - in general, negate booleans.
!= a != b A dedicated operator for the common "not equal" check. Almost always equivalent to not (a == b).
is a is 1 Tests for "reference equality". Unless you know what you are doing, this will trick you. Consider the following threee snippets: a=1;b=1;a is b is True. 999 is 999 is True. a=999;b=999;a is b is False. Never use this for value comparisons. If you want to compare types - check whether something is the integer 1 - check type and value separately.

There are two operators that, on the surface, you might think belong in this set - or and and. Certainly you can use them on top of these operators to achieve the value, but their behaviors are a bit different than you might expect.

a or b returns a if a is Truthy, otherwise b. For example, "" or "Bananas" returns "Bananas".

a and b returns b if a is Truthy, otherwise a. "" or "Bananas" returns "".

Arithmetic Operators

operator example notes
- -a A "negation" of a.
* a*b A "Product" of a and b. Often commutative, but not always.
+ a+b A "Sum" of a and b. Usually commutative.
- a-b A "Difference" of a and b - usually equivalent to a+(-b).
** a**b An "Exponentiation" of a to the power b (in math, $a^b$). Note that this does NOT use the caret ^ - due to old programming conventions, that is a lower-level operator not used by most programmers.
/ a/b A division operator $\dfrac ab $. Notably, produces floats from ints - for example, type(6/3) returns float. We will cover precise rationals as we move into sympy.
// a//b An integer division operator $\left \lfloor \dfrac ab \right \rfloor $. type(6//3) returns int - and 7//3 returns the integer 2. However, if either input is a float, the output will be a float as well - one that takes an integer value. 7.0//3 is the float 2.0.
% a%b Modulo operator; for positive int/float b, returns a value c such that 0 \leq c < b, and $c\equiv a \mod b$.

These operators behave mostly as expected for us, but some of them have defined - and useful, if surprising - behaviors on other types. for example, [1,2]*3 returns [1,2,1,2,1,2]. Play around, and when using them, be careful you are working with the types you think you are.

Operator Precedence

Python, like mathematics, has its own operator precedence. This operator precedence, for mathematical operators, is very familiar to mathematicians - but Python also allows parentheses so you do not have to be ambiguous.

Whenever it is unclear, use parentheses even when the operator precedence would do the right thing. This helps you write it without error and read it. If you have to look up operator precedence, this is a good sign you should not rely on it.

Input

For today's assignment, we are going to discuss one more builtin function:

input("A Message")

This collects input from the user, prompting them with the given message.

This is particularly useful for pausing when a human decision needs to be made - yes/nos, continues, and getting some human help when cleaning up a dataset. Many people also use it for starting values in computations, though those can also be passed in as command line arguments.

Defining Your Own Functions

We have discussed two built in functions, but the essence of modern programming is writing complex behaviors in your own functions.

Function names should be like variable names - a communication to yourself in the future about what they do. For example,

def print_hello_world():
    print("Hello World")

print_hello_world()

Note that the body of the function is indented by 4 spaces. This is how Python knows what is in the function and isn't.

Python functions can also take parameters. Placing a variable name in the function declaration, you can then use it in the body:

def print_hello_world(message):
    print(message)

print_hello_world("Hello World")

defaults

This is possibly less useful - right now, analagous to a restricted version of the print function - but we can do both functions in one with Default Values.

def print_hello_world(message="Hello World"):
    print(message)

print_hello_world()
print_hello_world("Goodnight World, See You Tomorrow")

Return Values

Functions are particularly useful for performing computations and returning just what is important. Variables defined within functions are discarded after - meaning you can just keep what is most important.

The function returns whatever is on the line with the return expression.

def isqrt(n):
    return int(sqrt(n))

This is a good example of a function with an "obvious" way to do it - presented above - that ends up not being the fastest reasonable way. Implementing it as a function allows us to easily change it to a more efficient version later - so long as it returns the correct value, the implementation doesn't matter to the result when we use it. (there is a good implementation in the math library.)

As mathematicians, the concept of abstracting complicated computations behind functions - and not worrying so much about them, allowing you to read and write complex expressions - should be familiar.

Docstrings

One last thing to mention is the docstring. We will cover it more in the future, but it is a good idea to write down the desired behavior of a function - often before you try to write it out.

I favor Numpy Style, which is a verbose but readable and fairly complete explanation.

Let's add one to this isqrt function:

def isqrt(n):
    """Compute the Integer Square Root of n.

    Parameters
    ----------
    n : a positive integer.

    Returns
    -------
    s : The integer square root of n

    Raises
    ------
    ValueError
        If the input cannot be interpreted as a nonnegative integer.

    Examples
    --------
    >>> isqrt(5)
    2
    """
    return int(sqrt(n))

This is a good reminded of what the parameters do - and can be accessed at any time after definition with the command:

help(isqrt)

Worksheet

Today's worksheet will cover the creation of a first python function.

Department of Mathematics, Purdue University
150 N. University Street, West Lafayette, IN 47907-2067
Phone: (765) 494-1901 - FAX: (765) 494-0548
Contact the Webmaster for technical and content concerns about this webpage.
Copyright© 2018, Purdue University, all rights reserved.
West Lafayette, IN 47907 USA, 765-494-4600
An equal access/equal opportunity university
Accessibility issues? Contact the Web Editor (webeditor@math.purdue.edu).