What python is

Python is an easy to learn, yet powerful programming language. In general, programming languages can be divided into low-level and high-level languages.
In a low-level language you have to tell the computer very detailed and specific what to do. These very specific commands can be executed by your computer quickly, because you tell it exactly what to do. A high-level programming language builds on top of a low-level language. You as programmer no longer have to deal with this low-level stuff. This makes programming very easy and comfortable. However, in the end your code will probably be slower compared to a code written in a low-level language.
As a beginner, you probably don't care about execution speed too much. A high-level language like python is perfect to get started. However, please don't get the impression: "Python is just something for beginners." Python is a widespread programming language used in basically all fields of computation such as science, industry or web applications.

How to use a jupyter notebook

You are currently looking at a jupyter notebook. These notebooks are one of the possibilities how to write and execute python code. A jupyter notebook consists of separate code cells. A cell can be executed either by clicking on the play button in the tool bar on top or by pressing shift + enter.

Execute the cell below!


In [ ]:
1 + 1

As you can see you can use python like a simple calculator. You can use the operators +, -, \*, / and parentheses () just like you would expect for a normal calculator. The python symbol to calculate powers is \*\*.

Use the next cell to calculate the square of 5-3 in python!


In [ ]:

Variables

For more complex calculations it is convenient to store numbers as variables. In principle, any combination of letters, numbers, and underscores which is not starting with a number can be used as a variable name. For readability purposes, it is recommended to use lowercase words separated by underscores as variable names. The = sign is used to assign a value to a variable.

Execute the following example!


In [ ]:
my_first_variable = 10
my_second_variable = 5.34

my_first_variable + my_second_variable

Let's say you want to calculate the gravitational force of some object with a mass of 4 kg.

Assign 9.81 to a variable called acceleration!
Assign 4 to a variable called mass!
Multiply both variables to calculate the gravitational force!


In [ ]:

Note, that the = is similar to the = in math, but it is not quite the same. Python always evaluates code on the right of the equal sign and assigns it to the variable on the left. So we can do:


In [ ]:
x = 5 + 2

But we can't do:


In [ ]:
5 + 2 = x

One thing we can do in python which does not make sense as a mathematical expression is shown in the following example:


In [ ]:
x = 5
x = x + 1

x

Here, we took the value of x, added 1 to it and then assigned the result back to the same name x. As a shortcut for such an operation we can use the += operator:


In [ ]:
x = 5
x += 1

x

The same also works for +=, -=, *= and /=.
Assign 3 to a variable called x!
Multiply x with 2 and assign the result to x!
Return the result by typing x!


In [ ]:

Variables can also hold data types other than numbers. For example they can store text. In programming a text piece is called a string. To tell python that something is a string and for example not a variable name, it has to be enclosed in quotes.
Execute the following example!


In [ ]:
my_first_string = 'Kartoffe'
my_second_string = 'lauf'

my_first_string + my_second_string * 2

Comments

Strings are not the only type of text in our code. Sometimes you want to add additional information to a human reader of your code but you don't want python to evaluate it. We can use the # sign to tell python that everything behind this sign in the same line is a comment and should not be evaluated. For example:


In [ ]:
5 + 7 # here we add 5 and 7 
# 3 * 5 this calulation is not evaluated because it is inside the comment.

Obviously, it is not necessary to comment on every simple statement like the one above but some comments can help to make your code more readable.

Functions

Sometimes we have to perform specific tasks over and over again. Let's say we have to two variables input_a and input_b and we have to perform the same complicated calculation with both variables. We could solve this problem by just copying and pasting parts of the code. For example:


In [ ]:
input_a = 5
input_b = 9

# This is the complicated calculation we want to performe for both variables:
intermediate_result = (input_a + 1) * 10 * 20 / 50
intermediate_result *= 50 - 40 / 3
result_of_a = intermediate_result / 59 * 2

# Here, I just copied the code above 
# and replaced input_a with input_b and result_of_a with result_of_b 
intermediate_result = (input_b + 1) * 10 * 20 / 50
intermediate_result *= 50 - 40 / 3
result_of_b = intermediate_result / 59 * 2

