Python basics#

The purpose of this tutorial is to introduce some basic Python syntax, which can help understand Ubermag simulations.

Variables#

Variables (such as the object with name a in the examples below) are created through assignment of a value. We can check the type of the variable using the type() function:

[1]:
a = 10
type(a)
[1]:
int
[2]:
a = 3.14  # decimal point present makes this a float type
type(a)
[2]:
float
[3]:
a = 'Python'  # single or double quotes define a string
type(a)
[3]:
str
[4]:
a = (1, 2, 3)  # tuple: round brackets
type(a)
[4]:
tuple
[5]:
a = ['a', 2, 3.14]  # list: square brackets
type(a)
[5]:
list

Large/small values, e.g. \(a = 2.1 \times 10^{6}\)

[6]:
a = 2.1e-6
a
[6]:
2.1e-06

Basic arithmetic operations#

1. Addition \(c = a + b\)#

[7]:
a = 10
b = 3
c = a + b

To inspect an object, we can just type its name if it is in the last line of a notebook cell:

[8]:
c
[8]:
13

The print() function can be used to send information to the standard output (typically the display):

[9]:
print('The value of c is:', c)
The value of c is: 13

2. Subtraction: \(a - b\)#

[10]:
a - b
[10]:
7

3. Multiplication: \(a \times b\)#

[11]:
a * b
[11]:
30

4. Division: \(a / b\)#

[12]:
a / b
[12]:
3.3333333333333335

In Python 3, if we divide two int variables, we are going to get float:

[13]:
5/2
[13]:
2.5

5. Power \(a^{b}\)#

[14]:
a = 5
b = 3
a ** b  # Common mistake to write a^b
[14]:
125

6. More complicated operations#

Other “more complicated” operations generally live in math or numpy. Before math can be used, it must be imported.

[15]:
import math

All functions living in math or any other module, can be accessed using . operator.

[16]:
theta = 3.14159  # theta is approximately pi
math.sin(theta)
[16]:
2.65358979335273e-06
[17]:
math.cos(theta)
[17]:
-0.9999999999964793
[18]:
math.sin(math.pi/2)
[18]:
1.0
[19]:
a = 10
math.log10(a)
[19]:
1.0
[20]:
math.log(math.e)  # natural log
[20]:
1.0

Sequences: lists and tuples#

A collection of values can be represented using lists and tuples.

[21]:
a = [1, 2, 3, 5.1e4]  # list -> square bracket
a
[21]:
[1, 2, 3, 51000.0]
[22]:
b = (1, 2, 3, 5.1e4)  # tuple -> round bracket
b
[22]:
(1, 2, 3, 51000.0)

Indexing#

[23]:
a[0]  # the first element
[23]:
1
[24]:
a[0] = 5
a
[24]:
[5, 2, 3, 51000.0]
[25]:
b[3]  # the last element
[25]:
51000.0

Alternatively -1 can be used as an index to get the last element

[26]:
a[-1]
[26]:
51000.0

Length (the number of elements in a sequence)#

[27]:
len(a)
[27]:
4
[28]:
len(b)
[28]:
4

What is the difference between list and tuple? Tuples are not mutable.

[29]:
b[2] = 3
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[29], line 1
----> 1 b[2] = 3

TypeError: 'tuple' object does not support item assignment

Unpacking#

If we have a point which is defined as a tuple and want to unpack the values into x, y, and z, we can write:

[30]:
point = (-1, 2, 0)
x = point[0]
y = point[1]
z = point[2]
print(f'x={x}, y={y}, z={z}')
x=-1, y=2, z=0

A more convenient way is:

[31]:
x, y, z = point
print(f'x={x}, y={y}, z={z}')
x=-1, y=2, z=0

Adding an element to the list:

[32]:
a.append('new_element')
a
[32]:
[5, 2, 3, 51000.0, 'new_element']

Sequences: numpy arrays#

Another sequence type that is used a lot in computational work is the ndarray type (n-dimensional array) from the numpy package. We can create a numpy array from other sequences, such as a list:

[33]:
import numpy as np  # by convention `np` is used as the alias for numpy
[34]:
c = np.array([5, 2, 10, 100])
c
[34]:
array([  5,   2,  10, 100])
[35]:
type(c)
[35]:
numpy.ndarray

