Skip to content

Instantly share code, notes, and snippets.

@vicradon
Last active August 28, 2025 09:37
Show Gist options
  • Save vicradon/5a8530545d63cc6037689c7a6143433a to your computer and use it in GitHub Desktop.
Save vicradon/5a8530545d63cc6037689c7a6143433a to your computer and use it in GitHub Desktop.

Python Intro

An introductory course to Python programming

Where Python Runs

Python runs in so many environments:

  1. On windows via the CMD, Powershell, or IDLE
  2. On anaconda via Jupyter Notebook or JupyterLab
  3. On Google Colab
  4. On Linux (default installed on Ubuntu)
  5. On macOS via Terminal or IDEs like PyCharm and VS Code
  6. On web browsers using platforms like Repl.it, Trinket, or Brython
  7. On mobile devices using apps like Pydroid (Android) or Pythonista (iOS)
  8. Embedded in other applications, for example as scripting in Blender, GIMP, or Autodesk Maya

What Python can do

EVERYTHING

What Python can do

  1. Build data dashboards
  2. Build frontend applications
  3. Build backend applications
  4. Build AI models and workflows
  5. Build games
  6. Build reporting tools
  7. Compute the age of the earth
  8. Order your peppersoup

etc

But Python is

NOTHING WITHOUT YOU, THE PROGRAMMER

This is where you come in

print("What's good, world?")
my_name = "Mr. Brother Man"
my_age = 42
my_interests = ["Anime", "Archery", "Skating"]
print("My name is", my_name, "and I am", my_age, "and my interests are", *my_interests)
message = f"My name is {my_name} and I am {my_age} and my interests are {my_interests[0]}, {my_interests[1]}, and {my_interests[2]}"
print(message)

Basic Explanation on Variables

my_name is a string. It is used for things like names, and natural language
my_age is an integer. It is used for whole numbers, both negative and positive
my_interests is a list. It is used for grouping/ordering related variables

# floats
PI = 3.14
accelertion_due_to_gravity = 9.81
print("Acceleration due to gravity on earth is", accelertion_due_to_gravity)
# bools
tech_bros_are_fraudsters = False
is_raining_season = True
print("Are techbros frauds?", tech_bros_are_fraudsters)
print("Are we in raning season?", is_raining_season)
# dictionary (hashmap)
account_names_to_numbers = {
"Ajibola Philips": 3110000000,
"Moses Franklin": 3011000000,
"Ijeoma Peters": 3021000000,
}
students = dict()
students["you"] = {"name": "", "age": 26, "occupation": "tech bro"}
print(type(account_names_to_numbers), type(students))
# Tuple
random_vector = (5, 45.0) # vector -> magnitude, direction
mercy_details = ("Mercy Franklin", 25, False) # name, age, is_single
# Set (hashset)
account_numbers = set([3110000000, 3110000000, 3021000000])
print("Account numbers", account_numbers)
account_numbers = {3110000000, 3021000000}

Class Exercise on Data Types and Structures

Task: Write a dictionary that has your name, twitter_handle, favorite constants, and what not. Goal: To help you understand the different data types you can work with in Python

my_internet_profile = {
    "name": None,                          # string
    "twitter_handle": None,                # string
    "favorite_physics_constant": None,     # float
    "age": None,                           # integer (you can lie about this lol)
    "finished_uni": None,                  # boolean
    "hobbies": None,                       # tuple
    "skills": None,                        # list
    "personal_quotes": None,               # set (one or two)
    "contact_info": {                      # dictionary (nested)
        "phone_number": None,              # integer (you can put a fake one)
        "email": None,                     # string (you can put a fake one)
        "website": None,                   # string
    },
}

print(my_internet_profile)
input("What is your name: ")
name = input("What is your name: ")
print(name)

Collecting Inputs Class Exercise

Task: Write a program that collects your name and interests Goal: To teach you how to implement data collection and casting

name = input("What is your name: ")
age = int(input("What"))
interest1 = input("What is your number 1 interest: ")
interest2 = input("What is ")
interest3 = input("What ")

combined = f"Your name is {name} and"

print(combined)
name = input("What is your name: ")
age = input("What")
interest1 = input("What is your number 1 interest: ")
interest2 = input("What is ")
interest3 = input("What ")
combined = f"Your name is {name} and"
print(combined)

Conditionals Branching within Programs

You can define branching rules in your codebase using conditionals. So if a particular condition is true or false, it can determine if certains blocks/lines of code will be executed.

