Introduction to Jupyter notebooks and Python

This is a quick introduction to Jupyter notebooks and Python. It combines key elements from several open source introductory courses on Jupyter and Python for scienfitic computing. Previous experience with programming is assumed (Elementary experience with MATLAB, R, etc. or C, C++, etc. is fine).

Author: Nicolai Riis, DTU Compute, July 2017.

Each notebook takes roughly 1 hour to go through (reading + exercises).

Consider skipping segments that are familiar to you and using the notebooks as refence during the course.

List of notebooks:

1) Introduction to Jupyter notebooks and Python – (Current)

2) Introduction to NumPy

3) Introduction to Matplotlib

4) Debugging and more

Licence:

These notebooks are released under the Attribution 3.0 Unported Licence (https://creativecommons.org/licenses/by/3.0/). This means you are free to share and adapt the content as you please as long as you give appropriate credit, link the licence and indicate if changes are made to the material.

For the original content that these notebooks are based upon please see:

Contact for errors:

nabr@dtu.dk


 

Table of contents

1.1 The Jupyter notebook interface

1.2 The IPython kernel

1.3 Quick introduction to Python

1.4 Exercises

1.5 Addendum: Markdown and Latex


 

1.1 The Jupyter notebook interface

In this course, you will be using the Jupyter Notebook as a toolkit for producing reproducable and replicable work in scientific computing.

A notebook consists of a series of cells. For example, this text is in what is called a “Markdown cell”. The following cell is a “code cell”:

# this is a code cell

You can tell what the type of a cell is by selecting the cell, and looking at the toolbar at the top of the page. For example, try clicking on this cell. You should see the cell type menu displaying “Markdown”, like this:

png

Command mode and edit mode

In the notebook, there are two modes: edit mode and command mode. By default the notebook begins in command mode. In order to edit a cell, you need to be in edit mode.

When you are in command mode, you can press enter to switch to edit mode. The outline of the cell you currently have selected will turn green, and a cursor will appear.
When you are in edit mode, you can press escape to switch to command mode. The outline of the cell you currently have selected will turn gray, and the cursor will disappear.

Markdown cells

For example, a markdown cell might look like this in command mode (Note: the following few cells are not actually cells – they are images and just look like cells! This is for demonstration purposes only.)

png

Then, when you press enter, it will change to edit mode:

png

Now, when we press escape, it will change back to command mode:

png

However, you’ll notice that the cell no longer looks like it did originally. This is because IPython will only render the markdown when you tell it to. To do this, we need to “run” the cell by pressing Ctrl-Enter, and then it will go back to looking like it did originally:

png

Code cells

For code cells, it is pretty much the same thing. This is what a code cell looks like in command mode (again, the next few cells LOOK like cells, but are just images):

png

If we press enter, it will change to edit mode:

png

And pressing escape will also go back to command mode:

png

If we were to press Ctrl-Enter like we did for the markdown cell, this would actually run the code in the code cell:

png

Executing cells

Code cells can contain any valid Python code in them (We give a short introduction to Python in Section 1.3). When you run the cell, the code is executed and any output is displayed.

You can execute cells with Ctrl-Enter (which will keep the cell selected), or Shift-Enter (which will select the next cell).

Try running the following cell and see what it prints out:

print("Printing cumulative sum from 1-10:")
total = 0
for i in range(1, 11):
    total += i
    print("Sum of 1 to " + str(i) + " is: " + str(total))
print("Done printing numbers.")

You’ll notice that the output beneath the cell corresponds to the print statements in the code. Here is another example, which only prints out the final sum:

total = 0
for i in range(1, 11):
    total += i
print(total)

Another way to print something out is to have that thing be the last line in the cell. For example, we could rewrite our example above to be:

total = 0
for i in range(1, 11):
    total += i
total

However, this will not work unless the thing to be displayed is on the last line. For example, if we wanted to print the total sum and then a message after that, this will not do what we want (it will only print “Done computing total.”, and not the total sum itself).

total = 0
for i in range(1, 11):
    total += i
total
print("Done computing total.")

If you are accustomed to Python 2, note that the parentheses are obligatory for the print function in Python 3.


 

1.2 The IPython kernel

When you first start a notebook, you are also starting what is called a kernel. This is a special program that runs in the background and executes Python code. Whenever you run a code cell, you are telling the kernel to execute the code that is in the cell, and to print the output (if any).

Just like if you were typing code at the Python interpreter, you need to make sure your variables are declared before you can use them. What will happen when you run the following cell? Try it and see:

a

The issue is that the variable a does not exist. We must make sure a is declared first (for example, you could set the value of a to 5 – or pick whatever value you want). Note the “=” sign is used for assigning a value. Once you have modified the above cell, you should be able to run the following cell (if you haven’t modified the above cell, you’ll get the same error!):

print("The value of 'a' is: " + str(a))

Running the above cell should work, because a has now been declared. To see what variables have been declared, you can use the %whos command:

%whos

If you ran the summing examples from the previous section, you’ll notice that total and i are listed under the %whos command. That is because when we ran the code for those examples, they also modified the kernel state.

(Note that commands beginning with a percent (%) or double percent (%%) are special IPython commands called magics. They will only work in IPython.)

Restarting the kernel

It is generally a good idea to periodically restart the kernel and start fresh, because you may be using some variables that you declared at some point, but at a later point deleted that declaration.

Your code should always be able to work if you run every cell in the notebook, in order, starting from a new kernel.

To test that your code can do this, first restart the kernel by clicking the restart button:

png

Then, run all cells above this one in the notebook in order by choosing Cell$\rightarrow$Run All Above from the menu above. (while having this cell selected in command mode)

There are many keyboard shortcuts for the notebook. To see a full list of these, go to Help$\rightarrow$Keyboard Shortcuts.
To learn a little more about what things are what in the IPython Notebook, check out the user interface tour, which you can access by going to Help$\rightarrow$User Interface Tour.

 

1.3 Quick Introduction to Python

The code you ran from the previous section was written in the Python programming language.

What is Python?

Python is a modern, general-purpose, object-oriented, high-level programming language.

General characteristics of Python:

  • clean and simple language: Easy-to-read and intuitive code, easy-to-learn minimalistic syntax, maintainability scales well with size of projects.
  • expressive language: Fewer lines of code, fewer bugs, easier to maintain.

Technical details:

  • dynamically typed: No need to define the type of variables, function arguments or return types.
  • automatic memory management: No need to explicitly allocate and deallocate memory for variables and data arrays. No memory leak bugs.
  • interpreted: No need to compile the code. The Python interpreter reads and executes the python code directly.

Advantages:

  • The main advantage is ease of programming, minimizing the time required to develop, debug and maintain the code.
  • Well designed language that encourage many good programming practices:
    • Modular and object-oriented programming, good system for packaging and re-use of code. This often results in more transparent, maintainable and bug-free code.
    • Documentation tightly integrated with the code.
  • A large standard library, and a large collection of add-on packages.

Disadvantages:

  • Since Python is an interpreted and dynamically typed programming language, the execution of python code can be slow compared to compiled statically typed programming languages, such as C and Fortran.
  • Somewhat decentralized, with different environment, packages and documentation spread out at different places. Can make it harder to get started.

Modules

Most of the functionality in Python is provided by modules. The Python Standard Library is a large collection of modules that provides cross-platform implementations of common facilities such as access to the operating system, file I/O, string management, network communication, and much more.

To use a module in a Python program it first has to be imported. A module can be imported using the import statement. For example, to import the module math, which contains many standard mathematical functions, we can do:

import math

This includes the whole module and makes it available for use later in the program. For example, we can do:

x = math.cos(2 * math.pi)

print(x)

Note: Make sure you are running the code examples above (recall command: Shift+Enter).

Looking at what a module contains, and its documentation

Once a module is imported, we can list the symbols it provides using the dir function:

import math

print(dir(math))

And using the function help we can get a description of each function (almost .. not all functions have docstrings, as they are technically called, but the vast majority of functions are documented this way).

help(math.log)
math.log(10)
math.log(10, 2)

Assignment

As you have already seen, the assignment operator in Python is =. Python is a dynamically typed language, so we do not need to specify the type of a variable when we create one.

Assigning a value to a new variable creates the variable:

# variable assignments
x = 1.0

Although not explicitly specified, a variable does have a type associated with it. The type is derived from the value that was assigned to it.

type(x)

Try changing x in the code cells above to an integer or bool (True,False) and check the type again.

Type casting

We can “cast” a type from one to another as follows.

x = 1.5

print(x, type(x))
x = int(x) #Change float to int (rounds down)

print(x, type(x))

This is used alot when printing, casting everything to a string (str). If you remove the str( ) around the x, then the print function will fail. (See the section on print futher down)

print("The value of x is: " + str(x))

Operators and comparisons

Most operators and comparisons in Python work as one would expect:

  • Arithmetic operators +, -, *, /, // (integer division), ‘**’ power
1 + 2, 1 - 2, 1 * 2, 1 // 2
1.0 + 2.0, 1.0 - 2.0, 1.0 * 2.0, 1.0 / 2.0

Note: The / operator always performs a floating point division in Python 3.x.
This is not true in Python 2.x, where the result of / is always an integer if the operands are integers.
to be more specific, 1/2 = 0.5 (float) in Python 3.x, and 1/2 = 0 (int) in Python 2.x (but 1.0/2 = 0.5 in Python 2.x).

  • The boolean operators are spelled out as the words and, not, or.
True and False
not False
  • Comparison operators >, <, >= (greater or equal), <= (less or equal), == equality, is identical.
2 > 1, 2 < 1
# equality
[1,2] == [1,2]
# objects identical?
l1 = l2 = [1,2]

l1 is l2

Compound types: Strings, List and dictionaries

Strings

Strings are the variable type that is used for storing text messages.

s = "Hello world"
type(s)
# length of the string: the number of characters
len(s)

We can index a character in a string using []:

s[0]

Heads up MATLAB users: Indexing start at 0!

We can extract a part of a string using the syntax [start:stop], which extracts characters between index start and stop -1 (the character at index stop is not included):

s[0:5]
s[4:5]

If we omit either (or both) of start or stop from [start:stop], the default is the beginning and the end of the string, respectively:

s[:5]
s[6:]

We can also define the step size using the syntax [start:end:step] (the default value for step is 1, as we saw above):

s[::1]
s[::2]

This technique is called slicing. Read more about the syntax here: http://docs.python.org/release/2.7.3/library/functions.html?highlight=slice#slice

Python has a very rich set of functions for text processing. See for example http://docs.python.org/2/library/string.html for more information.

String formatting examples

print("str1", "str2", "str3")  # The print statement concatenates strings with a space
print("str1", 1.0, False, -1j)  # The print statements converts all arguments to strings
print("value = %f" % 1.0)       # we can use C-style string formatting
# alternative, more intuitive way of formatting a string
s3 = 'value1 = {0}, value2 = {1}'.format(3.1415, 1.5)

print(s3)

List

Lists are very similar to strings, except that each element can be of any type.

The syntax for creating lists in Python is [...]:

l = [1,2,3,4]

print(type(l))
print(l)

We can use the same slicing techniques to manipulate lists as we could use on strings:

print(l)

print(l[1:3])

print(l[::2])

Python lists can be inhomogeneous and arbitrarily nested:

nested_list = [1, [2, [3, [4, [5]]]]]

nested_list
s
s# convert a string to a list by type casting:
s2 = list(s)

s2

Adding, inserting, modifying, and removing elements from lists

# create a new empty list
l = []

# add an elements using `append`
l.append("A")
l.append("d")
l.append("d")

print(l)

We can modify lists by assigning new values to elements in the list. In technical jargon, lists are mutable.

l[1] = "p"
l[2] = "q"

print(l)

Remove first element with specific value using ‘remove’

l.remove("A")

print(l)

Remove an element at a specific location using del:

del l[1]

print(l)

Tuples

Tuples are like lists, except that they cannot be modified once created, that is they are immutable.

In Python, tuples are created using the syntax (..., ..., ...), or even ..., ...:

point = (10, 20)

print(point, type(point))

We can unpack a tuple by assigning it to a comma-separated list of variables:

x, y = point

print("x =", x)
print("y =", y)

If we try to assign a new value to an element in a tuple we get an error:

point[0] = 20
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-114-ac1c641a5dca> in <module>()
----> 1 point[0] = 20

TypeError: 'tuple' object does not support item assignment

Dictionaries

Dictionaries are also like lists, except that each element is a key-value pair. The syntax for dictionaries is {key1 : value1, ...}:

params = {"parameter1" : 1.0,
          "parameter2" : 2.0,
          "parameter3" : 3.0,}

print(type(params))
print(params)
print("parameter1 = " + str(params["parameter1"]))
print("parameter2 = " + str(params["parameter2"]))
print("parameter3 = " + str(params["parameter3"]))
params["parameter1"] = "A"
params["parameter2"] = "B"

# add a new entry
params["parameter4"] = "D"

print("parameter1 = " + str(params["parameter1"]))
print("parameter2 = " + str(params["parameter2"]))
print("parameter3 = " + str(params["parameter3"]))
print("parameter4 = " + str(params["parameter4"]))

Control Flow

Conditional statements: if, elif, else

The Python syntax for conditional execution of code uses the keywords if, elif (else if), else:

statement1 = False
statement2 = False

if statement1:
    print("statement1 is True")

elif statement2:
    print("statement2 is True")

else:
    print("statement1 and statement2 are False")

For the first time, here we encounted a peculiar and unusual aspect of the Python programming language: Program blocks are defined by their indentation level.

Compare to the equivalent C code:

if (statement1)
{
    printf("statement1 is True\n");
}
else if (statement2)
{
    printf("statement2 is True\n");
}
else
{
    printf("statement1 and statement2 are False\n");
}

In C blocks are defined by the enclosing curly brakets { and }. And the level of indentation (white space before the code statements) does not matter (completely optional).

But in Python, the extent of a code block is defined by the indentation level (usually a tab or say four white spaces). This means that we have to be careful to indent our code correctly, or else we will get syntax errors.

Examples:

statement1 = statement2 = True

if statement1:
    if statement2:
        print("both statement1 and statement2 are True")
# Bad indentation!
if statement1:
    if statement2:
    print("both statement1 and statement2 are True")  # this line is not properly indented

Try fixing the indentation in the above code cell so it runs without error

statement1 = False

if statement1:
    print("printed if statement1 is True")

    print("still inside the if block")
if statement1:
    print("printed if statement1 is True")

print("now outside the if block")

Loops

In Python, loops can be programmed in a number of different ways. The most common is the for loop, which is used together with iterable objects, such as lists. The basic syntax is:

for loops:

for x in [1,2,3]:
    print(x)

The for loop iterates over the elements of the supplied list, and executes the containing block once for each element. Any kind of list can be used in the for loop. For example:

for x in range(4): # by default range start at 0
    print(x)

Note: range(4) does not include 4 !

for x in range(-3,3):
    print(x)
for word in ["scientific", "computing", "with", "python"]:
    print(word)

To iterate over key-value pairs of a dictionary:

params.items() #Gives a dict of key, value pairs.
for key, value in params.items():
    print(key + " = " + str(value))

List comprehensions: Creating lists using for loops:

A convenient and compact way to initialize lists:

l1 = [x**2 for x in range(0,5)]

print(l1)

while loops:

i = 0

while i < 5:
    print(i)

    i = i + 1

print("done")

Note that the print("done") statement is not part of the while loop body because of the difference in indentation.

Functions

A function in Python is defined using the keyword def, followed by a function name, a signature within parentheses (), and a colon :. The following code, with one additional level of indentation, is the function body.

def func0():   
    print("test")
func0()

Optionally, but highly recommended, we can define a so called “docstring”, which is a description of the functions purpose and behaivor. The docstring should follow directly after the function definition, before the code in the function body.

def func1(s):
    """
    Print a string 's' and tell how many characters it has    
    """

    print(s + " has " + str(len(s)) + " characters")
help(func1)
func1("test")

Functions that returns a value use the return keyword:

def square(x):
    """
    Return the square of x.
    """
    return x ** 2
square(4)

We can return multiple values from a function using tuples (see above):

def powers(x):
    """
    Return a few powers of x.
    """
    return x ** 2, x ** 3, x ** 4
powers(3)
x2, x3, x4 = powers(3)

print(x3)

Default argument and keyword arguments

In a definition of a function, we can give default values to the arguments the function takes:

def myfunc(x, p=2, debug=False):
    if debug:
        print("evaluating myfunc for x = " + str(x) + " using exponent p = " + str(p))
    return x**p

If we don’t provide a value of the debug argument when calling the the function myfunc it defaults to the value provided in the function definition:

myfunc(5)
myfunc(5, debug=True)

If we explicitly list the name of the arguments in the function calls, they do not need to come in the same order as in the function definition. This is called keyword arguments, and is often very useful in functions that takes a lot of optional arguments.

myfunc(p=3, debug=True, x=7)

Classes

Classes are the key features of object-oriented programming. A class is a structure for representing an object and the operations that can be performed on the object.

In Python a class can contain attributes (variables) and methods (functions).

A class is defined almost like a function, but using the class keyword, and the class definition usually contains a number of class method definitions (a function in a class).

  • Each class method should have an argument self as its first argument. This object is a self-reference.
  • Some class method names have special meaning, for example:
class Point:
    """
    Simple class for representing a point in a Cartesian coordinate system.
    """

    def __init__(self, x, y):
        """
        Create a new Point at x, y.
        """
        self.x = x
        self.y = y

    def translate(self, dx, dy):
        """
        Translate the point by dx and dy in the x and y direction.
        """
        self.x += dx
        self.y += dy

    def __str__(self):
        return("Point at [%f, %f]" % (self.x, self.y))

To create a new instance of a class:

p1 = Point(0, 0) # this will invoke the __init__ method in the Point class

print(p1)         # this will invoke the __str__ method

To invoke a class method in the class instance p:

p2 = Point(1, 1)

p1.translate(0.25, 1.5)

print(p1)
print(p2)

Note that calling class methods can modifiy the state of that particular class instance, but does not effect other class instances or any global variables.

That is one of the nice things about object-oriented design: code such as functions and related variables are grouped in separate and independent entities.

Exceptions

In Python errors are managed with a special language construct called “Exceptions”. When errors occur exceptions can be raised, which interrupts the normal program flow and fallback to somewhere else in the code where the closest try-except statement is defined.

To generate an exception we can use the raise statement, which takes an argument that must be an instance of the class BaseException or a class derived from it.

raise Exception("description of the error")

A typical use of exceptions is to abort functions when some error condition occurs, for example:

def my_function(arguments):

    if not verify(arguments):
        raise Exception("Invalid arguments")

    # rest of the code goes here

To gracefully catch errors that are generated by functions and class methods, or by the Python interpreter itself, use the try and except statements:

try:
    # normal code goes here
except:
    # code for error handling goes here
    # this code is not executed unless the code
    # above generated an error

For example:

try:
    print("test")
    # generate an error: the variable test is not defined
    print(test)
except:
    print("Caught an exception")

To get information about the error, we can access the Exception class instance that describes the exception by using for example:

except Exception as e:

To get information about the error, we can access the Exception class instance that describes the exception by using for example:

except Exception as e:

Further reading


 

1.4 Exercises

At the end of each notebook there will be a few coding assignments. To evaulate the assignments we provide automated test that your code must pass. These will be in one or more cells after the code you need to write. The tests are meant as a guide to check that your implementation is correct and may not catch all errors. In the following exercise, there is one test cell after the cell where you should put your answer.

Exercise 1: Hello (1 point)

Implement the function hello and make sure the test cells runs without any errors. You will need to delete the line with `raise NotImplementedError`, write your own solution, and then re-run the cell before running the test cell. Each time you change code in a cell, you will need to re-run that cell before running any other cells that depend on it!
def hello(name):
    """Returns a message containing "Hello, <name>!",
    where <name> is passed in as an argument.

    Parameters
    ----------
    name : string
        The name of the person to say hello to

    Returns
    -------
    the message containing "Hello, <name>!"

    """

    return "Hello, " + name + "!"

# try running your hello function with your own name and see what
# it returns
hello("YOUR NAME HERE")
# Hint: if the test cell is not passing, but your function seems
# to be showing the right thing, make sure you are actually
# returning a value from your function! You should be using
# `return`, not `print`. For example, this cell should display:
#
#     Your function returned: Hello, Reverend Bayes!
#
# and not:
#
#     Hello, Reverend Bayes!
#     Your function returned: None

message = hello("Reverend Bayes")
print("Your function returned: " + str(message))

When completing a problem set, it is often useful to supplement the autograder tests with test cases of your own. Below, we provide you with a “scratch” cell to be used for your own testing purposes.

Keep in mind, however, that writing your own test cases is **completely optional** — these are only here to help you as you complete the assignment. This means that you will *not* be graded on anything you include in this cell!

# add your own test cases in this cell!

"""(1 point) Test code for the 'hello' function. This cell should NOT give any errors when it is run."""
from nose.tools import assert_equal
assert_equal(hello("Jessica"), "Hello, Jessica!")
assert_equal(hello("jessica"), "Hello, jessica!")
assert_equal(hello("Tom"), "Hello, Tom!")
assert_equal(hello("123"), "Hello, 123!")

print("Success!")

Exercise 2: Cosine function for lists

Unfurtuantly the cosine function from the math library is not able to accept lists as input. So if we wanted to get the cosine of say the numbers in the list [0,12 Pi, Pi], we would have to input each element one at a time!

Pi = math.pi
cosines = math.cos([0,1/2*Pi,Pi])
Implement the function cos_list to output the cosine of each real number from an input list of real numbers and make sure the test cells runs without any errors. You will need to delete the line with `raise NotImplementedError`, write your own solution, and then re-run the cell before running the test cell. Each time you change code in a cell, you will need to re-run that cell before running any other cells that depend on it!
def cos_list(l):
    """Returns a list with the cosine of each element an input list,
    where the list is passed in as an argument "l".

    Parameters
    ----------
    l : list (non-empty) of real numbers, i.e., [5] or [5,3.14] or [1,9.2,-15]

    Returns
    -------
    A list with the cosines of each real number in l.

    """
    import math

    if type(l) is not list:
        raise Exception("Input is not a list")
    if not l:
        raise Exception("Input list is empty")
    for x in l:
        if (type(x) is not float) and (type(x) is not int):
            raise ValueError("Input list nust only contain real numbers")
    cosines = []
    for x in l:
        cosines.append(math.cos(x))
    return cosines

# add your own test cases in this cell!

"""(2 points) Test of results for the 'cos_list' function. This cell should NOT give any errors when it is run."""
from nose.tools import assert_equal
assert_equal([round( elem, 4) for elem in cos_list([0,1,3])],[1.0, 0.5403, -0.99])
assert_equal([round( elem, 4) for elem in cos_list([math.pi,1/2*math.pi])],[-1.0, 0.0])
assert_equal([round( elem, 4) for elem in cos_list([37])],[0.7654])
assert_equal([round( elem, 4) for elem in cos_list([9.13,-15,0.9])],[-0.9569, -0.7597, 0.6216])

print("Success!")
Consider improving the cos_list function to throw exceptions if it gets unexpected input. To do this, make sure to check if the input is actually a list and that it contains real numbers.

That is if input is not a list:

raise Exception("Input is not a list")

and if input is an empty list:

raise Exception("Input list is empty")

and if input list contains anything else than real numbers (float or integer):

raise ValueError("Input list must only contain real numbers")
"""(1 point) Test input checking for the 'cos_list' function. This cell should NOT give any errors when it is run."""
from nose.tools import assert_equal, assert_raises
assert_raises(Exception, cos_list, 5)
assert_raises(Exception, cos_list, math.pi)
assert_raises(Exception, cos_list, "hello")
assert_raises(ValueError, cos_list, [0,"hello"])
assert_raises(ValueError, cos_list, ["hello"])
assert_raises(ValueError, cos_list, [[3.14]])

print("Success!")
"""(1 point) Test input checking for the 'cos_list' function. This cell should NOT give any errors when it is run."""
from nose.tools import assert_equal, assert_raises
assert_raises(Exception, cos_list, [])

print("Success!")

Exercise 3: Building a class for persons with (name,sex,age)

Create a class called Person that contains the name, age and sex of a person.

A person should be added as follows:

p1 = Person(name='Billy', age=12, sex='M')

which should return an object p1, with p1.name=‘Billy, p1.age=12, p1.sex=’M’.

The class should be able to handle missing inputs by assigning them the “None” type.
That is if:

p2 = Person(name='Billy', sex='M')

then p2.age should return None.

If the sex is initalized as ’M’ for male, but no name is set, then the name should be defaulted to ‘John Doe’.
Similarly, if the sex is ‘F’, the name should default to ‘Jane Doe’ if no name is set.

Finally, print(p1) should print

Name: Billy, Age: 12, Sex: M.
class Person:
    """
    A class representing a person from their name, age and sex.    

    Init example:
    p1 = Person(name='Billy', age=12, sex='M')

    outputs:
    print(p1)
        Name: Billy, Age: 12, sex: M.

    """


    def __init__(self, name=None, age=None, sex=None):
        """
        Init person
        """
        self.sex = sex
        self.age = age
        self.name = name
        self.relatives = []

        if self.name is None:
            if self.sex is "F":
                self.name = 'Jane Doe'
            elif self.sex is "M":
                self.name = 'John Doe'

    def add_relative(self,person):
        if type(person) is Person:
            self.relatives.append(person)
        else:
            raise Exception('Trying to add someone that is not the Person class, ignoring')


    def show_relatives(self):
        for p in self.relatives:
            print(p)


    def __str__(self):
        return("Name: {}, Age: {}, Sex: {}.".format(self.name, self.age, self.sex))


# add your own test cases in this cell!

"""(2 points) Test input checking for the 'Person' Class. This cell should NOT give any errors when it is run."""
from nose.tools import assert_equal, assert_raises
test_p = Person(name='Bob', age=15, sex='M')
assert_equal(test_p.name, 'Bob')
assert_equal(test_p.age,15)
assert_equal(test_p.sex,'M')


test_q = Person(age=15, sex='M')
assert_equal(test_q.name, 'John Doe')
assert_equal(test_q.age,15)
assert_equal(test_q.sex,'M')

test_w = Person(sex='F')
assert_equal(test_w.name, 'Jane Doe')
assert_equal(test_w.age,None)
assert_equal(test_w.sex,'F')

test_x = Person()
assert_equal(test_x.name, None)
assert_equal(test_x.age,None)
assert_equal(test_x.sex,None)

assert_equal(test_p.__str__(),"Name: Bob, Age: 15, Sex: M.")

print("Success!")
Improve the Person class by adding the notion of relatives. Specifically add two new methods
add_relatives and show_relatives.

A persons relatives should be added as follows:

p1 = Person(name='Billy', age=12, sex='M')
p2 = Person(name='Charlie', age=37, sex='M')
p3 = Person(name='Alice', age=32, sex='F')
p1.add_relative(p2)
p1.add_relative(p3)

and shown by printing the result of the print statement for each relative.

p1.show_relatives()

should print

Name: Charlie, Age: 37, Sex: M.
Name: Alice, Age: 32, Sex: F.
# add your own test cases in this cell!
"""(2 points) Test input checking for the 'Person' Class. This cell should NOT give any errors when it is run."""
from nose.tools import assert_equal, assert_raises
import sys
from io import StringIO
test_p = Person(name='Bob', age=15, sex='M')
test_q = Person(age=15, sex='M')
test_w = Person(sex='F')

test_p.add_relative(test_q)
test_p.add_relative(test_w)

saved_stdout = sys.stdout
try:
    out = StringIO()
    sys.stdout = out
    test_p.show_relatives()
    output = out.getvalue().strip()
    assert output == 'Name: John Doe, Age: 15, Sex: M.\nName: Jane Doe, Age: None, Sex: F.'
finally:
    sys.stdout = saved_stdout

print("Success!")

 

1.5 Addendum: Markdown and Latex

As mentioned in the previous problem, Markdown is a special way of writing text in order to specify formatting, like whether text should be bold, italicized, etc.

You can use the following website as a reference for Markdown: https://help.github.com/articles/markdown-basics

  • Hint #1: after editing the Markdown, you will need to run the cell so that the formatting appears.
  • Hint #2: try selecting this cell so you can see what the Markdown looks like when you’re editing it. Remember to run the cell again to see what it looks like when it is formatted.

One of the advantages of using Markdown is that it allows us to easily write equations using LaTeX.

You should be able to find most of the symbols you need on this page. Alternatively, if you forget the mathematical operation that a particular symbol corresponds to, check the Wikipedia page on mathematical notation.

Basic equation formatting

To format an equation using LaTeX, you must wrap the text in dollar signs, $like this$. (or double dollar signs for centered $$like this$$)

png

Inspect the markdown below by entering edit mode in the cell and have a look at the formatting.

$$F(y)=\int_{-\infty}^y \frac{1}{\sqrt{2\pi\sigma^2}}e^{\frac{-(x-\mu)^2}{2\sigma^2}}dx$$