Brief Introduction to Python for CMPUT 466/566

Table of Contents

1 Installing Python and the Required Packages

In this course we will be using python3 for all course related code and examples. For details on how to install python3 on your preferred platform see this link or any of the following:

The default method for installing packages is through a program called pip which will come with your installation of python. In the following examples we will assume your version of pip is associated with a python3 installation (which you will possibly need to call through pip3 depending on your platform). If you are running into issues see a TA!

To install a package input the following command into your command line or terminal.

    pip install numpy

This will install numpy, which is a python library for linear algebra computations (you will need this for some of the below examples).

1.1 Using Virtual Environments

Virtual environments silo different project's dependencies to avoid conflicts and maintain proper version control. There are a number of places you can go to take advantage of virtual environments:

  • pyenv
  • pyvenv
  • virtualenv
  • Anaconda/Miniconda (conda)
  • docker
  • etc…

If you run into problems with setting up a virtual environment, or want to see what others do either ask the TAs or your peers. There are a lot of ways of setting up your working environment as there is no good default (maybe pyvenv?). There are also a number of resources on setting up virtual environments online:

Note: for MacOS users. If you use `pyvenv` you may run into issues with Matplotlib not working. I recommend using miniconda if you have problems.

1.2 Jupyter

Another useful tool is a piece of software called jupyter. Jupyter provides a platform for interactively running code and visualizing results. There are pros and cons with relying on jupyter notebooks for machine learning research. In this course we focus on a command line work flow for assignments (and require assignments to be returned in this format), but encourage students to explore many types of working environments as they develop their research pipeline. If you have any questions about using jupyter, or are curious about some of the issues which arise when using notebooks for general research ask one of the TAs (surprise we all have different opinions on this)!

1.3 Packages required for the course:

2 Introduction to basic python syntax

2.1 Importing Libraries

In the top of all your files you should import the libraries you are using in the current file. There are a few ways to import modules and functions. A subset (the subset we recommend) is as follows:

import math # This imports all of the math module with the namespace math.
import numpy as np # This imports all of numpy with the namespace np
from csv import reader # This imports the reader function from the module csv

You may be tempted to import all functions into your current names space using the from csv import *. We highly recommend against this as it will increase the potential for names to conflict (which may cause you to call the wrong function).

2.2 Functions

In the tradition of learning a new language, lets start with defining a function. A function can be thought of as a mapping from inputs to outputs. In python the syntax for defining a function is as follows:

def greeting():
    print("Hello, World!")
greeting()
Hello, World!



Key attributes are the `def` keyword, and the colon at the end of the first line. These are special indicators to tell python that the following indented code is apart of a function called `greeting` which takes no arguments. If we want to have our function work on an argument we do the following:

def another_greeting(name):
      print("Hello, {0}!".format(name))

another_greeting("Doctor")
Hello, Doctor!



When we run this function with the call: another_greeting("Doctor") we should see the line "Hello, Doctor!" printed on the results line. For this course it is important to understand how python manages the underlying references in a function call (i.e. what is copied and what is passed by reference). Python's default is to pass an object (or any complex structure) as a reference. This means the function can modify this argument, causing a side effect (i.e. your outer-scope state could change by calling a function). Primitive types (such as Integers and Floating point numbers) are passed by a copy operation and won't be effected by the internal operations of a function. The following example shows this:

complex_datastructure = [1, 2, 3, 4, 5]
base_type = 0.1

def test_references(cd, bt):
    cd[2] = 100
    bt = 0.4

print("Pre-Function:")
print("Datastructure: ", complex_datastructure)
print("Basic Type: ", base_type)

test_references(
    complex_datastructure,
    base_type)

print("Post-Function")
print("Datastructure: ", complex_datastructure)
print("Basic Type: ", base_type)

Pre-Function:
Datastructure:  [1, 2, 3, 4, 5]
Basic Type:  0.1
Post-Function
Datastructure:  [1, 2, 100, 4, 5]
Basic Type:  0.1



Functions in python can also take an unknown number of positional arguments and keyword arguments. The following two functions give some example on their use, but are not extensive in their treatment.

def args_and_kwargs(*args, **kwargs):
    for arg in args:
        print("another arg through *args :", arg)
    for kwarg in kwargs:
        print("another kwarg through **kwargs", kwarg, kwargs[kwarg])

def safe_get_kwargs(**kwargs):
    opt_x = kwargs.pop('x', 100)
    opt_y = kwargs.pop('y', 20)

    print("x={0}, y={1}".format(opt_x, opt_y))

    for kwarg in kwargs:
        print("another kwarg through **kwargs", kwarg, kwargs[kwarg])

You can run the above functions with the following:

print("Args and Kwargs:")
args_and_kwargs("these", "are", "positional", "args", x="kwargs", y="are", z="fun", a="too!")
print("")
print("Safe Get Args:")
safe_get_kwargs(x=55, z=10239)
Args and Kwargs:
another arg through *args : these
another arg through *args : are
another arg through *args : positional
another arg through *args : args
another kwarg through **kwargs x kwargs
another kwarg through **kwargs y are
another kwarg through **kwargs z fun
another kwarg through **kwargs a too!

Safe Get Args:


2.3 Classes

Python is an object oriented language at its core, which means we have the ability to define custom types as classes and make use of inheritance! Lets first start with a simple class definition.

class MyClass:
    def __init__(self, x):
        print("Class Constructor")
        self.x = x
    def __str__(self):
        print("Creating string from object of type myclass")
        return "MyClass: {0}".format(self.x)
        # return "MyClass: " + str(self.x)
    def my_print(self):
        print(str(self))

The above example declares a class with the name MyClass. The __init__ function defines the object constructor. When you run the line myc = MyClass(10) you will instantiate an object of type MyClass with a member x with the value 10. The __str__ function is another special function, which is called when you run str(myc). This makes printing classes convenient as you can see in the my_print method. The __str__ method also shows a convenient way to format strings inline.

myc = MyClass(10)
print(myc)
myc.my_print()
Class Constructor
Creating string from object of type myclass
MyClass: 10
Creating string from object of type myclass
MyClass: 10



2.3.1 Inheritance

While we can do a lot with just a single layer of classes, inheritance allows more code to be reused (or at least in theory). We give a simple example of a Student class which inherits from Person. We will not go into too much detail in this document, but a fuller explanation can be found here.

class Person:
    def __init__(self, fname, lname):
        self.first_name = fname
        self.last_name = lname
    def __str__(self):
        return "{0} {1} is a normal person".format(
            self.first_name, self.last_name)

class Student(Person):
    def __init__(self, fname, lname, stress):
        super().__init__(fname, lname)
        self.stress_level = stress
    def work(self):
        self.stress_level += 1
    def relax(self):
        self.stress_level = max(0, self.stress_level-1)
    def get_stress_level_str(self):
        sl = self.stress_level
        if sl == 0:
            ss = "infinitesimally stressed"
        elif sl <= 3:
            ss = "barely stressed"
        elif sl <= 6:
            ss = "moderately stressed"
        elif sl <= 9:
            ss = "highly stressed"
        elif sl <= 12:
            ss = "extremely stressed"
        else:
            ss = "dead."
        return ss
    def __str__(self):
        return "the student, {0} {1}, is {2}".format(
            self.first_name,
            self.last_name,
            self.get_stress_level_str())

luke = Student("Luke", "Skywalker", 0)
for i in range(0,14):
    print(luke)
    luke.work()

vader = Person("Darth", "Vader")
print(vader)
the student, Luke Skywalker, is infinitesimally stressed
the student, Luke Skywalker, is barely stressed
the student, Luke Skywalker, is barely stressed
the student, Luke Skywalker, is barely stressed
the student, Luke Skywalker, is moderately stressed
the student, Luke Skywalker, is moderately stressed
the student, Luke Skywalker, is moderately stressed
the student, Luke Skywalker, is highly stressed
the student, Luke Skywalker, is highly stressed
the student, Luke Skywalker, is highly stressed
the student, Luke Skywalker, is extremely stressed
the student, Luke Skywalker, is extremely stressed
the student, Luke Skywalker, is extremely stressed
the student, Luke Skywalker, is dead.
Darth Vader is a normal person


2.3.2 Running Code from command line/terminal

You may notice in some of the files we provide, there is a if statement following the main body of code. Usually following the below general syntax:

if __name__ == "__main__":
    # More Code goes here
    # .....
    # .....
    main(some_arguments)

This statement is telling python to only run the code after the conditional if the file is called through python my_file.py. If you have some code in this file that you want to import into another file, you can do so without the main(some_arguments) code being run!

3 Numpy and Numerical Computing in Python

Now that we have the basics of python syntax down we can discuss numpy operations and why numpy is imperative to mathematical computing with python. Whenever using numpy you will need to import the module, which is typically done with the name np (as above and below)

import numpy as np # import numpy
import random # imported for random numbers in the below example

Numpy is a module which wraps BLAS operations written in c (or fortran). This allows python to perform linear algebra operations much more efficiently than doing these operations in the python interpretor. Take for instance a simple inner product between two vectors:

def my_inner_product(l1, l2):
    res = 0.0
    for idx, v in enumerate(l1):
        res += v*l2[idx]
    return res

list_1 = [random.random() for i in range(0,1000)]
list_2 = [random.random() for i in range(0,1000)]

%time my_inner_product(list_1, list_2)

a1 = np.random.random(1000)
a2 = np.random.random(1000)

%time np.dot(a1, a2)

You should see np.dot significantly outperform my_inner_product in terms of performance, and as we get into more complex operations the pure python approach will become considerably burdened by the interpretor. And you will even see np.dot outperform my_innter_product when you past the python lists as arguments (i.e. np.dot(list_1, list_2)). This means you should never do math in pure python and instead favor numpy (or other fully-featured linear algebra packages).

Author: Matthew Schlegel

Created: 2019-09-22 Sun 14:31

Validate