MTRN4110 Python Workshop

by MTRNSoc

Code Repository

Topics Covered

  • Variables and Types
  • Strings
  • Lists and Loops
  • Tuples
  • Functions
  • Type Hints
  • Dictionaries
  • Numpy

What is Python?

  • Interpreted
  • Object-oriented
  • Dynamic typing
  • Garbage collected
  • Scripting language
  • Whitespace instead of { }

 

An interpreted language is where the source code is converted into bytecode which is then executed by a Python virtual machine.

Variables and Types

Variables and Types

Python is dynamic typed, which means that its variables, parameters, and return values of a function is not specified at run-time

# 'a' is of type int
a = 1
print(type(a)) # <class 'int'>

# # 'b' is of type float
b = 1.0
print(type(b)) # <class 'float'>

# 'c' is of type str
c = "a"
print(type(c)) # <class 'str'>

# 'd' is of type str
d = 'mtrn4110'
print(type(d)) # <class 'str'>

# 'e' is of type bool
e = True
print(type(e)) # <class 'bool'>

Strings

Strings

name = "Giraffe"
age = 18
height = 2048.11  # mm

# Different ways of printing the same code
# Giraffe, 18, 2048.11
print(name + ", " + str(age) + ', ' + str(height))  # string concatenation
print(name, age, height, sep=', ')
print(f"{name}, {age}, {height}")  # f-strings

Strings are immutable objects.

There are many different ways of formatting strings

String Methods

base = "Hi from mtrnsoc"

base_as_list = base.split(" ")
print(base_as_list)
# ['Hi', 'from', 'mtrnsoc']

print(base[0])
# H (first character)
print(base[-3])
# s (3rd last character)

# [start:stop:step]
# start: It is the index from where the slice starts. The default value is 0.
# stop: It is the index at which the slice stops.
# 		The character at this index is not included in the slice.
#       The default value is the length of the string.
# step: It specifies the number of jumps to take while going from start to stop.
#       It takes the default value of 1.

print(base[3:6:1])
# fro

There are also many types of in-built string methods that available to you

Conditions

Conditions

number = 42

if number == 42:
    print("42 is the meaning of life")
elif number % 2 == 0:
    print("Number is even")
else:
    print("Number is odd")

if number == 42 and number % 2 == 0:
    print("42 is the meaning of life")
    print("number is also even")

if not number == 42:
    print("Number is not 42")

If statements are written slightly different in Python.

  • Use `and` instead of &&
  • Use `or` instead of ||
  • Use `not` instead of !

Lists and Loops

Lists and Loops

names = ["Tanvee", "Alvin", "Juliet"]
names.append("Brandon")
print(names) # list is now ['Tanvee', 'Alvin', 'Juliet', 'Brandon']

for name in names:
    print(name)

# This prints
# Tanvee
# Alvin
# Juliet
# Brandon

# Concatenating lists
names += ["Kyra", "Iniyan"]

Lists are an ordered collection of data. They are very similar to vectors in C++. You can think of them as dynamically sized arrays.

Lists and Loops

names = ["Tanvee", "Alvin", "Juliet", "Kyra", "Iniyan"]

# Printing using indexes, like an array in c++
for i in range(0, len(names)):
    print(names[i])

# Indexing is always dangerous, but if you need
# to know the index, this would be the better way

for index, name in enumerate(names):
    print(f"{index} {name}")

# 0 Tanvee
# 1 Alvin
# 2 Juliet
# 3 Brandon
# 4 Kyra
# 5 Iniyan

You can also loop through strings

Tuples

