This notebook introduces some important aspects of the Python programming language. For more comprehensive tutorials, we refer to the following sources:
numpy
, scipy
, matplotlib
, librosa
) in Python.end
-commands) to separate blocks of code.#
.help()
dir()
globals()
, locals()
Let us start with some basic facts on Python variables:
a
, b
, ..., Y
, Z
) and the underscore (_
).A string is given in single ticks ('
) or double ticks ("
). If there is no other reason, we recommend single ticks. The following code assigns a string to a variable and prints it using the print
-command.
string_variable = 'Welcome to the Python Tutorial'
print(string_variable)
Here some basic string formatting:
print('This is an integer: %d.' % 17)
print('This is string1: %s and this is string2: %6s.' % ('ABCD', '1234'))
print('This is a floating point number: %06.3f.' % 3.14159265359)
Some basic math:
n = 3
print('n + 1 =', n + 1)
print('n - 1 =', n - 1)
print('n * 2 =', n * 2)
print('n / 2 =', n / 2)
print('n ^ 2 =', n ** 2)
Division always results in a floating point number, even if the number is divisible without remainder. (Note that there are differences between Python 2 and Python 3 in using /
). If the result should be an integer (e.g., when using it as an index), one may use the //
operator. The %
yields the remainder.
n = 8
print('Normal division:', n / 2)
print('Integer division:', n // 2)
print('Normal division:', n / 5)
print('Integer division:', n // 5)
print('Remainder of integer division:', n % 5)
For re-assigning a variable, one may use the following conventions:
n = 7
n += 11
print(n)
n *= 2
print(n)
n /= 18
print(n)
n **= 0.5
print(n)
The basic compound data types in Python are lists and tuples. A list is enclosed in square brackets and a tuple is enclosed in round brackets. Both are indexed with square brackets (with indexing starting with $0$). The len
function gives the length of a tuple or a list.
var_lis = ['I', 'am', 'a', 'list']
var_tup = ('I', 'am', 'a', 'tuple')
print(var_lis)
print(var_tup)
print(var_lis[0], var_tup[1], 'generated from',
var_tup[2], var_tup[3], 'and', var_lis[2], var_lis[3])
print(len(var_tup))
print(len(var_lis))
print(type(var_lis))
print(type(var_tup))
What is the difference between a list and a tuple? Tuples are immutable objects (i.e., their state cannot be modified after they are created) and a bit more efficient. Lists are more flexible. Here are some examples for list operations:
var_lis = [1, 2, 3]
var_lis[0] = -1
var_lis.append(10)
var_lis = var_lis + ['a', '12', [13, 14]]
print(var_lis)
print(var_lis[-1])
One can index a list with start, stop, and step values ([start:end:step
). Note that, in Python, the last index value is end-1
. Negative indices are possible with -1
referring to the last index. When not specified, start
refers to the first item, end
to the last item, and step
is set to $1$.
var_lis = [11, 12, 13, 14, 15]
print('var_lis[0:3] =', var_lis[0:3])
print('var_lis[1:3] =', var_lis[1:3])
print('var_lis[-1] =', var_lis[-1])
print('var_lis[0:3:2] =', var_lis[0:4:2])
print('var_lis[0::2] =', var_lis[0::2])
print('var_lis[::-1] =', var_lis[::-1])
The following examples shows how the elements of a list or tuple can be assigned to variables (called unpacking):
var_lis = [1, 2]
[a, b] = var_lis
print(a, b)
var_tup = (3, 4)
[c, d] = var_tup
print(c, d)
Leaving out brackets, tuples are generated.
t = 1, 2
print(t)
a, b = t
print(a, b)
The range
-function can be used to specify a tuple or list of integers (without actually generating these numbers):
print(range(9))
print(range(1, 9, 2))
A range can then be converted into a tuple or list as follows:
print(list(range(9)))
print(tuple(range(1, 9, 2)))
print(list(range(9, 1, -1)))
Boolean values in Python are True
and False
. Here are some examples for basic comparisons:
a = 1
b = 2
print(a < b)
print(a <= b)
print(a == b)
print(a != b)
The bool
function converts an arbitrary value into a boolean value. Here, are some examples:
print(bool('a'))
print(bool(''))
print(bool(1))
print(bool(0))
print(bool(0.0))
print(bool([]))
print(bool([4, 'hello', 1]))
There are also other data types in Python, which we want to mention here. In the following, we introduce sets, which are unordered collections of unique elements. Furthermore, we apply some basic set operations.
s = {4, 2, 1, 2, 5, 2}
print('Print the set s:', s)
print('Union of sets:', {1, 2, 3} | {2, 3, 4})
print('Intersection of sets:', {1, 2, 3} & {2, 3, 4})
s.add(7)
print('Adding an element', s)
s.remove(2)
print('Removing an element', s)
Another convenient data type are dictionaries, which are indexed by keys (rather than by a range of numbers as is the case for lists or arrays). The following code cell gives an examples and introduce some basic operations.
dic = {'a': 1, 'b': 2, 3: 'hello'}
print('Print the dictionary dic:', dic)
print('Print the keys of dic:', list(dic.keys()))
print('Access the dic via a key:', dic['b'])
print('Print the values of dic:', list(dic.values()))
For control structures such as if
, for
or while
one has to use indentations (as part of the syntax). A typical Python convention is to use four spaces. For example, an if
-statement is written as follows:
n = 2
if n == 2:
print('True')
else:
print('False')
The next example shows how to use a for
-loop. Note that an iterable may be specified by a range, a list, a tuple, or even other structures.
for i in range(3):
print(i, end='-')
print()
for i in ['a', 2, 'c', 'def']:
print(i, end='-')
print()
for i in 'abcd':
print(i, end='-')
print()
A while
-loop is written as follows:
a = 0
while a < 5:
print(a, end='-')
a += 1
One defines functions with the def
-keyword. As variable names, function names may contain letters (a
, b
, ..., Y
, Z
) and the underscore (_
). All but the first character can also be positive integer number. Usually one uses lower case letters and underscores to separate words. The following function is named add
. It has three arguments a
, b
, and c
(with b
and c
having a default value). The return
keyword is succeeded by the return value.
def add(a, b=0, c=0):
"""Function to add three numbers
| Notebook: B/B_libfmp.ipynb and
| Notebook: B/B_PythonBasics.ipynb
Args:
a (float): First number
b (float): Second number (default: 0)
c (float): Third number (default: 0)
Returns:
d (float): Sum
"""
d = a + b + c
print('Addition: ', a, ' + ', b, ' + ', c, ' = ', d)
return d
print(add(5))
print(add(5, 2, 1))
print(add(5, c=4))
There can also be multiple return values (which are returned as a tuple):
def add_and_diff(a, b=0):
"""Function to add and substract two numbers
Notebook: B/B_PythonBasics.ipynb
Args:
a: first number
b: second number (default: 0)
Returns:
first: a + b
second: a - b
"""
return a + b, a - b
x = add_and_diff(3, 5)
print(x)
Python has several useful built-in packages as well as additional external packages. One such package is NumPy, which adds support for multi-dimensional arrays and matrices, along with a number of mathematical functions to operate on these structures, see NumPy Reference Manual for details. The NumPy package is imported as follows:
import numpy as np
It is convenient to bind a package to a short name (for example np
as above). This short name appears as prefix when calling a function from the package. This is illustrated by the following array
-function provided by numpy:
x = np.array([1, 2, 3, 3])
print(x)
Each array has a shape, a type, and a dimension.
print('Shape:', x.shape)
print('Type:', x.dtype)
print('Dimension:', x.ndim)
In this example, note that x.shape
produces a one-element tuple, which is encoded by (4,)
for disambiguation. (The object (4)
would be an integer of type int
rather than a tuple.) Multi-dimensional arrays are created like follows:
x = np.array([[1, 2, 33], [44, 5, 6]])
print('x = ', x, sep='\n')
print('Shape:', x.shape)
print('Type:', x.dtype)
print('Dimension:', x.ndim)
There are a couple of functions for creating arrays:
print('Array of given shape and type, filled with zeros: ', np.zeros(2))
print('Array of given shape and type, filled with integer zeros: ', np.zeros(2, dtype='int'))
print('Array of given shape and type, filled with ones: ', np.ones((2, 3)), sep='\n')
print('Evenly spaced values within a given interval: ', np.arange(2, 8, 2))
print('Random values in a given shape: ', np.random.rand(2, 3), sep='\n')
print('Identity matrix: ', np.eye(3), sep='\n')
Reshaping of an array is possible like follows:
x = np.arange(2 * 3 * 4)
print('Shape:', x.shape)
print(x)
y = x.reshape((3, 8))
print('Shape:', y.shape)
print(y)
y = x.reshape((3, 2, 4))
print('Shape:', y.shape)
print(y)
print('Element y[0, 1, 2] = ', y[0, 1, 2])
NumPy allows for giving one of the new shape parameter as -1
. In this case, NumPy automatically figures out the unknown dimension. Note that in the following example the difference between the shape (6,)
and the shape (6, 1)
.
x = np.arange(6)
print('Shape: %6s; dim: %s' % (x.shape, x.ndim))
x = x.reshape(-1, 2)
print('Shape: %6s; dim: %s' % (x.shape, x.ndim))
x = x.reshape(-1, 1)
print('Shape: %6s; dim: %s' % (x.shape, x.ndim))
Applied to arrays, many operations are conducted in an element-wise fashion:
x = np.arange(5)
print('x + 1 =', x + 1)
print('x * 2 =', x * 2)
print('x > 2 =', x > 2)
Similarly, applied to two arrays of the same shape, these operations are conducted in an element-wise fashion. Matrix multiplication is performed by using the function np.dot
:
a = np.arange(0, 4).reshape((2, 2))
b = 2 * np.ones((2, 2))
print(a)
print(b)
print(a + b)
print(a * b)
print(np.dot(a, b))
Note that arrays and lists may behave in a completely different way. For example, addition leads to the following results:
a = np.arange(4)
b = np.arange(4)
print(a + b, type(a + b))
a = list(a)
b = list(b)
print(a + b, type(a + b))
The sum of an array's elements can be computed as follows:
x = np.arange(6).reshape((2, 3))
print(x)
print(x.sum())
print(x.sum(axis=0))
print(x.sum(axis=1))
There are many ways for accessing and manipulating arrays:
x = np.arange(6).reshape((2, 3))
print(x)
print(x[1, 1])
print([x > 1])
print(x[x > 1])
print(x[1, :])
print(x[:, 1])
NumPy offers many different numerical types and methods for type conversion. Specifying the exact type is often important when using packages such as numba
for optimizing machine code at runtime. In the following example, we give an example where a wrong initialization leads to an error when computing the square root of a negative number.
print('=== Initialization with \'int32\' leading to an error ===', flush=True)
x = np.arange(-2, 2)
print(x, x.dtype)
x = np.sqrt(x)
print(x)
print('=== Initialization with \'complex\' ===', flush=True)
x = np.arange(-3, 3, dtype='complex')
print(x, x.dtype)
x = np.sqrt(x)
print(x)