FMP AudioLabs

Python Basics


This tutorial introduces some important aspects of the Python programming language. For more comprehensive introductions and a complete reference, the following links may be useful: - [The Python Tutorial](https://docs.python.org/3/tutorial/index.html) introduces the reader informally to the basic concepts and features of the Python language and system. - The [Scipy Lecture Notes](https://www.scipy-lectures.org/) is a tutorial on the scientific Python ecosystem including libraries such as Numpy, Scipy, and Matploblib. - The Python package [LibROSA](https://librosa.github.io/) provides many building blocks to create music information retrieval systems. It also cotains a [gallery](https://librosa.github.io/librosa/advanced.html) of more advanced examples.

Basic Facts

  • Python is an interpreted (not compiled), open-source, multi-platform programming language.
  • There exist a set of modules for scientific computing (e.g. numpy, scipy, matplotlib, librosa) in Python.
  • Python uses indentation (and not brackets or end-commands) to separate blocks of code.
  • Comment lines start with the character #.
  • Useful function for help:
    • Invoke the built-in help system: help()
    • Lists objects in namespace: dir()
    • Shows global and local variables: globals(), locals()

Data Types

Let us start with some basic facts on Python variables:

  • Variables do not need to be declared; neither their type.
  • Variables are created automatically when they are first assigned.
  • A variable name may contain letters (a, b, ..., Y, Z) and the underscore (_).
  • Variable names are case sensitive.
  • All but the first character can also be positive integer number.
  • Usually one uses lower case letters and underscores to seperate words.

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.

In [1]:
string_variable = 'Welcome to the Python Tutorial'
print(string_variable)
Welcome to the Python Tutorial

Here some basic string formatting:

In [2]:
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)
This is an integer: 17.
This is string1: ABCD and this is string2:   1234.
This is a floating point number: 03.142.

Some basic math:

In [3]:
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)
n + 1 = 4
n - 1 = 2
n * 2 = 6
n / 2 = 1.5
n ^ 2 = 9

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.

In [4]:
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)
Normal division: 4.0
Integer division: 4
Normal division: 1.6
Integer division: 1
Remainder of integer division: 3

For re-assigning a variable, one may use the following conventions:

In [5]:
n = 7
n += 11
print(n)

n *= 2
print(n)

n /= 18
print(n)

n **= 0.5
print(n)
18
36
2.0
1.4142135623730951

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 bracekts. Both are indexed with square brackets (with indexing starting with $0$). The len function gives the length of a tuple or a list.

In [6]:
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))
['I', 'am', 'a', 'list']
('I', 'am', 'a', 'tuple')
I am generated from a tuple and a list
4
4

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:

In [7]:
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])
[-1, 2, 3, 10, 'a', '12', [13, 14]]
[13, 14]

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$.

In [8]:
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])
var_lis[0:3] = [11, 12, 13]
var_lis[1:3] = [12, 13]
var_lis[-1] = 15
var_lis[0:3:2] = [11, 13]
var_lis[0::2] = [11, 13, 15]
var_lis[::-1] = [15, 14, 13, 12, 11]

The following examples shows how the elements of a list or tuple can be assigned to variables (called unpacking):

In [9]:
var_lis = [1, 2]
[a, b] = var_lis
print(a, b)

var_tup = (3, 4)
[c, d] = var_tup
print(c, d)
1 2
3 4

Leaving out brackets, tuples are generated.

In [10]:
a, b = 1, 2
print(a, b)
1 2

The range-function can be used to specify a tuple or list of integers (without actually generating these numbers):

In [11]:
print(range(9))
print(range(1, 9, 2))
range(0, 9)
range(1, 9, 2)

A range can then be converted into a tuple or list as follows:

In [12]:
print(list(range(9)))
print(tuple(range(1, 9, 2)))
print(list(range(9, 1, -1)))
[0, 1, 2, 3, 4, 5, 6, 7, 8]
(1, 3, 5, 7)
[9, 8, 7, 6, 5, 4, 3, 2]

Boolean values in Python are True and False. Here are some examples for basic comparions:

In [13]:
a = 1
b = 2
print(a < b)
print(a <= b)
print(a == b)
print(a != b)
True
True
False
True

The bool function converts an arbitrary value into a boolean value. Here, are some examples:

In [14]:
print(bool('a'))
print(bool(''))
print(bool(1))
print(bool(0))
print(bool(0.0))
print(bool([]))
print(bool([4, 'hello', 1]))
True
False
True
False
False
False
True

There are also other data types in Python, which we do not cover here. These data types includ sets and dictionaries, which are illustrated by the following examples:

In [15]:
s = {4, 2, 1, 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})
print()

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()))
Print the set s: {1, 2, 4}
Union of sets: {1, 2, 3, 4}
Intersection of sets: {2, 3}

Print the dictionary dic: {'a': 1, 'b': 2, 3: 'hello'}
Print the keys of dic: ['a', 'b', 3]
Access the dic via a key: 2
Print the values of dic: [1, 2, 'hello']

Basic Control Structures

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:

In [16]:
n = 2
if n == 2:
    print('True')
else:
    print('False')
True

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.

In [17]:
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()
0-1-2-
a-2-c-def-
a-b-c-d-

A while-loop is written as follows:

In [18]:
a = 0
while a < 5:
    print(a, end='-')
    a += 1
0-1-2-3-4-

Functions

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 seperate 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 succeded by the return value.