Tuples

  • Tuples are used to store multiple items in a single variable.
  • They are immutable (once created, you can't change values)
  • To change it, you can convert the tuple to a list
  • They are usually used when you have to return more than 1 value in a function
t = ('MTRNSoc', '2022')
print(t)
# ('MTRNSoc', '2022')

tList = list(t)
tList[-1] = '2023'  # the index -1 accesses the last item
print(tList)
# ['MTRNSoc', '2023']

Functions

Functions

  • Functions in python don't have an explicit return type
  • If you have a function definition with no content, use the pass keyword to avoid getting an error
def my_fun(x, y):
    z = x + y
    return z

# these types don't actually do anything
# when the code is run.
# Purely for IDEs/editors
def my_typed_fun(x: int, y: int) -> int:
    z = x + y
    return z

Dictionaries

Dictionaries

  • Dictionaries are used to store data values in key:value pairs
  • Unlike lists and arrays (C++) that are referenced by their integer index, dictionaries are referenced by their key that maps to a value.
  • They are most similar to map and unordered_map in C++

Dictionaries

userData = {}
userData["name"] = "Leonard"
userData["age"] = 20
userData["height"] = "180cm"

print(userData)
# {'name': 'Leonard', 'age': 20, 'height': '180cm'}

# alternatively, you can construct a dictionary like this
userData2 = {
    'name' : 'Janice',
    'age' : 129,
    'height' : '180cm'
}
print(userData2)
# {'name': 'Janice', 'age': 129, 'height': '180cm'}

randomDict = {
    1: "hello",
    2: "world"
}

Dictionaries

Fetching Data using .get(). Takes in 1 argument and 1 optional argument.

  • First argument: Key to look up
  • Second argument: Default value if key was not found
userData = {
    'name' : 'Janice',
    'age' : 129,
    'height' : '180cm'
}

print(userData.get("name"))
# janice

print(userData.get("zID", None))
# None (similar to null)

Dictionaries

Looping through a dictionary

userData = {
    'name' : 'Janice',
    'age' : 129,
    'height' : '180cm'
}

print(userData.items())
# dict_items([('name', 'Janice'), ('age', 129), ('height', '180cm')])
# List of tuples that we can destructure in the loop

for key, value in userData.items():
    print(f"{key}: {value}")

# name: Janice
# age: 129
# height: 180cm

Type Hints

Type Hints

You can optionally write type hints in Python. These type hints don't actually do anything when you run the code. They are ignored by the python interpreter. 

They are only used by code editors and by a type checker called "mypy"

number: int = 42
number = "hello" # should error

def add_numbers(a: int, b: int) -> int:
    return a + b

some_string: str = "Hello World"
some_string = add_numbers(1, 2) # should error

Type Hints

Fancier type examples

import json
from typing import Dict, Optional, List, Union

numbers: List[Union[int, float]] = [1, 2.2, 3, 4, 5.0]
# List of numbers that can be int or float


class Packet:
  """The structure of packets for both receiver and sender"""

  def __init__(self):
    """Initial data setup"""
    self.flags: Dict[str, bool] = {
        # Type describes a dictionary that maps strings -> booleans
        "syn": False,
        "ack": False,
        "fin": False,
    }
    self.seq: Optional[int] = None
    self.ack: Optional[int] = None
    # Optional type as in the type can be None or an int
    self.data = None

Modules

Modules

Each python file is its own module. A python file can import modules in order to re-use code

def add(x: int, y: int) -> int:
    return x + y
import add
# import everything from add

from math_solvers import subtract, multiply
# This import is preferred as it only imports what
# it uses

print(add.add(1,2))
print(subtract(1,2))
print(multiply(1,2))
def subtract(x: int, y: int) -> int:
    return x - y

def multiply(x: int, y:int) -> int:
    return x * y

add.py

math_solvers.py

calculators.py

__name__

When code is imported from another python file/module, all global scoped code is run.

To prevent this, we have to wrap any globally scoped code inside this condition statement in fileB 

def do_something_A():
    print("File A function called")

print("Global print from fileA")
def do_something_B():
    print("File B function called")

if __name__ == "__main__":
    # This statement is only ever true
    # if fileB.py is directly run
    print("print from fileB inside condition")
import fileA
import fileB

fileA.do_something_A()
fileB.do_something_B()

fileA.py

fileB.py

controller.py

pip3 packages

pip3 packages

One of python's greatest advantages compared to C++ is the easy availability of published packages.

These packages can be installed through something called pip3. Some examples of packages are:

  • mypy: type checking
  • numpy: scientific computing/maths
  • scipy: math algorithms
  • Flask: webserver
  • pandas: data manipulation
  • virtualenv: creating virtual environments

These packages can be installed by running the command:

pip3 install package_name

virtualenv

By default pip3 installs Python packages globally, virtualenv allows you to create an isolated Python environment where packages are installed in this isolated environment.

pip3 install virtualenv

Install the virtualenv package globally

Create a folder for the isolated Python environment

source venv/bin/activate

Use the isolated python environment. This command would have to be done every time you open a new instance of a terminal

Requirements.txt

A requirements.txt is a file that exists that specifies what packages need to be installed in order to run the python code.

We can freeze and dump the packages we have currently installed into the txt file

 pip3 install -r requirements.txt

We can install the packages based on the txt file

pip3 freeze > requirements.txt

Requirements.txt

astroid==2.11.7
autopep8==1.6.0
dill==0.3.5.1
isort==5.10.1
lazy-object-proxy==1.7.1
mccabe==0.7.0
mypy==0.961
mypy-extensions==0.4.3
platformdirs==2.5.2
pycodestyle==2.8.0
pydocstyle==6.1.1
pylint==2.14.4
snowballstemmer==2.2.0
toml==0.10.2
tomli==2.0.1
tomlkit==0.11.1
typing_extensions==4.3.0
wrapt==1.14.1

NumPy

NumPy

  • It is a Python library used for working with multi-dimensional arrays and matrices
  • Easy to use and feature packed
  • Most of the algorithms are actually optimised C code

NumPy Arrays vs Lists

  • NumPy arrays use significantly less memory especially in high dimensions
  • Because most of the array functions execute from compiled C, they operate much faster
  • Many packages use NumPy and have optimised algorithms built around arrays
  • NumPy arrays cannot store different types in one array

NumPy Arrays

# import the library
import numpy as np

# Create array
list_2 = np.array([5, 2, 1, 8, 3, 4])  # create a one dimensional array list_2
print(type(list_2))                    # print the type
print(list_2.shape)                    # print the shape of list_2
print(list_2[2])                       # access third item

# Slicing and indexing
print(list_2[2:4:1])          # '2' is starting index, '4' is stopping index, '1' is the step

list_3 = np.array(([1, 2, 3], [3, 4, 5])) # create a two dimensional array list_3
print(list_3[1][2])                       # print '5' 
import numpy as np

zero_array = np.zeros(2)	# [0, 0]

one_array = np.ones(3)		# [1, 1, 1]

empty_array = np.empty(4)	# Not actually empty

r_array = np.arange(0, 10, 1)	# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

l_array = np.linspace(0, 10, 5)	# [0, 2.5, 5, 7.5, 10]

Array Creation Methods

import numpy as np

arr = np.array([2, 1, 5, 3, 7, 4, 6, 8])

# Sorting an array (there are many sorting algos)
sorted_arr = np.sort(arr) 		# [1, 2, 3, 4, 5, 6, 7, 8]

# Concatenating arrays
np.concatenate((arr, sorted_array))

x = np.array([[1, 2], [3, 4]])
y = np.array([[5, 6]])

z = np.concatenate((x, y), axis=0)

# z = [[1, 2],
#      [3, 4],
#      [5, 6]]

Array manipulation

Jupyter Notebooks

What is a Jupyter Notebook

  • Jupyter notebooks are convenient way to write code for scientific or engineering use
  • Notebooks contain both code and rich text elements
  • They are aimed to be human readable documents that contain analysis description and execution

What is a Kernel?

  • When a notebook runs a section of code, all the variables that are created are stored in memory
  • Variables can be used across code segments
  • The kernel manages the variables and python environment