This code is perfectly valid. However, if you code like this your code will soon be very messy. Whenever you start copying parts of your code, there is always a better way to structure your code. In the example above we could clean up the code by defining a function which does this complicated calculation for us. A function is typically something that: takes some input, does something with the input, and then returns something. For example:


In [ ]:
def square(number):
    squared = number * number
    return squared

So lets go through that step by step.

  1. We used the keyword __def__ to state that we want to define a function.
  2. We selected a name for our new function. In this case the name is "square" but we could also have chosen any other name.
  3. We chose a name for the input argument of our function and place it in ().
  4. We end this first line with a :
  5. We define what this function does. In this case we calculate the square of the input. To show that this code belongs to the function definition we place one "tab" before the code.
  6. With the keyword __return__ we define what this function should return. In this case we return the squared number.

Now that we have defined this function, we can use it like this:


In [ ]:
square(10)

Now we can also rewrite our "complicated calculation" example nicely. Instead of copying the code, we define a function which does this complicated calculation. Then, we can use this function whenever we need to performe such a calculation.


In [ ]:
def complicated_calculation(input_number):
    intermediate_result = (input_number + 1) * 10 * 20 / 50
    intermediate_result *= 50 - 40 / 3
    end_result = intermediate_result / 59 * 2
    return end_result

input_a = 5
input_b = 9

result_of_a = complicated_calculation(input_a)
result_of_b = complicated_calculation(input_b)

I hope you aggree that using a function in this example makes the code clearer and easier to read. Now it is your turn!
Define a function called gravitational_force wich takes an input argument mass and returns the gravitational force.


In [ ]:
# Define your function here:

# If you defined the function correctly the following statment should
# return the gravitational force acting on a mass of 4 kg
gravitational_force(4)

So far, our functions had exactly one input argument and returned one output. It is also possible to have zero or multiple inputs and or outputs. For example:


In [ ]:
def add(a, b):
    return a + b

def return_5_anyway(): # note that you still need brackets even when your function takes no input
    return 5

return_5_anyway() # also wenn we call a function we allways have to use the brackets

Built-in functions

Python already comes with many built-in functions. You can find a complete list here. One very useful function is the print function. It allows you to print stuff to the output. For example:


In [ ]:
calculation_result = 13 * 5

print(calculation_result)

# or maybe even nicer:
print('the calculation result is:', calculation_result)

We can get access to even more useful functions by importing modules. For example, we can import the math module containing all kinds of mathematical functionality. After the import we can access functions in the module by typing name_of_the_module.function . For example:


In [ ]:
import math

print(math.log(5))

There are some modules like "math" which are present in every python installation and countless modules which can be installed additionally.

Lists and loops

Let's come back to this awfully complicated calculation from the previous examples. Let's say we have to do this calculation not only twice but even more often. Our code could look like this:


In [ ]:
def complicated_calculation(input_number):
    intermediate_result = (input_number + 1) * 10 * 20 / 50
    intermediate_result *= 50 - 40 / 3
    end_result = intermediate_result / 59 * 2
    return end_result

input_1 = 5
input_2 = 9
input_3 = 3
input_4 = 2
input_5 = 7
input_6 = 8

result_of_1 = complicated_calculation(input_1)
print(result_of_1)

result_of_2 = complicated_calculation(input_2)
print(result_of_2)

result_of_3 = complicated_calculation(input_3)
print(result_of_3)

result_of_4 = complicated_calculation(input_4)
print(result_of_4)

result_of_5 = complicated_calculation(input_5)
print(result_of_5)

result_of_6 = complicated_calculation(input_6)
print(result_of_6)

Luckily we already banned this complicated calculation into the body of a function, so we don't have to deal with it each time we do the calculation. Still, the code is pretty long and contains lots of repetitions.
Lists can help use to make this more efficient. Instead of creating several variables input_1, input_2, input_3 ... it is more convenient to store all the values in a list:


In [ ]:
inputs = [5, 9, 3, 2, 7, 8]

A list is defined with square brackets and list elements are separated by commas.
Whenever we want to access an element of a list we can do so by typing the list name and the index of the element in square brackets.


In [ ]:
inputs = [5, 9, 3, 2, 7, 8]

print(inputs[3])
print(inputs[0])
print(inputs[-1])  # returns the last element of the list

It is important to note that python starts counting with 0. So the first element of a list has the index 0 and not index 1.
Print the fifth element of the inputs list! (it should be a 7)


In [ ]:
inputs = [5, 9, 3, 2, 7, 8]
print(# your code goes here)

It is also possible to get slices of the complete list. The general notation is [begin:end:step]. For example:


In [ ]:
inputs = [5, 9, 3, 2, 7, 8]

print(inputs[:4]) # the first four list elements
print(inputs[1:4]) # elements from index 1 to (not including) index 4
print(inputs[::2]) # every second element

Ok. Now we have conveniently stored all our input values in a list. But how do we perform our "complicated calculation" with all these inputs? The easiest solution is to use a for loop. have a look at the following example:


In [ ]:
inputs = [5, 9, 3, 2, 7, 8]

for number in inputs:
    print(number)

Let's go through that step by step:

  1. We defined a list called inputs.
  2. We used the keyword __for__ to make a for loop.
  3. We chose a name for the current element. In this case the name number was chosen.
  4. after the keyword __in__ we specify which list we want to iterate over.
  5. end line with :
  6. in the indented body of the loop we specify what we want do with each element. In this case we print each element.

Using a list and a for loop we can rewrite our "complicated calculation" example as follows:


In [ ]:
def complicated_calculation(input_number):
    intermediate_result = (input_number + 1) * 10 * 20 / 50
    intermediate_result *= 50 - 40 / 3
    end_result = intermediate_result / 59 * 2
    return end_result

inputs = [5, 9, 3, 2, 7, 8]

for number in inputs:
    result = complicated_calculation(number)
    print(result)

Now it's your turn again.

  • Define a function which returns the gravitational force (you can use the one you have written before)
  • create a list of masses of your choice
  • use a loop to iterate over all masses and print the gravitational force for each mass

In [ ]:

Objects

Let's say we want to write a game where some kind of creatures interact with each other. During the game we have to keep track of different properties of each creature and each creature should have certain functions it can perform. To store all these properties and and functions in one place we can create an object. Have a look at the following code:


In [ ]:
# First, we have to define what properties and functions our object should have.
# Our object will later be created as an instance of the class we define here.
class Creature:
    # In the __init__ function we define what happens when an instance of this class is created.
    # Note that each function in an object gets the object itself as first input argument.
    def __init__(self, initial_health, start_position):
        self.health = initial_health  # Here, we set a property of our object called health to initial_health. 
        self.position = start_position
    
    # Here we define some functions (also called methods) our object should have. 
    def move(self, new_position):
        self.position = new_position
    
    def attack(self, target):
        target.health -= 5

        
# Now, that we have defined what a creature is we can create instances of the creature class.
# Here, we create a creature object called bob and one called eve.
# The following statement calls the __init__ function of the Creature class 
# and passes an initial_health and a start_position.
# The start position is passed as a list containing an x and a y coordinate.
bob = Creature(100, [5, 3])
eve = Creature(120, [4, 3])

# the object properties and functions can be accessed by object_name.property:
print('Bob\'s health is:', bob.health)
print('Eve\'s health is:', eve.health)

eve.attack(bob)

print('after Eve\'s attack Bob\'s health is:', bob.health)

Obviously, this is not a finished game yet but hopefully it's clear how objects can help to write code for such an example.
Now your task is to define a class yourself.

  • Define a class named Ball!
  • Each ball should have the properties position, velocity, and radius that are set when calling the __init__ function. (Note, that there are two underscores in the front and at the end of the name)
  • Create an instance of the class Ball called tennis_ball with a position [0, 1] a velocity [0, 0] and a radius 0.033

In [ ]:
# Define your class here


# Create an instance called tennis_ball here


# If you have done everything correctly the following should print
# the position, velocity and radius of the tennis ball.
print('the position of the tennis ball is:', tennis_ball.position)
print('the velocity of the tennis ball is:', tennis_ball.velocity)
print('the radius of the tennis ball is:' , tennis_ball.radius)

Okay, now the properties of the ball are set. It has a position represented by a list with an x and y coordinate. You can also think of this list as a vector. So far, you have set the x value to 0 and the y value to 1. This means, the ball is floating in a height of 1 (meter). The ball's velocity is also a vector which, so far, only contains zeros.

A ball floating in a height of 1 meter is not very realistic. The ball should be able to move! We assume that there is no friction. The only thing that changes the velocity of the ball is the gravitational force. To make the ball move you have to:

  1. Update the position of the ball. We will do that be adding the velocity multiplied with time step. This is not absolutely correct because the velocity is not constant during a step. However, if we keep the step size reasonably small we should be fine.
  2. Update the velocity of the ball. Fix the code below to make the ball move!

In [ ]:
class Ball:
    def __init__(self, start_position, start_velocity, radius):
        self.position = start_position
        self.velocity = start_velocity
        self.radius = radius
    
    # so for the move function doesn't change anything. Fix it!
    def move(self, time_step):
        self.position[0] += 0
        self.position[1] += 0
        
        self.velocity[0] += 0
        self.velocity[1] += 0
        
tennis_ball = Ball([0, 1], [0, 0], 0.033)
time_step = 0.04

for step in range(1, 15): # range is a built-in function which returns all number is a given range
    tennis_ball.move(time_step)
    print('time:', step*time_step, 'position:', tennis_ball.position)

If you fixed the code correctly, it should take the ball roughly 0.45 s to fall from a height of 1 meter before it reaches y = 0. Then, the ball continues to fall to negative y values. Again, this is not a very realistic behaviour of our ball. At y = 0 there is the floor of our room. It should perform an elastic collision and bounce up again. We somehow have to adjust our behaviour when our ball hits the floor.

Conditions

First, we have to get to know one more data type: a boolean. A boolean can only have two different states. It can either be True or False. We for example create a boolean if we compare things. for example:


In [ ]:
print(5 > 1)
print(5 < 3)

We can also check if things are equal == or not equal !=


In [ ]:
print(5 == 5)  # note that we use == as = is already used for assignments in python
print(5 != 3)

Such booleans can be used to adjust the behaviour of our code in certain conditions by using the __if__-statement. All code in the body of an __if__-statement is only executed when the condition of the if statement evaluates to True. Have look at the following example:


In [ ]:
if 5 > 1:
    # this is executed because 5 > 1 is True
    print('5 > 1 is True')

if 5 < 3:
    # this is not executed because 5 < 3 is False
    print('I can write what I want. It will not be printed')

Knowing about conditions we can now update our tennis_ball code to make it bounce. Before updating the velocity, check whether the position has a negative y component. If that is the case invert the sign of velocity's y-component.


In [ ]:
class Ball:
    def __init__(self, start_position, start_velocity, radius):
        self.position = start_position
        self.velocity = start_velocity
        self.radius = radius
    
    def move(self, time_step):
        self.position[0] += self.velocity[0] * time_step
        self.position[1] += self.velocity[1] * time_step
        
        # fix the move function here to make the ball bounce
        
        self.velocity[0] += 0
        self.velocity[1] -= 9.81 * time_step
        
        
tennis_ball = Ball([0, 1], [0.2, 0], 0.033)
time_step = 0.04
number_of_steps = 150

trajectory = []  # this is an empty list

for step in range(number_of_steps):
    tennis_ball.move(time_step)
    trajectory.append(tennis_ball.position[:])  # append adds something at the end of a list

    

# don't worry about the code below too much it just crates an animation
# of your calculated positions.
from animation import animate
%matplotlib notebook

ani = animate(trajectory, time_step*100/0.04)

Probably you have noticed that the ball reaches smaller and smaller heights. It somehow looses energy although we have not included any friction. What is the reason for that?
Change the timestep to 0.004 and see how the behaviour changes!
(Hint: if you reduce the time_step you might want to increase the number_of_steps accordingly)

Great! You have just done your first own tennis ball computer simulation, and from simulating tennis balls to simulating atoms and molecules it is realy not a big step!

This was a very condensed introduction to python. If you want to learn more python, you can find hundreds of tutorials online. One good address to get started is www.codecademy.com.
You can also check out codecombat.com It is more a game than a tutorial but still you can learn some python along the way.