Conditionals exist in many places, but are majorly implemented with if, elif, and else.

Other places conditionals exist

while loop, run once

"""Run once example"""
a = 3
while a < 4:
  print("do something")
  a += 1

while loop, infinite (run infinitely)

while True:
  print("Welp, it's over")
"""In this conditionals example, you collect the distance ran by a user and prints message to them.
Ideally, you to guard against wrong input, since you are casting immediately to float (from string).
You can replace line 15 with the try catch below to guard against wrong input (don't forget the "import sys")
import sys
try:
distance = float(input("Enter distance run (in km): "))
except:
print("why did you enter a non-number?, sigh")
sys.exit(1)
"""
distance = float(input("Enter distance run (in km): "))
MARATHON_DISTANCE = 42.195
# simple if
if distance >= 5:
print("Yay, you ran at least 5K!")
# if/else
if distance < 1:
print("That's basically a warm-up jog 😅")
else:
print("Nice effort!")
# if/elif/else chain
if distance < 5:
print("Keep going, you'll reach 5K soon.")
if distance < 10:
print("Almost a quarter marathon!")
elif distance < 22:
print("Almost a half marathon!")
elif distance < MARATHON_DISTANCE:
print("You're close to a full marathon!")
elif distance == MARATHON_DISTANCE:
print("👏 Congrats, you completed a marathon!")
else:
print("You're god-level, you exceeded a marathon by", distance - MARATHON_DISTANCE, "km")
# nested if
if distance > 10:
if distance < 15:
print("You're in the 10-15km range, solid training ground!")
elif distance < 20:
print("You're in the 15-20km range, you're a strong one!")
# logical operators
if distance >= 5 and distance <= 10:
print("That's a nice middle-distance run.")
if distance == MARATHON_DISTANCE/2 or distance == MARATHON_DISTANCE/4:
print("Exact amounts eh?")

Class Exercise on Conditionals

Class Exercise on Conditionals

Task: Write a program that: Asks the user for their exam score (0–100). Uses conditionals to print their grade:

70 and above → "A" 60–69 → "B" 50–59 → "C" 40–49 → "D" below 40 → "F"

Goal: To help you learn how to branch logic with if/elif/else.

score = int(input("Enter your exam score (0-100): "))
# write your if/elif/else statements here

Loops Intro

In the collecting inputs section, remember how you needed to define interest1, interest2, and interest3 manually? What if there was a way to write less code? That is where a for-loop comes in.

name = input("What is your name: ")
age = input("What is your age: ")
interests = []
for i in range(3):
interest = input(f"What is your number {i} interest: ")
interests.append(interest)
interests_string = ", ".join(interests)
combined = f"Your name is {name} and you are {age} years old with interests in {interests_string}"
print(combined)

Parts of a loop

  1. increment variable
  2. iterator/iteration
  3. repeated code

In the previous loop, i is the increment variable,
range(3) is the iterator and the snippet below is the repeated code

  interest = input(f"What is your number {i} interest: ")
  interests.append(interest)
name = input("What is your name: ")
age = input("What is your age: ")
interests = []
i = 1 # increment variable
while i <= 3: #
interest = input(f"What is your number {i} interest: ")
interests.append(interest)
i += 1 # iteration
interests_string = ", ".join(interests)
combined = f"Your name is {name} and you are {age} years old with interests in {interests_string}"
print(combined)

Exercise 1 – Find an Account (for Loop)

You are given a list of account numbers:

accounts = ["1234567890", "9876543210", "5555555555", "1111222233"]

Task:

  1. Ask the user to enter an account number.
  2. Use a for loop to check if the account number exists in the list.
  3. If it exists, print "200 - Account found".
  4. Otherwise, print "404 - Account not found"
# Exercise 1 - For Loop
accounts = ["1234567890", "9876543210", "5555555555", "1111222233"]
account_to_search = input("Enter account number to search: ")
# TODO: use a for loop to check if search_account is in accounts
# Hint: loop through accounts and compare the `account_to_search` to the looped element

Exercise 2 – Valid Account Number Input (While Loop)

Banks only accept 10-digit numeric account numbers.

Task:

  1. Ask the user to enter their account number.
  2. Keep asking until they provide a valid 10-digit number (all numbers, length 10).
  3. Once valid, print "Account number accepted".

Goal: To practice using for loops to search lists, and while loops for input validation.

while True:
account_number = input("Enter your 10-digit account number: ")
# TODO: check that it is all digits and has length 10
# If valid -> break loop, else keep asking

Functions Intro

Functions allow you to group logic. So the input collector you’ve built so far can be stored as a function that can be called anytime.

def collect_input():
name = input("What is your name: ")
age = input("What is your age: ")
interests = []
for i in range(3):
interest = input(f"What is your number {i} interest: ")
interests.append(interest)
interests_string = ", ".join(interests)
return f"Your name is {name} and you are {age} years old with interests in {interests_string}"
collect_input()
def add(num1, num2):
return num1 + num2
number_sum = add(2, 3)
print(number_sum)
'''
num1 is the first parameter
num2 is the second parameter
2 is the first argument (things passed to functions)
3 is the second argument
return is a keyword for returning from a function
'''
def subtract(num1, num2):
return num1 - num2
difference = subtract(7, 3)
print(difference)
def divide(num1, num2):
return num1/num2
print("5 divide by 2 is", divide(5,2), "\n\n")
print("3 divide by 0 is", divide(3, 0)) # throws error
def divide(num1, num2):
try:
return num1/num2
except Exception as e:
print(str(e))
print("5 divide by 2 is", divide(5,2))
print("3 divide by 0 is", divide(3, 0)) # throws error
def divide(num1, num2):
try:
return num1/num2
except ZeroDivisionError:
print("You attempted to divide by zero")
print("5 divide by 2 is", divide(5,2))
print("3 divide by 0 is", divide(3, 0)) # throws error

Write a function that finds the min and max number in a list of numbers

Your function should:

  1. Take several inputs as arguments find_min_max(arg1, arg2, arg3, …, argn)
  2. Finds the minimum and maximum number
  3. Returns both of them as a tuple (minimum, maximum)

Write a few cases to verify that it works: [4, 7, 2, 9, 0, 3] [-9, -12, 38, 489, 38, 2, -2] [3903, 28, math.e, -90]

def find_min_max(*args):
min_num = float("-inf") # smallest possible number in python
max_num = float("inf") # largest possible number in python
return (min_num, max_num)
# TODO: write test cases and pass them to the function. Print the results

Python Classes

A Class is a more advanced way to group several functions and related logic in one place

class Calculator:
def __init__(self):
pass
def add(self, a, b):
return a + b
def subtract(self, num1, num2):
return num1 - num2
def divide(self, num1, num2):
try:
return num1/num2
except ZeroDivisionError:
print("You attempted to divide by zero")
def advanced_addition(self, *args):
"""This takes any number of arguments (numbers) and returns their sum."""
result = 0
for num in args:
result = result + num
return result
calculator = Calculator()
sum1 = calculator.add(4, 5)
diff = calculator.divide(9, 2)
sum2 = calculator.advanced_addition(4, 5, 6)
print(sum1)
print(diff)
print(sum2)

Parts of a class

  1. name - Calculator
  2. self - a secret first argument that the snitch, Python, passes to every function defined within a class to make them methods.
  3. init - a dunder method that python invokes when initializing a class. There are others like str, __converting a class to string
  4. Methods
class Calculator:
def __init__(self, log_results: bool = False):
self.log_results = log_results
def add(self, num1: float, num2: float):
result = num1 + num2
if self.log_results:
print(f"The sum of {num1} and {num2} is {result}")
return result
calculator = Calculator(log_results=True)
calculator.add(4, 5) # no need for print

Classes are simply ways to create your own type of logic

So you can create behaviors, like how printing occurs, how data is accessed, in very specific ways.

class Calculator:
def __init__(self, log_results: bool = False):
self.log_results = log_results
self.history = {
"addition": [],
"subtraction": [],
"division": [],
"multiplication": []
}
def add(self, num1: float, num2: float):
result = num1 + num2
if self.log_results:
print(f"The sum of {num1} and {num2} is {result}")
self.history["addition"].append((num1, num2, result))
return result
calculator = Calculator()
calculator.add(4, 5)
calculator.add(20, 25)
calculator.add(-20, 30)
print(calculator.history)

Practice Keeping State

Task: implement a new class called CalculatorHistory, then initialize it within the init method of the calculator class. Purpose: To make it possible to use object accessors to access history methods rather than dictionary accessors Starting point:

class CalculatorHistory:
    def __init__(self):
        # add the remaining
        self.addition = []

    def __str__(self):
        return f"""
            Addition: {self.addition}
        """

class Calculator:
    def __init__(self, log_results: bool = False):
        self.log_results = log_results
        self.history = {
            "addition": []
        }

    def add(self, num1: float, num2: float):
        result = num1 + num2

        if self.log_results:
            print(f"The sum of {num1} and {num2} is {result}")

        self.history["addition"].append((num1, num2, result))
        # goal 
        # self.history.addition.append((num1, num2, result))

        return result


calculator = Calculator()

calculator.add(4, 5)
calculator.add(20, 25)
calculator.add(-20, 30)

print(calculator.history)

Object Oriented Programming (OOP)

Python allows you to implement some object oriented programming principles like inheritance, encapsulation, and polymorphism, with other principles (abstraction) supported to some degree.

OOP princples help you write leaner, more organised code.

In this module, you will implement some OOP principles on the calculator exmple.

import math
class Calculator:
def __init__(self, log_results: bool = False):
self.log_results = log_results
def add(self, num1: float, num2: float):
result = num1 + num2
if self.log_results:
print(f"The sum of {num1} and {num2} is {result}")
return result
def subtract(self, num1, num2):
return num1 - num2
def divide(self, num1, num2):
try:
return num1/num2
except ZeroDivisionError:
print("You attempted to divide by zero")
def advanced_addition(self, *args):
"""This takes any number of arguments (numbers) and returns their sum."""
result = 0
for num in args:
result = result + num
return result
class ScientificCalculator(Calculator):
def __init__(self, log_results: bool = False):
super().__init__(log_results)
def sin(self, x):
return math.sin(x)
def cos(self, x):
return math.cos(x)
def tan(self, x):
return math.tan(x)
def log(self, x, base=10):
result = math.log(x, base)
if self.log_results:
print(f"The log to base 10 of {x} is: {result}")
return result
def nat_log(self, x):
return math.log(x, math.e)
def factorial(self, n):
return math.factorial(n)
sci_calc = ScientificCalculator(log_results=True)
sci_calc.add(4, 5) # from the main calculator
sci_calc.log(100)
"""
Encapsulation equals method/variable hiding
In Python, we are all adults here
So if you see underscores, don't use that method or accesor
Single underscore _something -> still accessible
Double underscores __something -> not accessible directly
"""
import math
class Calculator:
def __init__(self, log_results: bool = False):
self.__log_results = log_results
def enable_logging(self):
self.__log_results = True
def disable_logging(self):
self.__log_results = False
@property
def is_logging_enabled(self):
return self.__log_results
def add(self, num1: float, num2: float):
result = num1 + num2
if self.is_logging_enabled:
print(f"The sum of {num1} and {num2} is {result}")
return result
class ScientificCalculator(Calculator):
def __init__(self, log_results: bool = False):
super().__init__(log_results)
def log(self, x, base=10):
result = math.log(x, base)
if self.is_logging_enabled:
print(f"The log to base 10 of {x} is: {result}")
return result
sci_calc = ScientificCalculator()
sci_calc.__log_results = True
print(sci_calc.is_logging_enabled)
sci_calc.add(3, 4)
sci_calc.enable_logging()
print(sci_calc.is_logging_enabled)
sci_calc.add(3, 4)
"""
Polymorphism means one accessor, many implementation
Here, the `add` method is implemented both on the Calculator and ScientificCalculator classes
The former has a basic implementation while the later has a more involved implementation
"""
class Calculator:
def __init__(self, log_results: bool = True):
self.__log_results = log_results
def enable_logging(self):
self.__log_results = True
def disable_logging(self):
self.__log_results = False
@property
def is_logging_enabled(self):
return self.__log_results
def add(self, num1: float, num2: float):
result = num1 + num2
if self.is_logging_enabled:
print(f"The sum of {num1} and {num2} is {result}")
return result
class ScientificCalculator(Calculator):
def __init__(self, log_results: bool = True):
super().__init__(log_results)
def add(self, *nums):
result = sum(nums)
if self.is_logging_enabled:
stringified_nums = "The sum of "
for i in range(len(nums) - 1):
stringified_nums += f"{nums[i]}, "
stringified_nums += f"and {nums[-1]}" # last element
print(f"The sum of {stringified_nums} is {result}")
return result
normal_calc = Calculator()
sci_calc = ScientificCalculator()
normal_calc.add(3, 4)
sci_calc.add(3, 4, 5)
"""In Abstraction, we define a base template every class must implement.
Ignoring sub class inheritance, for example, the scientific calculator, if multiple defined classes implement the AbstractCalculator,
they must define the add, subtract, multiply, and divide methods.
"""
from abc import ABC, abstractmethod
from fractions import Fraction
class AbstractCalculator(ABC):
@abstractmethod
def add(self, num1, num2):
pass
@abstractmethod
def subtract(self, num1, num2):
pass
@abstractmethod
def multiply(self, num1, num2):
pass
@abstractmethod
def divide(self, num1, num2):
pass
class BasicCalculator(AbstractCalculator):
def add(self, num1, num2):
return num1 + num2
def subtract(self, num1, num2):
return num1 - num2
def multiply(self, num1, num2):
return num1 * num2
def divide(self, num1, num2):
if num2 == 0:
raise ValueError("Cannot divide by zero")
return num1 / num2
class RoundingCalculator(AbstractCalculator):
"""Always rounds to 2 decimal places (useful in finance)."""
def add(self, num1, num2):
return round(num1 + num2, 2)
def subtract(self, num1, num2):
return round(num1 - num2, 2)
def multiply(self, num1, num2):
return round(num1 * num2, 2)
def divide(self, num1, num2):
if num2 == 0:
raise ValueError("Cannot divide by zero")
return round(num1 / num2, 2)
class FractionCalculator(AbstractCalculator):
"""Uses fractions for exact rational math."""
def add(self, num1, num2):
return Fraction(num1) + Fraction(num2)
def subtract(self, num1, num2):
return Fraction(num1) - Fraction(num2)
def multiply(self, num1, num2):
return Fraction(num1) * Fraction(num2)
def divide(self, num1, num2):
if num2 == 0:
raise ValueError("Cannot divide by zero")
return Fraction(num1) / Fraction(num2)
calculators = [
BasicCalculator(),
RoundingCalculator(),
FractionCalculator()
]
for calc in calculators:
print(f"\n{calc.__class__.__name__}")
print("Add:", calc.add(7, 3))
print("Subtract:", calc.subtract(7, 3))
print("Multiply:", calc.multiply(7, 3))
print("Divide:", calc.divide(7, 3))
import locale
from abc import ABC, abstractmethod
try:
locale.setlocale(locale.LC_ALL, "en_NG.UTF-8")
except locale.Error:
locale.setlocale(locale.LC_ALL, "")
def format_currency(amount: float) -> str:
formatted = locale.format_string("%0.2f", amount, grouping=True)
return f"₦{formatted}"
class Payment(ABC):
@abstractmethod
def pay(self, amount: float):
pass
class VerveCardPayment(Payment):
def __init__(self, card_number: str, pin: str):
self.card_number = card_number
self.pin = pin
def pay(self, amount: float):
print(f"Charging {format_currency(amount)} to Verve card {self.card_number[-4:]}...")
class MomoPayment(Payment):
def __init__(self, phone_number: str):
self.phone_number = phone_number
def pay(self, amount: float):
print(f"Processing MoMo payment of {format_currency(amount)} from {self.phone_number}...")
class CryptoPayment(Payment):
def __init__(self, wallet_address: str):
self.wallet_address = wallet_address
def pay(self, amount: float):
print(f"Sending crypto payment of {format_currency(amount)} to wallet {self.wallet_address[:6]}...")
def checkout(payment_method: Payment, amount: float):
payment_method.pay(amount)
checkout(VerveCardPayment("1234567890123456", "123"), 10000.0)
checkout(MomoPayment("810548xxxx"), 23_435)
checkout(CryptoPayment("0xAbCdEfGhIjKlMnOpQrStUvWxYz"), 2_000_000.0)

Class Exercise on OOP

Task: Create a simple BankAccount class. It should store the account holder's name and balance. It should inherit from the AbstractBankAccount abstract class

It should have methods:

deposit(amount) -> adds to balance withdraw(amount) -> removes from balance (but not below 0) str() -> prints account details

Then Create a subclass called SavingsAccount that: Inherits from BankAccount Adds a method add_interest(rate) that increases the balance by a percentage

Goal: To practice creating your own classes, methods, inheritance, and state management.

class BankAccount:
def __init__(self, name, balance=0):
self.name = name
self.balance = balance
def deposit(self, amount):
pass # implement this
def withdraw(self, amount):
pass # implement this
def __str__(self):
return f"{self.name} has a balance of {self.balance}"
# TODO: Create a SavingsAccount class that inherits from BankAccount
# Example usage
account = BankAccount("Alice", 1000)
print(account)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment