# IPython Notebook Test

## Intro to Python¶

### The Basics¶

This is a text cell. It's for writing text using markdown, including

• pictures
• mathematical equations in $\LaTeX$, which is pronounced laytec!
• tables
• bullets
• numbered
• lists

Here is a picture (hovering over it will show the title):

Here is a mathematical equation: $$E_k = \frac{m_0 c^2}{\sqrt{1 - \frac{v^2}{c^2}}} - m_0 c^2$$

Here is a table:

a b a XOR b
0 0 0
0 1 1
1 0 1
1 1 0

You'll find a nice cheatsheet for markdown using this link.

Here's a numbered list:

1. Milk
2. Eggs

And here's the code for the previous cell:

Markdown
# Intro to Python
## The Basics

This is a **text** cell. It's for writing text using *markdown*, including

- pictures
- mathematical equations in $\LaTeX$, which is pronounced laytec!
- tables
- bullets
- numbered
- lists

Here is a picture (hovering over it will show the title):
![python powered logo from python.org](https://www.python.org/static/community_logos/python-powered-w-200x80.png '"Python powered" logo')

Here is a mathematical equation: $$E_k = \frac{m_0 c^2}{\sqrt{1 - \frac{v^2}{c^2}}} - m_0 c^2$$

Here is a table:

| a | b | a XOR b |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |

Here's a numbered list:

1. Milk
2. Eggs
3. Bread

This tutorial uses Python 3. Most of the code will not run under Python 2.

OK, Let's look at our first piece of python code. The following code cell has a well known Python easter egg that will summarizes the philospohy behind Python.

# This is a code cell

# Any text after a #, is a comment it's not part of the code.
# Comments are a nice way to document directly in the source code

# The following line is code!
import this                    # Behold, the ZEN OF PYTHON!

The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!


All Python code should generally follow the The Python Style Guide, or pep8 as it's commonly known. pep8 was designed to follow the zen above. Pep8 is taken seriously and if you want people to admire your code you should stick with it. There are tools for helping you with code formatting including Pylint, flake8, and AutoPep8. IDE's like PyCharm also provide real-time pep8 imspection and suggestions.

Python groups blocks of code using indenting. Spaces, not tabs, are strongly recommended (mixing spaces and tabs will cause problems). Some editors (and Jupyter notebooks) will insert 4 spaces at the beginning of the line when you hit the tab key, correctly indenting your code for Python.

The Python interpreter doesn't actually care about the number of blank lines but pep8 has recommendations for spacing that should be followed to consistency and readability.

The following code uses demonstrates the use of indents. Don't worry about the code itself at this point but notice the indents that start after the lines beginning def and for.

The line reversed_string += letter.upper() is the only one within the for block as the return line following it is unindented back a level.

Only the print statement at the end is not part of the block that begins with the def line.

def reverse_caps(my_string):
'''A simple function to reverse a string. Not the best way to do this!'''

# Get the length of the input string
string_length = len(my_string)

reversed_string = ""

# Loop over the reversed string
for letter in reversed(my_string):
# Add the uppercase version of the current letter to the end of reversed_string
reversed_string += letter.upper()

# Return our reversed, uppercase string
return reversed_string

# Print out a reversed, capitalized string
print(reverse_caps("Here is a long string!"))

!GNIRTS GNOL A SI EREH


#### Variables¶

Python uses a technique called duck-typing. If it looks like a duck, and it quacks like a duck, then it must be a duck! With duck-typing, it's not necessary to specify the type of a variable, Python will infer it. With that said, Python variables have the following main types:

String:

name = "Jamie"           # Put the string Jamie into the variable name


Integer:

i = 11                   # Put the integer 11 into the variable i
j = -1                   # Put the integer -1 into the variable j


Float:

pi = 3.1415729           # Put the floating point value 3.14... into the variable pi


#### Operators¶

Operators perform operations between values and/or variables. With numeric values mathematical operators are common:

n = i + 1               # Add 1 to i and put the result in n
half_pi = pi / 2        # Divide the variable pi by 2 and put the result in half_pi
c_squared = c**2        # Square c and put the result in c_squared


halfway_string = "half" + "way"   # concatenate half and way and store the result,
# halfway, in halfway_string

# Replace '?' with some integer values
# a = ?
# b = ?
a = 2
b = 3

# Add a and b and store the result in c
# c = ?
c = a + b

# This will print the result out using a python f-string
print(f"c = {c}")

c = 5


There are many ways to manage variables within strings in python (concatenation, .format(), etc) but we will stick with the newly introduced f-strings for this tutorial because they are easiest to read.

#### Flow Control¶

##### Conditionals¶

The if statement allows you to branch your code.

# If statements
name = "John"
age = 17

if name == "John" and age < 18:               # Notice that the operator for comparing equality is == not =
print("Hi John, I recognize you!")
seen_young_john = True
else:                                         # this will run is the statement is not true
seen_young_john = False                   # set the seen variable to False so the variable
#    exists even if young John has not been seen

if seen_young_john:
print()
print("John was here today.")

Hi John, I recognize you!

John was here today.

##### Loops¶

The for and while statements allow you to loop around a block many times.

# For loop
hello_string = "Hello World"
for character in hello_string:
if character == 'o':
print('-', end='')
else:
print(character, end='')
print()

Hell- W-rld

# While loop
print("\nFibonacci sequence: ")
count = 0
n1 = 1
n2 = 1
while count < 9:
print(n1,end=', ')
nth = n1 + n2
# update values
n1 = n2
n2 = nth
count += 1
print(n1)

Fibonacci sequence:
1, 1, 2, 3, 5, 8, 13, 21, 34, 55


#### Lists, Tuples and Dictionaries¶

A list is a variable with a sequenced collection of values.

my_int_list = [1, 2, 3]
my_string_list = ["A", "string", "list"]


You can access individual elements of a list using the element number in (square) brackets. Element numbers start at zero:

print(my_int_list[2])            # will print 3
print(my_string_list[0])     # will print A
my_int_list[1] = 10              # my_list is now [1, 10, 3]


A tuple is also a sequenced collection of values but the tuple cannot be changed i.e. tuple are immutable:

my_float_tuple = (1.2, 3.45, 7, 9.9)
my_string_tuple = ("yes", "no")
print(my_float_tuple[1])     # will print 3.45
my_string_tuple[1] = "ok"    # will error


A dictionary is a key-value store. It allows you to link a value to a key much like a paper dictionary does for words.

math_constant = {'pi': 3.1415, 'e': 2.718}
print(math_constant['pi'])                   # prints 3.1415
print(math_constant.keys[0])                 # prints pi
print(math_constant.values[1])               # prints 2.718

my_int_list = [1, 2, 3]
my_string_list = ["A", "string", "list"]

print(my_int_list[2])            # will print 3
print(my_string_list[0])     # will print A
my_int_list[1] = 10              # my_list is now [1, 10, 3]

my_float_tuple = (1.2, 3.45, 7, 9.9)
my_string_tuple = ("yes", "no")
print(my_float_tuple[1])     # will print 3.45

my_nested_list = [              # We can split anything between brackets, parens or braces over multiple lines
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
# Reference a nested list using [row][column]
print(my_nested_list[1][0])

3
A
3.45
4

# my_string_tuple[1] = "ok"    # will error - Uncomment to see the error

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-8-155fcb807504> in <module>()
----> 1 my_string_tuple[1] = "ok"    # will error - Uncomment to see the error

TypeError: 'tuple' object does not support item assignment
math_constant = {'pi': 3.1415, 'e': 2.718}
print(f"pi = {math_constant['pi']}, e = {math_constant['e']}")    # prints pi = 3.1415, e = 2.718

pi = 3.1415, e = 2.718


#### Ranges and Slices¶

The range() function is often used in for loops at it iterates up to a value or over a range. If there's only one parameter it's the stop value and values in the range stop before it and the start value will be 0. If there are two parameters then they are the start and the stop. If there are three parameters then they are start, stop and step.

Slicing is used in list references. A slice, as with a range, can potentially have a start a stop and an end. These are separated by a :. Slices will work with lists and tuples but not dictionaries.

# Print integers 0 to 4
print("Iterating over range(5):")
for i in range(5):
print(i)

print()
# Print integers 1 to 5
print("Iterating over range(1, 6):")
for i in range(1, 6):
print(i)

print()
# Print integers 0 to 4, in steps of 2
print("Iterating over range(0, 5, 2):")
for i in range(0, 5, 2):
print(i)

print()
# Print integers 4 to 0 in steps of -1
print("Iterating over range(5, -1, -1):")
for i in range(4, -1, -1):
print(i)

Iterating over range(5):
0
1
2
3
4

Iterating over range(1, 6):
1
2
3
4
5

Iterating over range(0, 5, 2):
0
2
4

Iterating over range(5, -1, -1):
4
3
2
1
0

print(f"range(10) = {range(10)}  This is a range object. We'll have to convert it to a list to see the sequence.\n")
my_list = list(range(10))                  # Note we have to convert the range object to a list
print(f"my_list = {my_list}")
print("\nSome Slices")
print("==== ======")
# :Stop - Up to Stop
print(f"my_list[:5] = {my_list[:5]}")
# Start: - From Start up
print(f"my_list[5:] = {my_list[5:]}")
# Start:Stop
print(f"my_list[1:5] = {my_list[1:5]}")
print(f"my_list[5:7] = {my_list[5:7]}")
# Start:Stop:Step
print(f"my_list[1:7:2] = {my_list[1:7:2]}")
print(f"my_list[7:1:-2] = {my_list[7:1:-2]}")
# Use minus with Start: or :Stop to count back from the end
print(f"my_list[:-3] = {my_list[:-3]}")
print(f"my_list[-3:] = {my_list[-3:]}")

range(10) = range(0, 10)  This is a range object. We'll have to convert it to a list to see the sequence.

my_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Some Slices
==== ======
my_list[:5] = [0, 1, 2, 3, 4]
my_list[5:] = [5, 6, 7, 8, 9]
my_list[1:5] = [1, 2, 3, 4]
my_list[5:7] = [5, 6]
my_list[1:7:2] = [1, 3, 5]
my_list[7:1:-2] = [7, 5, 3]
my_list[:-3] = [0, 1, 2, 3, 4, 5, 6]
my_list[-3:] = [7, 8, 9]


#### Functions¶

Functions are a way of reusing code. They can have inputs (parameters) and outputs (return values). The parameters work similarly to variables within the function. We've already seen an example of a function above in reverse_caps().

def hello(name_param, greeting="Hello"):
print(f"{greeting} {name_param}!")

name_var = "World"

hello(name_var)

# Nice! OK, let's reuse the function with a different name

name_var = "John"

hello(name_param=name_var)           # You can optionally specify the parameter by name

# And one more for kicks

name_var = 1010.01   # This is floating point so we don't need quotes

hello(name_var)

def ab_plus_one_div_2(num1, num2):
return (num1 * num2 + 1) / 2      # Note operator precedence multiplies before adding but we need
# parentheses to ensure that the division happens after the addition

print(f"(4 * 5 + 1) / 2 = {ab_plus_one_div_2(4, 5)}")

# hello function returning a string rather than printing one

def hello(name_param):
return f"Hello {name_param}!"

result = hello("xyzzy")                 # This time we pass though a constant string

print(result)

Hello World!
Hello John!
Hello 1010.01!
(4 * 5 + 1) / 2 = 10.5
Hello xyzzy!


#### Packages¶

Packages add new functions, classes, data and more. There's a vast amount of packages out there for all kinds of applications. There's a whole lot in just the The Python Standard Library. We'll be using the packages we've already downloaded for these tutorials but many more are available through Anaconda using the conda command or PyPI using the pip command. Some packages may require compiling for C code when they're installed from source. In order to install these you need a basic C development environment on your machine.

Once you get comfortable with Python you can write your own packages but there's lots to learn as packaging is a complex topic.

To use a package you'll have to import it.

# The simplest import statement
import math

print(f"5! = {math.factorial(5)}")

# or (we can )
import math as mt

print(f"5! = {mt.factorial(5)}")

# or (this works nicely as long as you don't have functions from different packages with the same name)
from math import factorial
print(f"5! = {factorial(5)}")

# or, and this is a little dodgier
from math import factorial as fac

print(f"5! = {fac(5)}")

print()

# It's also possible to import more than one package in a single import
import math, statistics as st

my_list = [math.factorial(0), math.factorial(1), math.factorial(2),
math.factorial(3), math.factorial(4), math.factorial(5)]
print(f"my_list = {my_list}")
print(f"mean(my_list) = {st.mean(my_list)}")
print(f"median(my_list) = {st.median(my_list)}")

5! = 120
5! = 120
5! = 120
5! = 120

my_list = [1, 1, 2, 6, 24, 120]
mean(my_list) = 25.666666666666668
median(my_list) = 4.0


#### Further explorations¶

We've only scratched the surface here. Other things you can do with python include:

• File handling
• Decorators
• List comprehensions
• Generators
• Classes and object-oriented programming
• Networking with python (scraping, requests, servers)

Check out: