# IPython & Jupyter Notebook introduction¶

IPython is a Python interpreter with a few additional, useful features and can often be used instead of the "python" command. To start an IPython session, simply type `ipython` into your console.

Jupyter notebooks (or better, the newer Jupyter Lab) is a web interface to IPython with a few additional extras.

To start them, type `jupyter notebook` or `jupyter lab` (maybe not yet installed in the venv) respectively.

## Cells and execution¶

Jupyter notebooks/lab works with cells. Each cell can have a type, either markdown (for text as this one) or code (or raw, but that's advanced).

A cell can be executed with (default) Shift + Enter while Alt + Enter executes the current cell and adds an empty cell below.

### Arbitrary order and statefulness¶

From the moment the first cell is executed, we're inside one Python session. So all the definitions and states are remembered and are only reset by restarting the kernel. The cells can also be executed in an arbitrary order and multiple times. This can be tricky with stateful objects when a cell is executed multiple times. Let's look at it.

In [2]:

    # FIRST, EXECUTE CELL BELOW
    # otherwise raises error
    print(a)

    1

In [1]:

    # Execute first
    a = 1

This can be tricky with stateful objects such as lists. Make sure to reset this objects or just restart the kernel.

In [4]:

    # creating the list
    list1 = []

In [5]:

    # Execute this cell multiple times. List will grow
    list1.append(42)
    print(list1)

    [42]

## Help functionality and tab completion¶

`?` command returns the docstring of the corresponding method, class, etc.

In [6]:

    dict?

`?` can also be applied to instances

In [9]:

    letters = {"a": 1, "b": 2}

    letters?

`help(.)` gives the full documentation of the object and its methods.

In [8]:

    help(dict)

    Help on class dict in module builtins:

    class dict(object)
     |  dict() -> new empty dictionary
     |  dict(mapping) -> new dictionary initialized from a mapping object's
     |      (key, value) pairs
     |  dict(iterable) -> new dictionary initialized as if via:
     |      d = {}
     |      for k, v in iterable:
     |          d[k] = v
     |  dict(**kwargs) -> new dictionary initialized with the name=value pairs
     |      in the keyword argument list.  For example:  dict(one=1, two=2)
     |
     |  Built-in subclasses:
     |      StgDict
     |
     |  Methods defined here:
     |
     |  __contains__(self, key, /)
     |      True if the dictionary has the specified key, else False.
     |
     |  __delitem__(self, key, /)
     |      Delete self[key].
     |
     |  __eq__(self, value, /)
     |      Return self==value.
     |
     |  __ge__(self, value, /)
     |      Return self>=value.
     |
     |  __getattribute__(self, name, /)
     |      Return getattr(self, name).
     |
     |  __getitem__(...)
     |      x.__getitem__(y) <==> x[y]
     |
     |  __gt__(self, value, /)
     |      Return self>value.
     |
     |  __init__(self, /, *args, **kwargs)
     |      Initialize self.  See help(type(self)) for accurate signature.
     |
     |  __ior__(self, value, /)
     |      Return self|=value.
     |
     |  __iter__(self, /)
     |      Implement iter(self).
     |
     |  __le__(self, value, /)
     |      Return self<=value.
     |
     |  __len__(self, /)
     |      Return len(self).
     |
     |  __lt__(self, value, /)
     |      Return self<value.
     |
     |  __ne__(self, value, /)
     |      Return self!=value.
     |
     |  __or__(self, value, /)
     |      Return self|value.
     |
     |  __repr__(self, /)
     |      Return repr(self).
     |
     |  __reversed__(self, /)
     |      Return a reverse iterator over the dict keys.
     |
     |  __ror__(self, value, /)
     |      Return value|self.
     |
     |  __setitem__(self, key, value, /)
     |      Set self[key] to value.
     |
     |  __sizeof__(...)
     |      D.__sizeof__() -> size of D in memory, in bytes
     |
     |  clear(...)
     |      D.clear() -> None.  Remove all items from D.
     |
     |  copy(...)
     |      D.copy() -> a shallow copy of D
     |
     |  get(self, key, default=None, /)
     |      Return the value for key if key is in the dictionary, else default.
     |
     |  items(...)
     |      D.items() -> a set-like object providing a view on D's items
     |
     |  keys(...)
     |      D.keys() -> a set-like object providing a view on D's keys
     |
     |  pop(...)
     |      D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
     |
     |      If the key is not found, return the default if given; otherwise,
     |      raise a KeyError.
     |
     |  popitem(self, /)
     |      Remove and return a (key, value) pair as a 2-tuple.
     |
     |      Pairs are returned in LIFO (last-in, first-out) order.
     |      Raises KeyError if the dict is empty.
     |
     |  setdefault(self, key, default=None, /)
     |      Insert key with a value of default if key is not in the dictionary.
     |
     |      Return the value for key if key is in the dictionary, else default.
     |
     |  update(...)
     |      D.update([E, ]**F) -> None.  Update D from dict/iterable E and F.
     |      If E is present and has a .keys() method, then does:  for k in E: D[k] = E[k]
     |      If E is present and lacks a .keys() method, then does:  for k, v in E: D[k] = v
     |      In either case, this is followed by: for k in F:  D[k] = F[k]
     |
     |  values(...)
     |      D.values() -> an object providing a view on D's values
     |
     |  ----------------------------------------------------------------------
     |  Class methods defined here:
     |
     |  __class_getitem__(...) from builtins.type
     |      See PEP 585
     |
     |  fromkeys(iterable, value=None, /) from builtins.type
     |      Create a new dictionary with keys from iterable and values set to value.
     |
     |  ----------------------------------------------------------------------
     |  Static methods defined here:
     |
     |  __new__(*args, **kwargs) from builtins.type
     |      Create and return a new object.  See help(type) for accurate signature.
     |
     |  ----------------------------------------------------------------------
     |  Data and other attributes defined here:
     |
     |  __hash__ = None

### Tab completion¶

IPython supports tab completion giving the list of available functions or for letters function letters description of the function singature. Just start to type what you want and press Tab.

In [ ]:

    pri  # press TAB to make this "print"

In [ ]:

    p  # press TAB to show all the commands starting with "p"

## Run scripts and use system shell commmands¶

`!` allows to invoke system shell commands.

In [10]:

    !ls

    Lecture_ipython.ipynb script.py

## Magic function¶

IPython comes with a set of functions that can be called in a command-line style. `%` runs the line as a command, `%%` the whole cell

Some examples of magic functions

### Running Python scripts¶

With `%run <filename>` we can invoke python scripts in an IPython shell

In [11]:

    %run script.py

    Hello, I am a script!

### Loading Python scripts¶

`%load <filename>` loads the content of the file into the cell

In [14]:

    # Uncomment the line below to execute it
    # %load script.py

### Timing¶

The commands `time` (single execution) and `timeit` (many executions, more accurate) measure the execution time (single and statistical evaluation)

In [15]:

    %time a = 1

    CPU times: user 2 µs, sys: 1 µs, total: 3 µs
    Wall time: 3.81 µs

In [16]:

    %%time
    for _ in range(int(1e6)):
        pass

    CPU times: user 39.1 ms, sys: 1.53 ms, total: 40.6 ms
    Wall time: 39.9 ms

user refers to the time spent to execute the command, sys refers to the time spent in the kernel

In [17]:

    %timeit a = 1

    10.2 ns ± 0.364 ns per loop (mean ± std. dev. of 7 runs, 100,000,000 loops each)

In [18]:

    %%timeit
    for _ in range(int(1e6)):
        pass

    21 ms ± 1.2 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

### Storing return values¶

We can also store the output of the magic commands in a variable

In [19]:

    script_out = %run script.py

    Hello, I am a script!

In [20]:

    print(script_out)  # there is no return value!

    None

In [21]:

    dict_timing = %time dict(a=1,b=2)

    CPU times: user 2 µs, sys: 0 ns, total: 2 µs
    Wall time: 4.05 µs

In [22]:

    dict_timing

Out[22]:

    {'a': 1, 'b': 2}

## Debugging¶

IPython allows to do interactive debugging using the `%%debug` magic command.

You can use the usual debug commands for navigation

In [ ]:

    %%debug
    a = 3
    for _ in range(a):
        a += 1
    print("End")

    NOTE: Enter 'c' at the ipdb>  prompt to continue execution.
    > <string>(2)<module>()

    ipdb> p a
    6

## Other languages¶

There is builtin magic for other programming languages.

For example Perl, LaTex and R

In [24]:

    %%perl
    use strict;

    my $a = 5;
    while($a > 0) {
        print "$a ";
        $a--;
    }
    print "\n";

    5 4 3 2 1

In [25]:

    %%perl
    # Function definition
    sub Hello {
       print "Hello, World!\n";
    }
    Hello()

    Hello, World!

In [18]:

    %%latex
    $$a=1+\gamma$$
    Some text
    \begin{eqnarray}
    a^2+b^2 &=&c^2\\
    x&=&y
    \end{eqnarray}

$$a=1+\gamma$$ Some text \begin{eqnarray} a^2+b^2 &=&c^2\\ x&=&y \end{eqnarray}
In [2]:

    # Needs rpy2 installed
    %load_ext rpy2.ipython

In [8]:

    %%R
    library(lattice)
    attach(mtcars)

    # scatterplot matrix
    splom(mtcars[c(1,3,4,5,6)], main="MTCARS Data")

    The following objects are masked from mtcars (pos = 3):

        am, carb, cyl, disp, drat, gear, hp, mpg, qsec, vs, wt

    The following objects are masked from mtcars (pos = 4):

        am, carb, cyl, disp, drat, gear, hp, mpg, qsec, vs, wt

In [7]:

Out[7]:

    6

### Documentation¶

Markdown text can be used to document your work. HTML and custom tags allow you to structure your text and explain your `code`, for example what a list comprehension is `squares = [n**2 for n in numbers]`.
And even LaTeX code can be used in markdown cells: $\nabla f = (\partial_x f,\partial_y f,\partial_z f)$