The ndarray data type has been designed for numeric work. It is fast in execution and allows to carry out the same operation on all elements of the array at the same time:

[36]:
3*c
[36]:
array([ 15,   6,  30, 300])
[37]:
np.sqrt(c)  # element-wise square root
[37]:
array([ 2.23606798,  1.41421356,  3.16227766, 10.        ])
[38]:
c.sum()
[38]:
117

The Ubermag modules, such as discretisedfield, often return numpy arrays.

Dictionaries#

Dictionaries map keys (such as region1) to values (such as 1e-12):

[39]:
d = {'region1': 1e-12, 'region797': 5e-11}
d
[39]:
{'region1': 1e-12, 'region797': 5e-11}

Accessing an element in a dictionary

[40]:
d['region1']  # string in quotes
[40]:
1e-12

Conditional execution#

All lines belonging to one execution branch must be indented.

[41]:
a = 5
b = 4

if a == 5 and b < 10:
    # indented lines
    print("I'm in!")  # single and double quotes
    a += 1  # a = a + 1

a  # output the value
I'm in!
[41]:
6
[42]:
if a == 10:
    print('A')
elif a <= 4:
    print('B')
else:
    print('C')
C

Iteration#

[43]:
for i in [1, 2, 3, 5.1]:
    print(i)
1
2
3
5.1
[44]:
a = [0, 5, 9, 4]
for i in range(len(a)):
    print(f'{a[i]} + 1 = {a[i] + 1}')  # f-string
0 + 1 = 1
5 + 1 = 6
9 + 1 = 10
4 + 1 = 5
[45]:
for i in a:
    print(f'{i} + 1 = {i + 1}')
0 + 1 = 1
5 + 1 = 6
9 + 1 = 10
4 + 1 = 5

Functions#

[46]:
def area(a, b):
    # indented
    return a * b

area(5, 2)
[46]:
10
[47]:
def sum_of_elements(a):
    s = 0
    for i in a:
        s = s + i

    return s

sum_of_elements([1, 2, 3])
[47]:
6

Default values for arguments#

[48]:
def volume(a, b, c):
    return a * b * c

volume(1, 2, 3)
[48]:
6
[49]:
def volume(a, b=2, c=3):
    return a * b * c

volume(1)
[49]:
6

Accessing modules through import#

[50]:
import numpy

numpy.pi
[50]:
3.141592653589793

Often we specify an alias

[51]:
import numpy as np

np.pi
[51]:
3.141592653589793

Common mistakes#

1. No colon#

[52]:
def speed(s, t)
    return s/t
  Cell In[52], line 1
    def speed(s, t)
                   ^
SyntaxError: expected ':'

2. Lack of indentation#

[53]:
def speed(s, t):
return s/t
  Cell In[53], line 2
    return s/t
    ^
IndentationError: expected an indented block after function definition on line 1

3. Mixing incompatible types#

[54]:
a = 10
b = 'a'
a + b
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[54], line 3
      1 a = 10
      2 b = 'a'
----> 3 a + b

TypeError: unsupported operand type(s) for +: 'int' and 'str'

4. Using an undefined variable#

[55]:
my_var + 5
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[55], line 1
----> 1 my_var + 5

NameError: name 'my_var' is not defined

5. Module is not imported#

[56]:
import scipy
scpy.fft.fft()  # typo: scpy instead of scipy
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[56], line 2
      1 import scipy
----> 2 scpy.fft.fft()  # typo: scpy instead of scipy

NameError: name 'scpy' is not defined

Object oriented programming basics#

In Python, everything is an object. Each object contains attributes and methods (functions). When we define an object, using . we can access its different methods. For instance, if we define a string:

[57]:
my_object = "Ubermag"

Now we can access some of its methods:

[58]:
my_object.lower()
[58]:
'ubermag'

We can see all methods and attributes of an object using the dir() function:

Getting help#

In Jupyter notebooks, it is often enough to append a question mark to the function name:

[59]:
import math
math.sqrt?
Signature: math.sqrt(x, /)
Docstring: Return the square root of x.
Type:      builtin_function_or_method

Alternatively, use the help() function to get more information about an object or method:

[60]:
help(math.sqrt)
Help on built-in function sqrt in module math:

sqrt(x, /)
    Return the square root of x.

Further reading#