In [19]:
def add(a, b=0, c=0):
    print('Addition: ', a, ' + ', b, ' + ', c)
    return a + b + c

print(add(5))
print(add(5, 2, 1))
print(add(5, c=4))
Addition:  5  +  0  +  0
5
Addition:  5  +  2  +  1
8
Addition:  5  +  0  +  4
9

There can also be multiple return values (which are returned as a tuple):

In [20]:
def add_and_diff(a, b=0):
    return a + b, a - b

x = add_and_diff(3, 5)
print(x)
(8, -2)

NumPy

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. In the following, we give some examples.

The NumPy package is imported as follows:

In [21]:
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-funtion provided by numpy:

In [22]:
x = np.array([1, 2, 3, 3])
print(x)
[1 2 3 3]

Each array has a shape:

In [23]:
print(x.shape)
(4,)

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:

In [24]:
x = np.array([[1, 2, 3], [4, 5, 6]])
print(x)
print(x.shape)
[[1 2 3]
 [4 5 6]]
(2, 3)

There are a couple of functions for creating arrays:

In [25]:
print('Array of given shape and type, filled with zeros: %s' % np.zeros(2))
print('Array of given shape and type, filled with ones:\n %s' %  np.ones((2, 3)))
print('Evenly spaced values within a given interval: %s' % np.arange(2, 8, 2))
print('Random values in a given shape:\n', np.random.rand(2, 3))
print('Identity matrix:\n%s' % np.eye(3))
Array of given shape and type, filled with zeros: [ 0.  0.]
Array of given shape and type, filled with ones:
 [[ 1.  1.  1.]
 [ 1.  1.  1.]]
Evenly spaced values within a given interval: [2 4 6]
Random values in a given shape:
 [[ 0.62042216  0.33394766  0.79183659]
 [ 0.05584108  0.68308524  0.69623552]]
Identity matrix:
[[ 1.  0.  0.]
 [ 0.  1.  0.]
 [ 0.  0.  1.]]

Reshaping of an array is possible like follows:

In [26]:
x = np.arange(2 * 3 * 4)
print(x)
print('Shape:', x.shape)

y = x.reshape((3, 8))
print(y)
print('Shape:', y.shape)

y = x.reshape((2, 3, 4))
print(y)
print('Shape:', y.shape)
[ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23]
Shape: (24,)
[[ 0  1  2  3  4  5  6  7]
 [ 8  9 10 11 12 13 14 15]
 [16 17 18 19 20 21 22 23]]
Shape: (3, 8)
[[[ 0  1  2  3]
  [ 4  5  6  7]
  [ 8  9 10 11]]

 [[12 13 14 15]
  [16 17 18 19]
  [20 21 22 23]]]
Shape: (2, 3, 4)

Applied to arrays, many operations are conducted in an element-wise fashion:

In [27]:
x = np.arange(5)
print('x + 1 =', x + 1)
print('x * 2 =', x * 2)
print('x > 2 =', x > 2)
x + 1 = [1 2 3 4 5]
x * 2 = [0 2 4 6 8]
x > 2 = [False False False  True  True]

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:

In [28]:
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))
[[0 1]
 [2 3]]
[[ 2.  2.]
 [ 2.  2.]]
[[ 2.  3.]
 [ 4.  5.]]
[[ 0.  2.]
 [ 4.  6.]]
[[  2.   2.]
 [ 10.  10.]]

Note that arrays and lists may behave in a completely different way. For example, addition leads to the following results:

In [29]:
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))
[0 2 4 6] <class 'numpy.ndarray'>
[0, 1, 2, 3, 0, 1, 2, 3] <class 'list'>

The sum of an array's elements can be computed as follows:

In [30]:
x = np.arange(6).reshape((2, 3))
print(x)
print(x.sum())
print(x.sum(axis=0))
print(x.sum(axis=1))
[[0 1 2]
 [3 4 5]]
15
[3 5 7]
[ 3 12]

There are many ways for accessing and manipulating arrays:

In [31]:
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])
[[0 1 2]
 [3 4 5]]
4
[array([[False, False,  True],
       [ True,  True,  True]], dtype=bool)]
[2 3 4 5]
[3 4 5]
[1 4]

Specifying the data type for an array can be important in some situations.

In [32]:
x = np.arange(-3, 3)
print(x, type(x), type(x[0]), x.dtype)
x = np.sqrt(x)
print(x)     
[-3 -2 -1  0  1  2] <class 'numpy.ndarray'> <class 'numpy.int64'> int64
[        nan         nan         nan  0.          1.          1.41421356]
/home/fzalkow/miniconda3/envs/FMP/lib/python3.6/site-packages/ipykernel_launcher.py:3: RuntimeWarning: invalid value encountered in sqrt
  This is separate from the ipykernel package so we can avoid doing imports until
In [33]:
x = np.arange(-3, 3, dtype='complex')
print(x, type(x), type(x[0]), x.dtype)
x = np.sqrt(x)
print(x)
[-3.+0.j -2.+0.j -1.+0.j  0.+0.j  1.+0.j  2.+0.j] <class 'numpy.ndarray'> <class 'numpy.complex128'> complex128
[ 0.00000000+1.73205081j  0.00000000+1.41421356j  0.00000000+1.j
  0.00000000+0.j          1.00000000+0.j          1.41421356+0.j        ]

Acknowledgment: This notebook was created by Frank Zalkow, Stefan Balke, and Meinard Müller.