00_datatypes

Open on binder: Binder

This notebook is based on the tutorial series by Rajath Kumar M P. Visit his GitHub to find out more: https://github.com/rajathkmp

What you will learn

In this tutorial, you will learn: - How to run code in jupyter notebooks. - What operators there are and how to use them. - How python stores numbers. - What functions are already available in python (built-in). - What data structures python provides and what special methods they offer. - How to control the flow of python code.

How to navigate jupyter notebooks

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 toolbar on top or by pressing shift + enter.

The zen of python

This little snippet by Tim Peters contains 19 guiding principles how the python programming language should be used. We use it as a way to demonstrate how to execute code in jupyter-notebook cells. Click into the cell reading “import this” and run it by hitting “shift” and “enter” at the same time. Alternatively you can click on the “run” button in the top toolbar. After that continue in executing the cells. Have fun in learning python!

[1]:
import this
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.
Readability counts.
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!

Antigravity

Just execute this cell. Trust me I’m a tutorial.

[2]:
import antigravity

Keywords

Some words are automatically recognized by your viewer (in this case jupyter notebook) and highlighted accordingly. Keywords are case-sensitive. See the examples below:

[3]:
import keyword
keyword.kwlist
[3]:
['False',
 'None',
 'True',
 'and',
 'as',
 'assert',
 'async',
 'await',
 'break',
 'class',
 'continue',
 'def',
 'del',
 'elif',
 'else',
 'except',
 'finally',
 'for',
 'from',
 'global',
 'if',
 'import',
 'in',
 'is',
 'lambda',
 'nonlocal',
 'not',
 'or',
 'pass',
 'raise',
 'return',
 'try',
 'while',
 'with',
 'yield']

Variables

Values or other kinds of data in python is stored in variables. Variables are declared with an equal sign, which assigns the value on the right to the variable name on the left.

[4]:
a = 3
b = 2
c = 'Hello World!'

Access the variables values this way. Note how the cell returns the string with the annotation Out [4]: or a different integer. However, this way you can only return the value of the last line in a code cell.

[5]:
c
[5]:
'Hello World!'
[6]:
c
b
a
[6]:
3

If you want to see the values of multiple variables at once you can use the print function. See the examples below:

>>> print(a) # prints variable a

Notice how the Out [x]: is missing from the output of these cells. This is because the print() function returns None.

[7]:
print(a)
print(a + b)
print(c)
3
5
Hello World!
[8]:
d = print(a)
print(d)
3
None

There are some best practices when declaring variables. - Variable names should say something about their content. - When the variable uses a built-in name (e.g. print, None) an underscore (‘_’) can be appended. - Throwaway variables are most often called ‘__’ (double underscore)

In the next cell a string is declared as a variable called print_ (with trailing underscore)

[9]:
print_ = 'This should be printed'
print(print_)
This should be printed
[10]:
a = 1
b = 1.2
print(a)
print(b)
print(a + b)
1
1.2
2.2

Comments

Comments are declared by hashes (#). Python styleguides also dictate a space between the hash and the actual line. However, this is not a requirement for comments. Multi-line comments are declared by triple quotes (’’’). If you like, you can un-comment the next lines and see how overwriting the print() function with a variable called print (without trailing underscore) breaks the system. If you want to resume you need to restart the Kernel. You can do this by clicking on “Kernel” in the top toolbar and then “Restart”, or with button highlighted with a red box.

Toc

[11]:
'''
This is a multiline
comment.
'''

a = 3

# The next line breaks the system. Restart of the Kernel required.
# print = 'This should be printed'

print(a)
3

Operators

Arithmetic operations

Following operations are built into python.

\(\Large Symbol\)

\(\Large Task\ Performed\)

\(\Large +\)

Addition

\(\Large -\)

Subtraction

\(\Large *\)

Multiplication

\(\Large /\)

Division

\(\Large \%\)

Modulo

\(\Large //\)

Floor Division

\(\Large **\)

Power of

[12]:
print(1 + 3)
print(3 ** 2)
print(5 - 2)
print(1 / 2) # The result of this operation is special. We'll learn later why.
4
9
3
0.5

Prepare for some modulo functions!

[13]:
print(5 % 5)
print(5 % 2)
print(9 % 3)
print(9 % 5)
0
1
0
4

It computes the remainder of a division.

Relational operators

Following relational operators are in python:

\(\Large Symbol\)

\(\Large Task Performed\)

\(\Large ==\)

True if equal

\(\Large !=\)

True if not equal

\(\Large <\)

less than

\(\Large >\)

greater than

\(\Large <=\)

less than or equal to

\(\Large >=\)

greater than or equal to

\(\Large is\)

Identity operator

[14]:
print(5 == 5)
True
[15]:
print(4 <= 5)
True
[16]:
print(5 != 5)
False
[17]:
a = 5
b = 5.0
print(a == b)
print(a is b)
True
False

I have to admit that this question requires a certain amount of prior knowledge. It boils down to datatypes. A is an integer and b is a float. Datatypes will be introduced later on. Stay tuned.

Bitwise operators

Following bitwise operators are available:

\(\Large Symbol\)

\(\Large Task Performed\)

\(\Large \&\)

Logical And

\(\Large |\)

Logical OR

\(\Large \hat{}\)

XOR (exclusive OR)

\(\Large \sim\)

Negate

\(\Large >>\)

Right shift

\(\Large <<\)

Left shift

[18]:
a = 2 # 0b10 in binary
b = 3 # 0b11 in binary
[19]:
print(bin(a))
print(bin(b))
print(a & b)
print(bin(a & b))
0b10
0b11
2
0b10

The result can be explained by remembering that ‘&’ is a bitwise operator. So let’s compare the binaries on a bitwise basis:

  • First bits: 1 & 1 equals 1

  • Second bits: 1 & 0 equals 0

[20]:
print(1 & 1)
print(0 & 1)
1
0
[21]:
print(5 >> 1)
2

0000 0101 -> 5

Shifting the digits by 1 to the right and zero padding

0000 0010 -> 2

[22]:
print(5 << 1)
10

0000 0101 -> 5

Shifting the digits by 1 to the left and zero padding

0000 1010 -> 10

# example for False AND False
print(0 & 0)
[23]:
print(1 & 1)
print(1 & 0)
print(1 | 0)
print(0 | 1)
print(1 ^ 0)
print(1 ^ 1)
1
0
1
1
1
0

Boolean operators

Boolean operators are used on booleans, that is truth, values. Following boolean operators are available:

\(\Large Operator\)

\(\Large Task Performed\)

\(\Large and\)

Logical AND

\(\Large or\)

Logical OR

\(\Large not\)

Negate

Because python’s booleans True and False are just different representations of 1 (or any other integer) and 0, we can write the exercise from above with boolean values, but using bitwise operators:

[24]:
print(True & True)
print(True & False)
print(True | False)
print(False | True)
print(True ^ False)
print(True ^ True)
True
False
True
True
True
False
[25]:
print(False or True)
print(False and False)
print(True or False)
print(True or True)
print(not False)
print(not True)
True
False
True
True
True
False

Floats and ints

There are two datatypes to hold numerical values in python. The int (integer) datatype captures negative and positive numbers (-2, -1, 0, 1, 2, 15). The float (floating point) datatype captures all numbers imaginable (1.0, 2.3, 5.245, 3.141). Certain floats (1.0) can also be integers (you can omit the decimal zero and just use a dot to tell python, that the number is a float (1.)). Python is pretty easy-going on datatypes (polymorphism) and can compare int and float. This would not be possible for some other programming languages.

[26]:
1 == 1.
[26]:
True

Addition of floats and ints also works like a charm and yields the higher order datatype.

[27]:
print(1 + 2)
print(1.0 + 2.0)
print(1.5 + 2)
3
3.0
3.5

Also, integer division (in contrast to other programming languages) works even when the result is not an integer. Some programming languages yield 2 as the result for 5 / 2. In python, we can reproduce that behavior by using the floor division (//):

[28]:
5 // 2
[28]:
2

So don’t worry about divisions:

[29]:
5 / 2
[29]:
2.5

However, some tasks require ints and floats are not allowed. Python will let you know, that it expected an int with a handy error message:

[30]:
['a', 'b', 'c'][1.0]
<>:1: SyntaxWarning: list indices must be integers or slices, not float; perhaps you missed a comma?
<>:1: SyntaxWarning: list indices must be integers or slices, not float; perhaps you missed a comma?
/tmp/ipykernel_2468/3465051626.py:1: SyntaxWarning: list indices must be integers or slices, not float; perhaps you missed a comma?
  ['a', 'b', 'c'][1.0]
/tmp/ipykernel_2468/3465051626.py:1: SyntaxWarning: list indices must be integers or slices, not float; perhaps you missed a comma?
  ['a', 'b', 'c'][1.0]
/tmp/ipykernel_2468/3465051626.py:1: SyntaxWarning: list indices must be integers or slices, not float; perhaps you missed a comma?
  ['a', 'b', 'c'][1.0]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[30], line 1
----> 1 ['a', 'b', 'c'][1.0]

TypeError: list indices must be integers or slices, not float

Built-in functions and printing

Bog-standard python comes with some already declared functions which we can use right from the start.

The help() function

The help() function can be used to get information more information about objects, functions and methods.

[31]:
help(print)
Help on built-in function print in module builtins:

print(...)
    print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)

    Prints the values to a stream, or to sys.stdout by default.
    Optional keyword arguments:
    file:  a file-like object (stream); defaults to the current sys.stdout.
    sep:   string inserted between values, default a space.
    end:   string appended after the last value, default a newline.
    flush: whether to forcibly flush the stream.

Conversion between numbering systems

To differentiate our base-10 numbers from binary, hexadecimal, and octal numbering systems the other systems get prefixed with these prefixes:

  • 0b: binary

  • 0x: hexadecimal

  • 0o: octal

With the built-in functions bin(), hex() and oct() numbers can be converted.

[32]:
a = 5
b = 0b00001111
c = 0xff
[33]:
print(a)
print(b)
print(c)
5
15
255
[34]:
print(oct(a))
print(oct(b))
print(bin(c))
0o5
0o17
0b11111111

The IEEE-754 numbering system

The IEEE-754 (spoken: I-triple-E) standard defines a numbering system, that is most commonly known for its 32 and 64 bit (single and double precision) numbers. The IEEE system is used on almost all computers to store floating point numbers. If you tell python to store a float like 0.5 as the variable a your computer stores it as a sequence of 32 bits.

IEE-754 standard

(image from https://www.geeksforgeeks.org/)

  • The 31st bit (also called sign bit) tells whether the number is negative (1) or positive (-1)

  • The next 8 bits (bits 30 to 23) are the exponent bits. The sign is an 8-bit based integer. Meaning an exponent bit of 0b00000000 means the exponent is \(0 - 127 = -127\). The base of the exponent is 2 (\(2^{-127}\)). An exponent of 0b11111111 is (\(255 - 127 = 128\)).

  • The remaining 23 bits are the significant precision bits with the formula:

\begin{equation} 1 + \sum_{i = 1}^{23} b_{23-i} \cdot 2^{-i} \end{equation}

This means the bits - 0b100000... translate to $ 1 + 1 \cdot 2`^{-1} + 0 + :nbsphinx-math:dots = 1.5$ - ``0b010000…` to $ 1 + 0 \cdot 2`^{-1} + 1 :nbsphinx-math:cdot 2`^{-2} + 0 + \dots `= 1.25$ - ``0b001000...` to \(1.125\). - 0b000100... to \(1.0625\). - 0b100100... to \(1.5625\).

[35]:
print(0b11111111)
255
[36]:
from IEEE754 import float_to_ieee

print(float_to_ieee(0.5))
print(float_to_ieee(-0.5))
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[36], line 1
----> 1 from IEEE754 import float_to_ieee
      3 print(float_to_ieee(0.5))
      4 print(float_to_ieee(-0.5))

ModuleNotFoundError: No module named 'IEEE754'
[37]:
from IEEE754 import ieee_to_float

print(ieee_to_float('10111111000000000000000000000000'))
print(ieee_to_float('10111111000000000000010000000000'))
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[37], line 1
----> 1 from IEEE754 import ieee_to_float
      3 print(ieee_to_float('10111111000000000000000000000000'))
      4 print(ieee_to_float('10111111000000000000010000000000'))

ModuleNotFoundError: No module named 'IEEE754'

The number 0.1 does not have a representation in IEE547, because in binary it is a real non-rational (i.e. periodic) number. Just like the number 1/3 can’t be represented in decimal notation, as the decimal places never halt (0.333333333). For the IEEE754 representation of 0.1, the periodic repeat unit is 1001:

[38]:
from IEEE754 import float_to_ieee

print(float_to_ieee(0.1))
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[38], line 1
----> 1 from IEEE754 import float_to_ieee
      3 print(float_to_ieee(0.1))

ModuleNotFoundError: No module named 'IEEE754'

Using int() for conversion

The built-in function int() accepts two values when used for conversion, one is the value in a different number system and the other is its base. Note that input number in the different number system should be of string type.

[39]:
print(int('010',8))
print(int('0xaa',16))
print(int('1010',2))
8
170
10

int() can also be used to get only the integer value of a float number or can be used to convert a number which is of type string to integer format. Similarly, the function str( ) can be used to convert the integer back to string format

[40]:
print(int(7.7))
print(int('7'))
7
7

chr()

Also note that function bin() is used for binary and float() for decimal/float values. chr() is used for converting ASCII to its alphabet equivalent, ord() is used for the other way round.

[41]:
print(chr(98))
print(ord('A'))
print(chr(27))
b
65


Never seen the symbol of the ASCII-character number 27? That’s because it’s a so-called control-character. These go back to the times of Teleprinters, which were used to communicate via text. These control-characters mark stuff like beginning and end of a transmission. ASCII-character 27 is the escape control-character (ESC).

Toc

Image taken from wikipedia

The ord() function

The ord function is the exact opposite of the chr() function.

[42]:
print(ord('a'))
print(ord('b'))
print(ord('c'))
print(chr(ord('h')))
print(chr(ord('i')))
97
98
99
h
i

The type function

Python has a very handy builtin function called type() which you can use to get the datatype of a variable.

[43]:
print(type(1))
print(type(1.))
print(type(0xff))
print(type(False))
print(type(None))
<class 'int'>
<class 'float'>
<class 'int'>
<class 'bool'>
<class 'NoneType'>

Simpifying arithmetic operations

The round() function rounds the input value to a specified number of places or to the nearest integer.

Note: Python rounding is not the same as mathematical rounding. In python even numbers are rounded down and uneven are rounded up. For mathematical rounding you will need the built-in module math.

[44]:
print(round(5.5))
print(round(6.5))
6
6
[45]:
print(round(6.5))
print(round(7.5))
6
8
[46]:
print(round(5.6231))
print(round(4.55892, 2))
6
4.56

complex() is used to define a complex number and abs() outputs the absolute value of the same.

[47]:
c = complex('5+2j')
print(abs(c))
5.385164807134504

divmod(x,y) outputs the quotient and the remainder in a tuple (you will be learning about tuples in further chapters) in the format (quotient, remainder).

[48]:
print(divmod(9,2))
print(9 // 2)
print(9 % 2)
(4, 1)
4
1

isinstance() returns True, if the first argument is an instance of that class. Multiple classes can also be checked at once.

[49]:
instances = (int, float)

print(isinstance(1, int))
print(isinstance(1.0, int))
print(isinstance(1.0, instances))
print(isinstance(1, instances))
True
False
True
True

pow(x,y,z) can be used to find the power \(x^y\) also the mod of the resulting value with the third specified number can be found i.e. : (\(x^y\) % z).

[50]:
print(pow(3,3))
print(pow(3,3,5))
27
2

The range() function outputs the integers of the specified range. It can also be used to generate a series by specifying the difference between the two numbers within a particular range. The elements are returned in a list (will be discussing in detail later.)

[51]:
print(range(3))
print(range(2,9))
print(range(2,27,8))
range(0, 3)
range(2, 9)
range(2, 27, 8)

Accepting user inputs

input() accepts input and stores it as a string. Hence, if the user inputs an integer, the code should convert the string to an integer and then proceed.

[52]:
abc = input("Type something here and it will be stored in variable abc\n")
---------------------------------------------------------------------------
StdinNotImplementedError                  Traceback (most recent call last)
Cell In[52], line 1
----> 1 abc = input("Type something here and it will be stored in variable abc\n")

File /opt/hostedtoolcache/Python/3.8.16/x64/lib/python3.8/site-packages/ipykernel/kernelbase.py:1181, in Kernel.raw_input(self, prompt)
   1179 if not self._allow_stdin:
   1180     msg = "raw_input was called, but this frontend does not support input requests."
-> 1181     raise StdinNotImplementedError(msg)
   1182 return self._input_request(
   1183     str(prompt),
   1184     self._parent_ident["shell"],
   1185     self.get_parent("shell"),
   1186     password=False,
   1187 )

StdinNotImplementedError: raw_input was called, but this frontend does not support input requests.

The built-in function type returns the type of a variable.

[53]:
print(type(abc))
print(type)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[53], line 1
----> 1 print(type(abc))
      2 print(type)

NameError: name 'abc' is not defined
[54]:
inp = input("What number would you like to have 5 added to it?\n")
out = float(inp) + 5
print(out)
---------------------------------------------------------------------------
StdinNotImplementedError                  Traceback (most recent call last)
Cell In[54], line 1
----> 1 inp = input("What number would you like to have 5 added to it?\n")
      2 out = float(inp) + 5
      3 print(out)

File /opt/hostedtoolcache/Python/3.8.16/x64/lib/python3.8/site-packages/ipykernel/kernelbase.py:1181, in Kernel.raw_input(self, prompt)
   1179 if not self._allow_stdin:
   1180     msg = "raw_input was called, but this frontend does not support input requests."
-> 1181     raise StdinNotImplementedError(msg)
   1182 return self._input_request(
   1183     str(prompt),
   1184     self._parent_ident["shell"],
   1185     self.get_parent("shell"),
   1186     password=False,
   1187 )

StdinNotImplementedError: raw_input was called, but this frontend does not support input requests.

Printing and Strings

There are three ways to define strings in python.

  • Single quotes ('string') are often used for single characters, key-words and fixed strings.

  • Double quotes ("This is a string") are often used when working with natural language.

  • Triple quotes ("""This is a special string called docstring. The doc means documentation""") are often used to annotate functions and classes, so that other people can refer to this docstring when they try to comprehend your code.

Strings are often used to print useful information. If your script runs into an error while looking for a file, because the file is not there you could make the script print an error message that helps you to identify the problem.

if file does not exist:
    print('The file does not exist')
    exit()

There are several ways to help you format your strings and print them:

  • plain string printing: print('This is a string')

  • printing of multiple values:

a = 'The value of b is'
b = 3
print(a, b)
  • string concatenation:

filename = '/path/to/file.txt'
print('The file ' + filename + ' does not exist.'
  • %-formatted strings (python2): print('The value of b is %s' % b)

  • formatted strings (python3): print('The value of a is {} and of b is {}'.format(a, b))

  • f-strings (new in python3.6): print(f'The value of a is {a} and of b is {b}')

Let’s take a look at all these different string formats.

[55]:
print("Hello World")
Hello World
[56]:
print("""Multi-line strings can only be

written with the triple-quoted string

because it preserves line breaks""")

# a single-quoted string can not be broken into lines
# print('This can not be
#       printed')
Multi-line strings can only be

written with the triple-quoted string

because it preserves line breaks
[57]:
print("If you want to have line breaks\nin single-quoted strings,\nyou need to add a newline character(\\n)")
If you want to have line breaks
in single-quoted strings,
you need to add a newline character(\n)

If you have long strings and don’t want line breaks you can put multiple strings inside parentheses (like a tuple), but without commas:

[58]:
s = ('This is a long string, which does not break over multiple '
     'lines. The line breaks are handled by the program displaying '
     'the string (so-called soft-wraps). Writing strings like this is '
     'especially useful if you don\'t want to write never-ending '
     'lines of code. Most styleguides also recommend to break '
     'lines of python code after 79 character. This is talked '
     'about in PEP8: https://www.python.org/dev/peps/pep-0008/')
print(type(s))
print(s)
<class 'str'>
This is a long string, which does not break over multiple lines. The line breaks are handled by the program displaying the string (so-called soft-wraps). Writing strings like this is especially useful if you don't want to write never-ending lines of code. Most styleguides also recommend to break lines of python code after 79 character. This is talked about in PEP8: https://www.python.org/dev/peps/pep-0008/

Depending on the width of the printed text above, some words might be broken over the lines. This can be prevented with the built-in package textwrap (python 3.9):

[59]:
import textwrap
print('\n'.join(textwrap.wrap(s)))
This is a long string, which does not break over multiple lines. The
line breaks are handled by the program displaying the string (so-
called soft-wraps). Writing strings like this is especially useful if
you don't want to write never-ending lines of code. Most styleguides
also recommend to break lines of python code after 79 character. This
is talked about in PEP8: https://www.python.org/dev/peps/pep-0008/

Strings can be assigned to variable say string1 and string2 which can called when using the print statement.

[60]:
string1 = 'World'
print('Hello', string1)

string2 = '!'
print('Hello', string1, string2)
Hello World
Hello World !

String concatenation is the “addition” of two strings. Observe that while concatenating there will be no space between the strings.

[61]:
print('Hello' + string1 + string2)
HelloWorld!

This works only with strings, because python can’t add a non-string object to a string

[62]:
b = 3
print("Variable b is " + b)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[62], line 2
      1 b = 3
----> 2 print("Variable b is " + b)

TypeError: can only concatenate str (not "int") to str

But we can make b a string, by calling the built-in function str() on it.

[63]:
b = 3
print("Variable b is " + str(b))

Variable b is 3

This way of constructing strings is not very readable, if you look at the code. That’s what f-strings are for.

[64]:
distance = 100
time = 2
velocity = distance / time

print("A vehicle travelling at " + str(velocity) + " km/h will cover a distance of " + str(distance) + " km in " + str(time) + " hours.")
A vehicle travelling at 50.0 km/h will cover a distance of 100 km in 2 hours.
[65]:
print(f"A vehicle travelling at {velocity} km/h will cover a distance of {distance} km in {time} hours.")
A vehicle travelling at 50.0 km/h will cover a distance of 100 km in 2 hours.
[66]:
username = input("What is your name?\n")
color = input("What is your favorite color?\n")
print(f"{username}, your favorite color is {color}.")
---------------------------------------------------------------------------
StdinNotImplementedError                  Traceback (most recent call last)
Cell In[66], line 1
----> 1 username = input("What is your name?\n")
      2 color = input("What is your favorite color?\n")
      3 print(f"{username}, your favorite color is {color}.")

File /opt/hostedtoolcache/Python/3.8.16/x64/lib/python3.8/site-packages/ipykernel/kernelbase.py:1181, in Kernel.raw_input(self, prompt)
   1179 if not self._allow_stdin:
   1180     msg = "raw_input was called, but this frontend does not support input requests."
-> 1181     raise StdinNotImplementedError(msg)
   1182 return self._input_request(
   1183     str(prompt),
   1184     self._parent_ident["shell"],
   1185     self.get_parent("shell"),
   1186     password=False,
   1187 )

StdinNotImplementedError: raw_input was called, but this frontend does not support input requests.

PrecisionWidth and FieldWidth

Numbers can be formatted using this special notation:

  • :s -> string

  • :d -> Integer

  • :f -> Float

  • :o -> Octal

  • :x -> Hexadecimal

  • :e -> exponential

[67]:
d = 18
print(f"Actual Number = {d:d}")
print(f"Float of the number = {d:f}")
print(f"Octal equivalent of the number = {d:o}")
print(f"Hexadecimal equivalent of the number = {d:x}")
print(f"Exponential equivalent of the number = {d:e}")
Actual Number = 18
Float of the number = 18.000000
Octal equivalent of the number = 22
Hexadecimal equivalent of the number = 12
Exponential equivalent of the number = 1.800000e+01

Fieldwidth is the width of the entire number and precision is the width towards the right. One can alter these widths based on the requirements.

The default Precision Width is set to 6.

[68]:
d = 3.121312312312
print(f'{d:f}')
3.121312

Notice upto 6 decimal points are returned. To specify the number of decimal points, '{:(fieldwidth).(precisionwidth)f}' is used.

[69]:
print(f'{d:.5f}')
3.12131

If the field width is set more than the necessary than the data right aligns itself to adjust to the specified values.

[70]:
print(f'{d:9.5f}')
  3.12131

Zero padding is done by adding a 0 at the start of fieldwidth.

[71]:
print(f'{d:020.5f}')
00000000000003.12131

For proper alignment, a space can be left blank in the field width so that when a negative number is used, proper alignment is maintained.

[72]:
d_neg = -d
print(f' {d:9f}')
print(f' {d_neg:9f}')
  3.121312
 -3.121312

The ‘+’ sign can be returned at the beginning of a positive number by adding a + sign at the beginning of the field width.

[73]:
print(f' {d:+9f}')
print(f' {d_neg:9f}')
 +3.121312
 -3.121312

As mentioned above, the data right aligns itself when the field width mentioned is larger than the actual fieldwidth. But left alignment can be done by specifying a negative symbol in the field width.

[74]:
print(f' {d:-9.3f}')
print(f' {d:9f}')
     3.121
  3.121312

Debugging. There’s also the possibility for a “debugging” f-string by using an equal sign:

[75]:
print(f'Here are the current variables {a=}, {d=}')
Here are the current variables a=5, d=3.121312312312

Exercise: Printing a date and a time

[76]:
DD = 3
MM = 5
YYYY = 2012.0
hh = 3
mm = 20
print("DD.MM.YYYYY hh:mm")
DD.MM.YYYYY hh:mm
[77]:
print(f"{DD:02}.{MM:02}.{YYYY:.0f} {hh}:{mm}")
03.05.2012 3:20

Data structures

Data structures are a collection of data in a specific format. If you want to store multiple variables which give the temperature of a probe overt time it would be better to store them in a list instead of giving every temperature its own variable.

Lists

Lists are the most commonly used data structure. Think of it as a sequence of data that is enclosed in square brackets and data are separated by a comma. Each of these data can be accessed by calling its index value.

Lists are declared by just equating a variable to ‘[ ]’ or list.

[78]:
a = []
print(type(a))
<class 'list'>

You can create a list by directly declaring it:

[79]:
x = ['apple', 'orange', 1, 1.1, 'hello', 'list', "Even longer strings are possible"]

Indexing

In python, Indexing starts from 0. Thus, now the list x, will have apple at 0 index and orange at 1 index.

The zero based indexing will lead to problems when data from other programs (atomic coordinate files) are loaded. Always check if you transition from 1-based to 0-based indexing!

[80]:
x[0]
[80]:
'apple'

Indexing can also be done in reverse order. That is the last element can be accessed first.

[81]:
x[-1]
[81]:
'Even longer strings are possible'
[82]:
print(x[4])
hello

Nested lists

A list can itself be an element of a list

[83]:
x = ['apple', 'orange']
y = ['carrot','potato']
z  = [x,y]
print(z)
[['apple', 'orange'], ['carrot', 'potato']]

Indexing in nested lists can be quite confusing if you do not understand how indexing works in python. So let us break it down and then arrive at a conclusion.

Let us access the data ‘apple’ in the above nested list. First, at index 0 there is a list [‘apple’,‘orange’] and at index 1 there is another list [‘carrot’,‘potato’]. Hence z[0] should give us the first list which contains ‘apple’.

[84]:
z0 = z[0]
print(z0)
['apple', 'orange']

Now observe that z0 is not at all a nested list thus to access ‘apple’, z0 should be indexed at 0.

[85]:
z0[0]
[85]:
'apple'
[86]:
z[0][0]
[86]:
'apple'

Slicing

Indexing is only limited to accessing a single element, Slicing on the other hand is accessing a sequence of data inside the list.

Slicing is done by defining the index values of the first element and the last element +1 from the parent list that is required in the sliced list. It is written as parentlist[ a : b ] where a,b are the index values from the parent list. If a or b is not defined then the index value is considered to be the first value for a if a is not defined and the last value for b when b is not defined.

[87]:
num = [0,1,2,3,4,5,6,7,8,9]
[88]:
print(num[0:4])
[0, 1, 2, 3]

See, how the 4th element is excluded from the list. The lower index is inclusive, the higher index is exclusive. This is done, so slicing the list with [4:] returns the remainder of the list.

[89]:
print(num[4:])
[4, 5, 6, 7, 8, 9]

You can also use a step to only return every nth element of the list

[90]:
print(num[1:9:4])
print(num[::2])
[1, 5]
[0, 2, 4, 6, 8]
[91]:
print(num[1:6:2])
[1, 3, 5]

Built-in list functions

To find the length of the list or the number of elements in a list, len() is used.

[92]:
len(num)
[92]:
10

If the list consists of all numerical elements then min() and max() gives the minimum and maximum value in the list.

[93]:
print(min(num))
print(max(num))
0
9

Lists can be concatenated by adding with ‘+’. The resultant list will contain all the elements of the lists that were added. The resultant list will not be a nested list.

[94]:
[1,2,3] + [5,4,7]
[94]:
[1, 2, 3, 5, 4, 7]

There might arise a requirement where you might need to check if a particular element is there in a predefined list. This can be done with the in logic operator

[95]:
elements = ['Earth', 'Air', 'Fire', 'Water']
print('Earth' in elements)
print('Aether' in elements)
True
False

To a list of strings, max() and min() can also be applied. max() would return a string element whose ASCII value is the highest and vice versa. Note that only the first index of each element is considered each time and if the value is the same then the second index considered so on and so forth.

[96]:
mlist = ['bzaa','ds','nc','az','z','klm']
print(max(mlist))
print(min(mlist))
z
az

Here, the first index of each element is considered and thus z has the highest ASCII value thus it is returned and minimum ASCII is a. But what if numbers are declared as strings?

[97]:
nlist = ['1','94','93','1000']
print(max(nlist))
print(min(nlist))
94
1

Even if the numbers are declared in a string the first index of each element is considered and the maximum and minimum values are returned accordingly.

But if you want to find the max() string element based on the length of the string then another parameter ‘key=len’ is declared inside the max() and min() function.

[98]:
print(max(elements, key=len))
print(min(elements, key=len))
Earth
Air

But even ‘Water’ has length 5. The max() or min() functions return the first element when there are two or more elements with the same length.

Any other built-in function can be used or the lambda function which will be discussed in 02_functions_and_classes.

A string can be converted into a list by using the list() function.

[99]:
list('hello')
[99]:
['h', 'e', 'l', 'l', 'o']

append() is used to add an element at the end of the list.

[100]:
lst = [1,1,4,8,7]
lst.append(1)
print(lst)
[1, 1, 4, 8, 7, 1]

count() is used to count the number of a particular element that is present in the list.

[101]:
lst.count(1)
[101]:
3

If you want to combine two lists you can also use the extend() built-in function. See how the append() creates a nested list.

[102]:
lst = [1,1,4,8,7]
lst1 = [5,4,2,8]
lst.append(lst1)
print(lst)
[1, 1, 4, 8, 7, [5, 4, 2, 8]]
[103]:
lst = [1,1,4,8,7]
lst1 = [5,4,2,8]
lst.extend(lst1)
print(lst)
[1, 1, 4, 8, 7, 5, 4, 2, 8]

index() is used to find the index value of a particular element. Note that if there are multiple elements of the same value then the first index value of that element is returned.

[104]:
print(lst.index(1))
print(lst.index(4))
0
2

insert(x,y) is used to insert an element y at a specified index value x.

[105]:
lst.insert(5, 'name')
print(lst)
[1, 1, 4, 8, 7, 'name', 5, 4, 2, 8]

insert(x,y) inserts but does not replace an element. If you want to replace the element with another element you simply assign the value to that particular index.

[106]:
lst[5] = 'Python'
print(lst)
[1, 1, 4, 8, 7, 'Python', 5, 4, 2, 8]

The pop() function returns the last element in the list. This element is also removed from the list

[107]:
element = lst.pop()
print(element)
print(lst)
8
[1, 1, 4, 8, 7, 'Python', 5, 4, 2]

An index value can be specified to pop a certain element corresponding to that index value.

[108]:
lst.pop(0)
[108]:
1

pop() is used to remove an element based on its index value which can be assigned to a variable. One can also remove element by specifying the value of the element using the remove() function.

[109]:
lst.remove('Python')
print(lst)
[1, 4, 8, 7, 5, 4, 2]

An alternative to remove function but with using index value is del

[110]:
del lst[1]
print(lst)
[1, 8, 7, 5, 4, 2]

Python offers built in operation sort() to arrange the elements in ascending order.

[111]:
lst.sort()
print(lst)
[1, 2, 4, 5, 7, 8]

To reverse the order the argument reverse of the method sort can be set to True

[112]:
lst.sort(reverse=True)
print(lst)
[8, 7, 5, 4, 2, 1]
[113]:
elements.sort(key=len)
print(elements)
elements.sort(reverse=True, key=len)
print(elements)
['Air', 'Fire', 'Earth', 'Water']
['Earth', 'Water', 'Fire', 'Air']

Copying a list

Most python newcomers make this mistake so be careful.

[114]:
lista = [2,1,4,3]
listb = lista
print(listb)
[2, 1, 4, 3]

Now we perform some random operations on lista

[115]:
lista.pop()
print(lista)
lista.append(9)
print(lista)
[2, 1, 4]
[2, 1, 4, 9]
[116]:
print(listb)
[2, 1, 4, 9]

listb has also changed though no operation has been performed on it. This is because you have assigned the same memory space of lista to listb. So how do fix this?

If you recall, in slicing we had seen that parentlist[a:b] returns a list from parent list with start index a and end index b and if a and b is not mentioned then by default it considers the first and last element. We use the same concept here. By doing so, we are assigning the data of lista to listb as a variable.

[117]:
lista = [2,1,4,3]
listb = lista[:]
print(listb)
[2, 1, 4, 3]
[118]:
lista.pop()
print(lista)
lista.append(9)
print(lista)
[2, 1, 4]
[2, 1, 4, 9]
[119]:
print(listb)
[2, 1, 4, 3]

Tuples

Tuples are similar to lists with one big difference. The elements inside a list can be changed but in tuples they cannot be changed. Think of tuples as something which has to be True for a particular something and cannot be True for no other values. For better understanding, Recall the divmod() function

[120]:
xyz = divmod(10,3)
print(xyz)
print(type(xyz))
(3, 1)
<class 'tuple'>

Here the quotient has to be 3 and the remainder has to be 1. These values cannot be changed whatsoever when 10 is divided by 3. Hence, divmod returns these values in a tuple.

To define a tuple, A variable is assigned to parenthesis ( ) or the function tuple() is called on the variable.

[121]:
tup = () # this is an empty tuple
tup2 = tuple()

If you want to directly declare a tuple it can be done by using a comma at the end of the data.

[122]:
27,
[122]:
(27,)

27 when multiplied by 2 yields 54, But when multiplied with a tuple the data is repeated twice.

[123]:
2 * (27,)
[123]:
(27, 27)

Values can be assigned while declaring a tuple. It takes a list as input and converts it into a tuple, or it takes a string and converts it into a tuple.

[124]:
tup3 = tuple([1,2,3])
print(tup3)
tup4 = tuple('Hello')
print(tup4)
(1, 2, 3)
('H', 'e', 'l', 'l', 'o')

Tuples follow the same indexing and slicing as Lists.

[125]:
print(tup3[1])
tup5 = tup4[:3]
print(tup5)
2
('H', 'e', 'l')

Mapping one tuple to another

[126]:
(a,b,c) = ('alpha','beta','gamma')
[127]:
print(a,b,c)
alpha beta gamma
[128]:
d = tuple('AGPeterTutorial')
print(d)
('A', 'G', 'P', 'e', 't', 'e', 'r', 'T', 'u', 't', 'o', 'r', 'i', 'a', 'l')

More mapping

Mapping and indexing can be used to switch around elements in tuples. Something that can come in very handy later.

[129]:
a = 0
b = 1
print(a)
0

Execute the next cell a few times.

[130]:
a, b = b, a + b
print(a)

1

The fibonacci sequence was returned.

Built-in tuple functions

The functions count() and index() are working just like they did with lists.

[131]:
print(d.count('a'))
print(d.index('a'))
1
13

Sets

Sets are mainly used to eliminate repeated numbers in a sequence/list. They are also used to perform some standard set operations (Mengenlehre).

Sets are declared with either a sequence in braces { } or the set() function which will initialize an empty set. Also set([sequence]) can be executed to declare a set with elements.

[132]:
set1 = set()
print(type(set1))
<class 'set'>
set0 = set([1,2,2,3,3,4]) print(set0)

Here, elements 2,3 which are repeated twice are seen only once. Thus, in a set each element is distinct.

Built-in functions

[133]:
set1 = {1,2,3}
set2 = set([2,3,4,5])

The union() function returns a set which contains all the elements of both the sets without repetition.

[134]:
set1.union(set2)
[134]:
{1, 2, 3, 4, 5}

add() will add a particular element into the set. Note, that the index of the newly added element is arbitrary and can be placed anywhere not necessarily at the end.

[135]:
set1.add(0)
set1
[135]:
{0, 1, 2, 3}

The intersection() function outputs a set which contains all the elements that are in both sets.

[136]:
set1.intersection(set2)
[136]:
{2, 3}

The difference() function outputs a set which contains elements that are in set1 and not in set2.

[137]:
set1.difference(set2)
[137]:
{0, 1}

The symmetric_difference() function outputs a function which contains elements that are in one of the sets.

[138]:
set2.symmetric_difference(set1)
[138]:
{0, 1, 4, 5}

issubset(), isdisjoint(), issuperset() are functions returning boolean (truth) values. They are used for checking if the set1/set2 is a subset, disjoint or superset of set2/set1, respectively.

[139]:
set1.issubset(set2)
[139]:
False
[140]:
set2.isdisjoint(set1)
[140]:
False
[141]:
set2.issuperset(set1)
[141]:
False

pop() is used to remove an arbitrary element in the set

[142]:
set1.pop()
print(set1)
{1, 2, 3}

The remove() function deletes the specified element from the set.

[143]:
set1.remove(2)
set1
[143]:
{1, 3}

clear() is used to clear all the elements and make that set an empty set.

[144]:
set1.clear()
set1
[144]:
set()

Strings

Strings are ordered text based data which are represented by enclosing them in quotes.

[145]:
String0 = 'Python is awesome'
String1 = "Python is awesome"
String2 = '''Python
is
awesome'''
[146]:
print(String0 , type(String0))
print(String1, type(String1))
print(String2, type(String2))
Python is awesome <class 'str'>
Python is awesome <class 'str'>
Python
is
awesome <class 'str'>

String Indexing and Slicing are similar to Lists which was explained earlier.

[147]:
print(String0[4])
print(String0[4:])
o
on is awesome

Built-in functions

The find() function returns the index of a given value in the string. If not found find() returns -1. Remember to not confuse the returned -1 for reverse indexing value.

[148]:
print(String0.find('on is'))
print(String0.find('am'))
4
-1

The index returned is the index of the first element in the input value.

[149]:
print(String0[4])
o

One can also tell the find() function between which index values it has to search.

[150]:
print(String0.find('i',1))
print(String0.find('i',1,3))
7
-1

capitalize() is used to capitalize the first element in the string.

[151]:
String3 = 'observe the first letter in this sentence.'
print(String3.capitalize())
Observe the first letter in this sentence.

center() is used to center align the string by specifying the field width.

[152]:
String0.center(70)
[152]:
'                          Python is awesome                           '

One can also fill the left out spaces with any other character.

[153]:
String0.center(70,'-')
[153]:
'--------------------------Python is awesome---------------------------'

zfill() is used for zero padding by specifying the field width.

[154]:
String0.zfill(30)
[154]:
'0000000000000Python is awesome'

expandtabs() allows you to change the spacing of the tab character. ’\t' which is by default set to 8 spaces.

[155]:
s = 'h\te\tl\tl\to'
print(s)
print(s.expandtabs(1))
print(s.expandtabs())
h       e       l       l       o
h e l l o
h       e       l       l       o

The index() function works the same way as the find() function. The only difference is find returns ‘-1’ when the input element is not found in the string but the index() function throws a ValueError

[156]:
print(String0.index('Python'))
print(String0.index('is',0))
print(String0.index('is',10,20))
0
7
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[156], line 3
      1 print(String0.index('Python'))
      2 print(String0.index('is',0))
----> 3 print(String0.index('is',10,20))

ValueError: substring not found

The endswith() function is used to check if the given string ends with a specific character.

[157]:
String0.endswith('y')
[157]:
False

The start and stop index values can also be specified.

[158]:
print(String0.endswith('l',0))
print(String0.endswith('M',0,5))
False
False

The count() function counts the number of a character in the given string. The start and the stop index can also be specified or left blank.

[159]:
print(String0.count('a',0))
print(String0.count('a',5,10))
1
0

The join() function is used add a character in between the elements of the input.

[160]:
'-'.join(String0)
[160]:
'P-y-t-h-o-n- -i-s- -a-w-e-s-o-m-e'

The join() function can also be used to convert a list into a string.

[161]:
a = list(String0)
print(a)
b = ''.join(a)
print(b)
['P', 'y', 't', 'h', 'o', 'n', ' ', 'i', 's', ' ', 'a', 'w', 'e', 's', 'o', 'm', 'e']
Python is awesome

Before converting it into a string join() function can be used to insert any char in between the list elements.

[162]:
c = '/'.join(a)[18:]
print(c)
 /a/w/e/s/o/m/e

The split() function converts a string into a list using a substring as an input.

[163]:
d = c.split('/')
print(d)
[' ', 'a', 'w', 'e', 's', 'o', 'm', 'e']

In the split() function one can also specify the number of times one wants to split the string or the number of elements the new returned list should contain. The number of elements is always one more than the specified number this is because of zero based indexing.

[164]:
e = c.split('/',3)
print(e)
print(len(e))
[' ', 'a', 'w', 'e/s/o/m/e']
4

lower() converts any capital letter to small letter.

[165]:
print(String0)
print(String0.lower())
Python is awesome
python is awesome

upper() converts any small letter to capital letter.

[166]:
String0.upper()
[166]:
'PYTHON IS AWESOME'

The replace() function replaces the given substring with another string.

[167]:
String0.replace('awesome','super awesome')
[167]:
'Python is super awesome'

The strip() function is used to delete leading and trailing characters. If no argument is provided it removes whitespaces (space and tab).

[168]:
f = '    hello      '
print(f.strip())
hello

When a character is provided strip() removes all occurrences of this character. If this character is not at the edges of the string is left unchanged.

[169]:
f = '   ***----hello---*******     '
print("This string is for you to see the leading whitespaces.")
print(f.strip('*'))
This string is for you to see the leading whitespaces.
   ***----hello---*******

If more characters are provided all occurrences of these characters are stripped from the string.

[170]:
f = '   ***----hello---*******     '
print(f.strip(' *-'))
hello

The lstrip() and rstrip() function have the same functionality as the strip() function but lstrip() deletes only towards the left side and rstrip() towards the right.

[171]:
print(f.lstrip(' *'))
print(f.rstrip(' *'))
----hello---*******
   ***----hello---

The isalpha() method checks, whether a string contains only letters.

[172]:
'Hello'.isalpha()
[172]:
True
[173]:
s = '112a'
s1 = '1'
print(s.isdigit(), s1.isdigit())
print(s.isnumeric())
print(s.isdecimal())
print(s.isalnum()) # alphanumeric
print('Hello!'.isalnum())
False True
False
False
True
False

Strings are immutable

In contrast to lists, strings are immutable, meaning you can not change parts in a string with assignments, and you can’t change the string in place.

s = 'Hello Spam!'
s[3] = 'T'
# This will throw an error!
[174]:
s = 'Hello Spam!'
s[1] = 'T'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[174], line 2
      1 s = 'Hello Spam!'
----> 2 s[1] = 'T'

TypeError: 'str' object does not support item assignment
[175]:
s = 'Hello Spam!'
_ = s.lower()
print(_, s) # s is unchanged
hello spam! Hello Spam!

If you want to change some letters in a string, you have to first create a mutable object. You can do this with the built-in function list() or converting the string to a bytearray, which is mutable.

[176]:
s = 'Hello Spam!'
s = list(s)
s[1] = 'T'
s = ''.join(s)
s
[176]:
'HTllo Spam!'
[177]:
s = bytearray(b'Hello Spam!')
print(s)
s.extend(b' And Eggs!')
s = s.decode()
print(s)
bytearray(b'Hello Spam!')
Hello Spam! And Eggs!

Interview question with strings

s = 'bob likes dogs'
[178]:
s = 'bob likes dogs'
s = ' '.join(s.split()[::-1])
print(s)
dogs likes bob

Dictionaries

Dictionaries are like a database because here you can index a particular sequence with your user defined string. To declare a dictionary you can use braces { } or the dict() function. The difference to declaring a set with braces is the use of colons in the expression.

[179]:
d0 = {} # Empty dictionary NOT empty set
set0 = set()
print(type(d0))
print(type(set0))
<class 'dict'>
<class 'set'>
[180]:
d0 = {'One': 1, 'OneTwo': 12}
d0['OneTwoThree'] = 123
d0['ThreeFour'] = 34
print(d0)
{'One': 1, 'OneTwo': 12, 'OneTwoThree': 123, 'ThreeFour': 34}

The keys() built-in method gives you the keys of the dictionary.

[181]:
print(d0.keys())
dict_keys(['One', 'OneTwo', 'OneTwoThree', 'ThreeFour'])

To access elements from a dict you use the keys to index the dict.

[182]:
d0['One']
[182]:
1

Two lists which are can be made into a dict using the built-in function zip(). Since python3 the zip() function is not directly executed. Doing so saves computing time by holding the actual execution of the function back until it is really needed.

To make a dictionary out of this zip object you can call the dict() function on it.

[183]:
keys = ['One', 'Two', 'Three', 'Four', 'Five']
numbers = [1, 2, 3, 4, 5]
d2 = zip(keys, numbers)
print(d2)
d2 = dict(d2)
print(d2)
<zip object at 0x7ff8d80dfb40>
{'One': 1, 'Two': 2, 'Three': 3, 'Four': 4, 'Five': 5}

Built-in functions

The clear() function is used to erase the dictionary.

[184]:
d2.clear()
print(d2)
{}

A dict can also be built using loops. Loops is a part of control flow, which we will look at shortly.

[185]:
for i in range(len(keys)):
    d2[keys[i]] = numbers[i]
print(d2)
{'One': 1, 'Two': 2, 'Three': 3, 'Four': 4, 'Five': 5}

The values() function returns a list with the values of the dictionary.

[186]:
d2.values()
[186]:
dict_values([1, 2, 3, 4, 5])

The keys() function gives the keys.

[187]:
d2.keys()
[187]:
dict_keys(['One', 'Two', 'Three', 'Four', 'Five'])

The items() returns a list of tuples, where tuple[0] is the keys and tuple[1] is the value.

[188]:
d2.items()
[188]:
dict_items([('One', 1), ('Two', 2), ('Three', 3), ('Four', 4), ('Five', 5)])

The pop() function is used to get the value of a key and remove that key from the dict.

[189]:
popped_value = d2.pop('Four')
print(popped_value)
print(d2)
4
{'One': 1, 'Two': 2, 'Three': 3, 'Five': 5}

The update() function updates and adds new values.

[190]:
item_prices = {'Banana': 0.5, 'Avocado': 3, 'Bread': 1.5}
new = {'Bread': 2, 'Baguette': 1.5}
item_prices.update(new)
print(item_prices)
{'Banana': 0.5, 'Avocado': 3, 'Bread': 2, 'Baguette': 1.5}

Control Flow

Control Flow defines the order on which a program is executed. The most common control flow elements are the two loops (for and while) and the if-elif-else logic. Additionally, python has more advanced control flow with break, continue, and pass.

If

If statements in python must follow this formatting:

if condition:
    do_this

Note the colon and the indentation in the next line. In python an indentation consists of either a tab character or 4 spaces. No more, no less. Let’s try a very simple example.

[191]:
if True:
    print('This is true')

if False:
    print('This is false')
This is true

Note, how only the first if-statement is executed, because the condition of the second is False. Let’s go one step further and assign a truth value to a variable.

[192]:
a = 12
b = 10
truthvalue = a > b
print(truthvalue)
True
[193]:
if truthvalue:
    print("a is greater than b")
a is greater than b

However, this is not how it’s done most of the time. Most often the condition is directly plugged into the if statement. this would look something like this:

[194]:
x = 12
if x > 10:
    print("Hello")
Hello

If with multiple conditions - Operators

You can use the operators to tailor your if-statements.

[195]:
x = 15
if x > 10 and x <= 20:
    print("variable lies between 10 and 20")
variable lies between 10 and 20
[196]:
x = 2
if not x == 1:
    print("x is not 1")
x is not 1
[197]:
x = 5
if x == 5 or x == 10:
    print("x is either 5, or 10")
x is either 5, or 10

If-else

If an unindented else: follows the indent of an if statement. The indented block below the else statement is executed, if the condition inside the if statement is false.

[198]:
x = 12
if x > 10:
    print("hello")
    print("Multiple lines can be executed here. They all need to be indented")
    a = 2**2
    print(f"With {a} spaces. No more, no less.")
else:
    print("world")
hello
Multiple lines can be executed here. They all need to be indented
With 4 spaces. No more, no less.

If-elif-else

If you want to check for multiple conditions you can use an if-elif-else construction.

[199]:
x = 10
y = 12
if x > y:
    print("x>y")
elif x < y:
    print("x<y")
else:
    print("x=y")
x<y

There is also the possibility to nest if statements. Correct indentation becomes very important here.

[200]:
x = 10
y = 12
if x > y:
    print("x>y")
elif x < y:
    print("x<y")
    if x==10:
        print("x=10")
    else:
        print("invalid")
else:
    print("x=y")
x<y
x=10
[201]:
num = 0

if num > 0:
    print(str(num), "is a positive number")
elif num == 0:
    print("It is a zero")
else:
    print(str(num), "is a negative number")
It is a zero

For loops

The task of repeating all those numbers seemed a bit tedious. Here’s a way to automate this: for-loops. With for loops you can execute the indented block as often as you’d like. for-loops follow a syntax like this:

for i in iterable:
    run code
    run this code also
    run code until iterable is exhausted

Note the colon and the indentation. The iterable in this syntax is an iterable object. Some objects, like lists are iterable and can be plugged into the for-statement. There’s also the built-in function range, which is often used in this context.

[202]:
for i in [0, 1, 2, 3]:
    print(i)
0
1
2
3
[203]:
for i in range(4):
    print(i)
0
1
2
3

Note, how the built-in function takes the integer 4 as input, but only returns 3 as the highest integer. That’s because the range function returns 4 integers, of which 0 is also one. Let’s simplify the last exercise using a for loop.

[204]:
for i in [0,-5,1000,-0.5,28.9]:
    if i > 0:
        print(str(i), "is a positive number")
    elif i == 0:
        print("It is a zero")
    else:
        print(str(i), "is a negative number")
It is a zero
-5 is a negative number
1000 is a positive number
-0.5 is a negative number
28.9 is a positive number

If nested lists are plugged into the for-loop, the outermost list is used as the iterable.

[205]:
list_of_lists = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
for list1 in list_of_lists:
        print(list1)
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]

If you want to access the lists on lower levels, you can write a nested for-loop.

[206]:
list_of_lists = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
for list1 in list_of_lists:
    for x in list1:
        print(x)
1
2
3
4
5
6
7
8
9

For-else

In python you can have an ``else`` statement after the completion of a for-loop to execute something once.

[207]:
for i in [0, 1, 2, 3]:
    print(f"At iteration {i}. This line is executed multiple times.")
else:
    print(f"For loop finished. This line is executed one. Variable i is still {i}")
At iteration 0. This line is executed multiple times.
At iteration 1. This line is executed multiple times.
At iteration 2. This line is executed multiple times.
At iteration 3. This line is executed multiple times.
For loop finished. This line is executed one. Variable i is still 3

While loops

While-loops are similar to for-loops. They execute the indented code. However, while-loops execute code as long as a conditional is true. The syntax looks something like this.

condition = True
while condition:
    execute this code
    execute this also
    change the condition

Notice how the condition is changed inside the while-loop? This a very important part of while-loops. If you don’t change the condition in the while loop, it will be executed indefinitely and eat up a lot of computer resources.

[208]:
i = 1
count_to = 6

while i < count_to:
    print(i)
    i = i + 1
print(f"Loop finished. counted to {i}")
1
2
3
4
5
Loop finished. counted to 6

Break

Break can break for and while-loops. It is often used with an if-statement.

[209]:
for i in range(100):
    print(i)
    if i>=7:
        break
0
1
2
3
4
5
6
7

Else in loops

There is also the possibility to use the else statement in a loop. The code of the else statement is executed, when the loop is terminated through exhaustion of its iterator. It is not executed, if a break is executed.

[210]:
for n in range(2, 10):
    for x in range(2, n):
        if n % x == 0:
            print(f"{n} equals {x} * {n//x}. Breaking the loop.")
            break
    else:
        # loop fell through without finding a factor
        print(n, 'is a prime number. Iterator exhausted.')
2 is a prime number. Iterator exhausted.
3 is a prime number. Iterator exhausted.
4 equals 2 * 2. Breaking the loop.
5 is a prime number. Iterator exhausted.
6 equals 2 * 3. Breaking the loop.
7 is a prime number. Iterator exhausted.
8 equals 2 * 4. Breaking the loop.
9 equals 3 * 3. Breaking the loop.

Floor division.

Continue

Continue continues with the next iteration of the loop.

[211]:
for i in range(10):
    if i>4:
        print("The end.")
        continue
    elif i<7:
        print(i)
0
1
2
3
4
The end.
The end.
The end.
The end.
The end.
[212]:
for i in range(10):
    if i>4:
        print("The end.")
        break
    elif i<7:
        print(i)
0
1
2
3
4
The end.

Pass statements

The pass statement does nothing. It allows you to fulfill the syntax without executing code. Have a look at the next code cell.

[213]:
for i in [0, 1, 2]:
    pass

for i in [3, 4, 5]:
  Cell In[213], line 4
    for i in [3, 4, 5]:
                       ^
SyntaxError: unexpected EOF while parsing

Check the Error-message:

  File "<ipython-input-268-5a99e96149af>", line 4
    for i in [3, 4, 5]:
                       ^
SyntaxError: unexpected EOF while parsing

This tells us, that the second for-loop (the one iterating over the list [3, 4, 5]) has a syntax error. The first for-loop however doesn’t throw an Error. It also does nothing due to the pass statement.

Moving on

Please visit the notebook basics_01_functions_classes.ipynb for further tutorials.