CpE Tutorial: Programming with Python

Link to CpE Video Channel

Tutorial Slides

Table of Contents

This page contains supplemental material for the Computer Engineering department tutorial “Programming with Python”. You can use any IDE you like, but since we will be using PyCharm in our classes, it is recommended that you start by reading the instructions for downloading and setting up PyCharm and its dependencies. If you already have installed the development environment, you may skip to the following section.

Setting up PyCharm

  1. If you do not have it installed already, go to the official Python website and download the latest stable version of Python for your operating system.

  2. Install Python by executing the downloaded file. Make sure to check the box for “Add Python 3.8 to Path” if it is not checked already. Keep in mind the location where Python will be installed as you might need it later.

  3. Go to the JetBrains website and download either the community or professional (recommended) version of PyCharm.

  4. Install PyCharm by running the downloaded executable. During installation, you can choose the path to your default destination folder where all your projects will be saved.

  5. If you chose to download the professional version of PyCharm, you need register it using your KU email account by clicking here and submitting the filled out form.

  6. To create a new project in PyCharm:

    • Go to File → New Project…
    • Choose Pure Python as your project type and enter the location where you want to save your project.
    • In the same window, choose a new environment using “Virtualenv” and the base interpreter (which should point to the location you chose to install Python in Step 2).
    • Click on Create.

Your First Program

Writing a “Hello World” program is quite straightforward since the Python interpreter does most of the work behind the scenes. No need for boilerplate code.

# Your first program: hello.py
print("Hello World!")

Variables and Datatypes

This section demonstrates some of the different data types you can assign to variables as well as a few ways to manipulate them. This is by no means an exhaustive list of all the supported operations. Click here to go to the official documentation for a complete reference of all the possible data types and functions/operations that they support.

Numeric Data Types

# Numeric data types assignment 
x = 10
y = 3
x_float = 10.5

# Integer arithmetic 
z1 = x + y * 10

# Integer arithmetic but the result is casted to a float (due to the / operation)
z2 = (x / 5 + 1) ** 2 - y

# Integer arithmetic with the floored quotient operator // (result is an integer)
z3 = (x // 5 + 1) ** 2 - y

# Type conversion
print(type(z1))
z4 = int(z2)
z5 = float(z3)

# Setting an integer using hex representation
x_int_hex = 0x6A

Strings

# Defining a new string
my_str = "This is my first string"

# Mutli-line string definition
my_str_multi = "This is a very very " \
               "long string"

# Accessing strings
my_str[3]           # Returns 's'
my_str[11:16]       # Returns 'first'

# Concatenate strings
my_str2 = " and this is my second string"
my_str3 = my_str + my_str2
my_str4 = "".join([my_str, my_str2])

# Finding the length of a string
str_length = len(my_str)

# Formatting strings
formatted_str = "The elapsed time is {} minutes and {} seconds".format(2, 30)

# Finding the position of a substring
formatted_str.find("seconds")

Example: Computing the volume of a cylinder

# Import the math module
import math

rad = input("Please enter the radius value (m): ")
height = input("Please enter the height (m): ")
rad_int = int(rad)
height_int = int(height)

vol = math.pi * (rad_int ** 2) * height_int
print("Volume of the cylinder is: {} m^3".format(vol))

Lists

The List data type is one of the principal array-like structures in Python used for storing and manipulating sequences of data. They are mutable (you can add and modify items in the list) and indexed.

# Defining a new list
my_list = [2, 4, 6, 8, 10]

# Indexing a list
my_list[3]          # Returns 8
my_list[2:4]        # Returns [6,8]
my_list[-2:]        # Returns [8,10]

# Changing the contents of a list
my_list[:4] = [5, 7]

# Appending to a list
my_list.append(12)
my_list.extend([15, 20])

# Removing from a list
del my_list[0]
my_list.remove(10)

# Sort a list
my_list.sort()

Dictionaries

The Dictionary data type is a hashtable-like structure that allows you to create an unordered, mutable list of key-value pairs. Indexing using any valid key in the dictionary returns the value corresponding to this key.

# Defining a new dictionary
my_dict = {'apple': 5, 'orange': 3, 'cookie': 2}

# Accessing dictionaries
my_dict["apple"]

# Adding new key/value pairs
my_dict["tomato"] = 8

# Removing a key
my_dict.pop("apple")

# Getting a list of all keys
list(my_dict.keys())
sorted(my_dict.keys())

Control Structures

In addition to writing programs consisting of only sequential statements, Python allows you to control the flow of your program using various constructs. These control structures change how your programs behave based on the evaluation result of conditional Boolean expressions. There exists selection statements (if, elif, else…) that choose one out of several possible branches to take and repetition statements (while, for) that repeat a section of code as long as some Boolean expression is satisfied. The following are some examples.

Example: Login Module

name = input("Please enter your name: ")
eid = int(input("Please enter your ID: "))

# Decide the role
if eid < 100:
    role = "a manager"
elif 200 <= eid <= 250:
    role = None
else:
    role = "an employee"

# Print the message for authorized users
if role is None:
    print("Access Denied")
else:
    print("Welcome to the system {}. You are {}".format(name, role))

Example: Printing Even Numbers Only

# Initialize loop and sum variables
n = 10
n_sum = 0

# Repeat until you reach the end condition (n == 40)
while n < 40:
    # If n is divisible by 2 (i.e. the quotient of n/2 is 0)
    if n % 2 == 0:
        # Print the even number and add it to the sum
        print(n, end=' ')
        n_sum += n
    # Increment loop counter
    n = n + 1

print()
print("Sum of values: ", n_sum)

Example: For-loop over lists and dictionaries

# Set up the list of all customer names
customers = ["Ahmad", "Fatma", "Khalid"]

# Loop over the list and print the message
for name in customers:
    print("Thank you for subscribing, {}".format(name))

# Create the dictionary of grocery items
grocery = {"apples": 3, "oranges": 7, "cookies": 4}
for k in grocery:
    print("I need {} {}".format(grocery[k], k))

Example: Printing Even Numbers Only (using for)

n_sum = 0
# Repeat for all numbers in the range 10 -> 40 (excluded) and skip every other number
for n in range(10, 40, 2):
    print(n, end=' ')
    n_sum += n

print()
print("Sum of values: ", n_sum)

Exercises

  1. Write a program that requests a string from the user and, if the number of characters in the string is less than 10, would output “Please enter a longer string”. Furthermore, if the string contains 2 repeating characters (not necessarily in consecutive order) output “Please only enter unique characters”.

  2. Write a script that examines a list of numbers then, for each number in the list, if the number is even it adds 5 to that number whereas if the number is odd it subtracts 5 from this number. Print the updated list to the console.

  3. Write a program that accepts a sequence of input strings from the user of the form “item x” where x is any integer representing the number of that item. Then create a dictionary containing the items as keys and the number as the value. Finally output the sum of all the values in the dictionary.

Functions

Functions are blocks of code that encapsulate a certain piece of functionality. They are defined once (either in or outside of the main script file) and can be called multiple times to execute the code defined within the function. They can accept one or more inputs as arguments to be operated on and can optionally return a value. Functions are useful in facilitating modularity, resuablity, and maintainability of your code.

Example: Function for calculating volume of a cylinder

import math

# Function definition
def calc_vol(r, h):
    r_squared = r ** 2
    v = math.pi * r_squared * h
    return v

# Calling the function 
vol = calc_vol(3, 10)
print("Volume of the cylinder is: {} m^3".format(vol))

Example: Program for computing student grades

"""grade_functions.py: A module containing functions to compute grades"""

def total_grade(grades, weights):
    """A function that calculates the final grade"""
    total = 0

    # Iterate over a list of tuples made up by joining two lists
    for grade, weight in zip(grades, weights):
        total += grade * weight

    # Return the weighted total
    return total / sum(weights)

def letter_grade(total=0):

    if total >= 90:
        letter = "A"
    elif 80 <= total < 90:
        letter = "B"
    elif 70 <= total < 80:
        letter = "C"
    else:
        letter = "F"

    return letter
"""student_grade.py: the main file that calls the grade functions"""
import grade_functions

my_grades = [80, 90, 100]
my_weights = [30, 20, 50]

total = grade_functions.total_grade(weights=my_weights, grades=my_grades)
letter = grade_functions.letter_grade(total)

Exercises

  1. Write a function called fibonacci that takes as input an integer $n$ and returns a list containing the first $n$ Fibonnaci numbers. If the user did not enter any input, set the default value of $n$ to be 1. Recall that the Fibonnaci sequence is defined using the recurrence $f_n = f_{n-1} + f_{n-2}$ for $n > 1$ where $f_0 = 0$ and $f_1 = 1$

  2. ROT13 is a substitution cipher that is used to obscure words (in an easily reversible manner). Given a string of characters, ROT13 offsets each character in the string by 13 places ahead in the alphabet. For example, “hello” is transformed into “uryyb” because ‘u’ is 13 places ahead of ‘h’ and ‘r’ is 13 places ahead of ‘e’ and so on. Write a function that implements ROT13 and another function that reverses ROT13. Call them in your main file to encode/decode strings of your choice.

Classes and Objects

Classes provide a way for defining the structure of new data types such that objects of that new type can be instantiated. Such instances of the class have attributes attached to them (defined by the class) that are used for maintaining the instance's state. Furthermore, operations that are defined by the class can be used to change the state of any instance of this class.

Example: Student Class

"""This module contains all the classes related to university personnel"""


class Student:
    """Simple Student class"""

    # Class attributes (variable)
    abbrev = "KU"

    # Constructor
    def __init__(self, first='', last='', sid=0, gpa=3):
        # Initialize the instance attributes
        self.first_name_str = first
        self.last_name_str = last
        self.id_int = sid

        # a "private" attribute
        self.__gpa = gpa

    # Class methods
    def update(self, first='', last='', sid=0):
        if first:
            self.first_name_str = first
        if last:
            self.last_name_str = last
        if sid:
            self.id_int = sid

    def print_msg(self):
        print("Hello {} {}. Your student ID is: {}".format(self.first_name_str, self.last_name_str, self.id_int))

    # "Private" method
    def __get_gpa(self):
        return self.__gpa

    # Public getter method
    @property
    def gpa(self):
        return self.__gpa

    # Public setter method
    @gpa.setter
    def gpa(self, new_gpa):
        self.__gpa = round(new_gpa, 2)

    # Implementation of the str() built-in function
    def __str__(self):
        return "{} {}, ID:{}".format(self.first_name_str, self.last_name_str, self.id_int)
"""Main file that tests the classes we made"""
from classes import Student

# Create an instance of Student
s = Student("Hamid", "Salman", "119988")

# Call the class method on s
s.print_msg()

# Call the class method as a class attribute instead of a method call
Student.print_msg(s)

# Adding a new instance attribute on-the-fly
s.major = "CpE"
print(s.major)

# Accessing a private attribute (ERROR)
#s.__gpa
#s._Student__gpa  (valid but breaks the encapsulation principle)
s.gpa

You can use the following helpful built-in functions to retrieve information about your class and objects

# Return the list of names in the class scope
dir(Student)

# Return the list of names in the object's scope
dir(s)

# Returns the type of the object
type(s)

# Returns True if s is an instance of Student and False otherwise
isinstance(s, Student)

You can also create a hierarchy of classes where one class (a child) can inherit the attributes and behavior of another class (the parent). This principle of inheritance allows developers to extend and add to the behavior of existing functionality without repeating code. Furthermore, polymorphism allows one to override the behavior of child classes that were inherited from parents in order to express any functionality that the child may uniquely posses. The following shows a module that demonstrates inheritance and polymorphism for Student and Faculty classes, both of which inherit from the User class.

"""This module contains all the classes related to university personnel"""
from abc import ABC, abstractmethod


class User(ABC):

    # Constructor
    def __init__(self, first='', last='', uid=0):
        # Initialize the instance attributes
        self.first_name_str = first
        self.last_name_str = last
        self.id_int = uid

    # Class attributes (variable)
    abbrev = "KU"

    # Class methods
    def update(self, first='', last='', uid=0):
        if first:
            self.first_name_str = first
        if last:
            self.last_name_str = last
        if uid:
            self.id_int = uid

    def print_msg(self):
        print("Hello User {} {}. Your student ID is: {}".format(self.first_name_str, self.last_name_str, self.id_int))

    # Can be a method or a property
    @abstractmethod
    def display_id(self):
        # You can enter an implementation here but an abstract method MUST BE overriden by children
        pass


class Student(User):
    """Student class"""

    def __init__(self, first='', last='', sid=0, gpa=3):
        super().__init__(first, last, sid)
        # a "private" attribute
        self.__gpa = gpa

    # "Private" method
    def __get_gpa(self):
        return self.__gpa

    # Public getter method
    @property
    def gpa(self):
        return self.__gpa

    # Public setter method
    @gpa.setter
    def gpa(self, new_gpa):
        self.__gpa = round(new_gpa, 2)

    def print_msg(self):
        print("Hello Student {} {}. Your student ID is: {}".format(self.first_name_str, self.last_name_str,
                                                                   self.id_int))

    def display_id(self):
        # super().display_id()
        print("KU" + self.id_int)

    # Overrides the str() built-in function
    def __str__(self):
        return "{} {}, ID:{}".format(self.first_name_str, self.last_name_str, self.id_int)

    # Overloading the + operator
    def __add__(self, st2):
        avg = (self.gpa + st2.gpa) / 2
        return Student(gpa=avg)


class Faculty(User):
    """Faculty class"""

    def __init__(self, first='', last='', fid=0, dep=None):
        super().__init__(first, last, fid)
        # a "private" attribute
        self.__department = dep

    def display_id(self):
        print(self.id_int[:3])

    def print_msg(self):
        print("Hello Faculty {} {}. Your ID is: {}".format(self.first_name_str, self.last_name_str, self.id_int))

Exercises:

  1. Create a class called Triangle that has three data attributes to indicate the $(x,y)$ coordinates of the vertices of a triangle on a Cartesian plane. Within the class, create a method that can be used to compute the length of the longest edge of any Triangle object. Instantiate your class with any set of coordinates and use it to find the longest edge.