diff --git a/workspace/3_oops/10_exception.py b/workspace/3_oops/10_exception.py new file mode 100644 index 0000000..e5da373 --- /dev/null +++ b/workspace/3_oops/10_exception.py @@ -0,0 +1,19 @@ +# Exception: An event that interrupts the normal flow of the program's execution. +# ZeroDivisionError: Occurs when you try to divide by zero. +# SyntaxError: Occurs when Python encounters incorrect syntax. +# TypeError: Occurs when you try to combine two objects that are not compatible. +# ValueError: Occurs when a built-in operation or function receives an argument that has the right type but an inappropriate value. +# +# 1. try:, 2. except:, 3. else:, 4. finally: + +try: + number = int(input("Enter a number: ")) + print(1/number) +except ZeroDivisionError: + print("You can't divide by zero.") +except ValueError: + print("Please enter a valid number.") +except Exception: + print("Something went wrong.") +finally: + print("This will always run.") diff --git a/workspace/3_oops/11_1_file_detection.py b/workspace/3_oops/11_1_file_detection.py new file mode 100644 index 0000000..2b89b44 --- /dev/null +++ b/workspace/3_oops/11_1_file_detection.py @@ -0,0 +1,16 @@ +# Python file detection + +import os + +# file_path = "workspace/3_oops/test.txt" +file_path = "workspace/3_oops" + +if os.path.exists(file_path): + print(f"File exists in {file_path}.") + + if os.path.isfile(file_path): + print(f"File is a regular file.") + elif os.path.isdir(file_path): + print(f"File is a directory.") +else: + print(f"File does not exist in {file_path}.") \ No newline at end of file diff --git a/workspace/3_oops/11_2_file_writing.py b/workspace/3_oops/11_2_file_writing.py new file mode 100644 index 0000000..7b27e45 --- /dev/null +++ b/workspace/3_oops/11_2_file_writing.py @@ -0,0 +1,70 @@ +# Python write to files (.txt, .csv, .json) + +import csv +import json + + +text_data = "Hello, World!" + +file_path = "workspace/3_oops/output.txt" +xfile_path = "workspace/3_oops/output_x.txt" +employees = ["John", "Doe", "Jane", "Doe"] + +# w: write, a: append, r: read, x: create +with open(file_path, "w") as file: + file.write(text_data) + print(f"Data written to {file_path}.") + +try: + with open(xfile_path, "x") as file: + file.write(text_data) + print(f"Data written to {xfile_path}.") +except FileExistsError: + print(f"File already exists in {xfile_path}.") + +try: + with open(file_path, "a") as file: + file.write("\n" +text_data) + print(f"Data written to {file_path}.") + for employee in employees: + file.write("\n" +employee) +except FileExistsError: + print(f"File already exists in {file_path}.") + + +#JSON + +jfile_path = "workspace/3_oops/output.json" + +student = { + "name": "John Doe", + "age": 25, + "grade": "A" +} + +try: + with open(jfile_path, "w") as file: + json.dump(student, file, indent=4) + print(f"JSON Data written to {jfile_path}.") +except FileExistsError: + print(f"File already exists in {jfile_path}.") + +# CSV + +students = [["Name", "Age", "Grade"], + ["John Doe", 25, "A"], + ["Jane Doe", 24, "B"], + ["Jim Doe", 23, "C"], + ["Jill Doe", 22, "D"]] + +cfile_path = "workspace/3_oops/output.csv" + +try: + with open(cfile_path, "w", newline="") as file: + writer = csv.writer(file) + for row in students: + writer.writerow(row) + print(f"CSV Data written to {cfile_path}.") + +except FileExistsError: + print(f"File already exists in {cfile_path}.") \ No newline at end of file diff --git a/workspace/3_oops/11_3_file_read.py b/workspace/3_oops/11_3_file_read.py new file mode 100644 index 0000000..f9b3643 --- /dev/null +++ b/workspace/3_oops/11_3_file_read.py @@ -0,0 +1,59 @@ +# Python reading files (.txt, .csv, .json) + +import csv +import json + + +file_path = 'workspace/3_oops/output.txt' +nfile_path = 'workspace/3_oops/no-exists.txt' + +# r= read, w= write, a= append, r+= read and write +with open(file_path, 'r') as file: + content = file.read() + print(content) + +try: + with open(nfile_path, 'r') as file: + content = file.read() + print(content) +except FileNotFoundError: + print('File not found') + +try: + with open(file_path, 'r') as file: + content = file.read() + print(content) +except FileNotFoundError: + print('File not found') +except PermissionError: + print('Permission denied') + +# JSON +jfile_path = 'workspace/3_oops/output.json' + +try: + with open(jfile_path, 'r') as file: + content = json.load(file) + print(content) + print(content['name']) +except FileNotFoundError: + print('File not found') +except PermissionError: + print('Permission denied') + + +# CSV +cfile_path = 'workspace/3_oops/output.csv' + +try: + with open(cfile_path, 'r') as file: + content = csv.reader(file) + for line in content: + print(line) + print(line[0]) + print(line[1]) +except FileNotFoundError: + print('File not found') +except PermissionError: + print('Permission denied') + diff --git a/workspace/3_oops/12_datetime.py b/workspace/3_oops/12_datetime.py new file mode 100644 index 0000000..890be21 --- /dev/null +++ b/workspace/3_oops/12_datetime.py @@ -0,0 +1,27 @@ +# Python system datetime module +import datetime + +date = datetime.date(2025, 10, 31) +today = datetime.date.today() + + +print(date) # 2025-10-31 +print(today) # 2025-01-01 + + +time = datetime.time(23, 59, 59) +now = datetime.datetime.now() + +nowFormatted = now.strftime("%H:%M:%S %d-%m-%Y") + +print(time) # 23:59:59 +print(now) # 2025-01-01 16:49:18.910042 +print(nowFormatted) # 16:49:18 2025-01-01 + +target_datetime = datetime.datetime(2025, 10, 31, 23, 59, 59) +current_datetime = datetime.datetime.now() + +if target_datetime < current_datetime: + print("Target datetime has passed") +else: + print("Target datetime has not passed") \ No newline at end of file diff --git a/workspace/3_oops/6_class_method.py b/workspace/3_oops/6_class_method.py new file mode 100644 index 0000000..6aecb90 --- /dev/null +++ b/workspace/3_oops/6_class_method.py @@ -0,0 +1,40 @@ +# Class method: Allowing access to the class from the instance +# Take (cls/self) as the first argument, which represents the class itself +# +# Instance method: Best for operations on instance of the class (object) +# Static method: Best for utility functions, which do not require access to the class or instance +# Class method: Best for class-level operations, which require access to the class, but not the instance + +class Employee: + + num_of_emps = 0 + total_pay = 0 + + def __init__(self, name, pay): + self.name = name + self.pay = pay + self.email = name + '@company.com' + Employee.num_of_emps += 1 + Employee.total_pay += pay + + # Instance method + def get_info(self): + return (f"{self.name}: {self.pay}") + + @classmethod + def get_num_of_emps(cls): + return (f"Total # of employees: {cls.num_of_emps}") + + @classmethod + def get_avg_pay(cls): + if cls.num_of_emps == 0: + return 0 + + return f"Average pay: {cls.total_pay / cls.num_of_emps:0.2f}" + +emp1 = Employee('Doe', 6000) +emp2 = Employee('John', 5000) +emp3 = Employee('Jane', 7000) + +print(Employee.get_num_of_emps()) +print(Employee.get_avg_pay()) \ No newline at end of file diff --git a/workspace/3_oops/7_magic(dunder)_method.py b/workspace/3_oops/7_magic(dunder)_method.py new file mode 100644 index 0000000..87f295c --- /dev/null +++ b/workspace/3_oops/7_magic(dunder)_method.py @@ -0,0 +1,61 @@ +# Magic methods: dunder/double underscore methods +# __init__, __str__, __eq__ +# These methods are called automatically when certain operations are performed on objects. +# Allow developers to define custom behavior for objects. + +class Book: + + def __init__(self, title, author, pages): + self.title = title + self.author = author + self.pages = pages + + def __str__(self): + return (f"'{self.title}' by {self.author}") + + def __eq__(self, other): + return self.title == other.title and self.author == other.author + + def __lt__(self, other): + return self.pages < other.pages + + def __gt__(self, other): + return self.pages > other.pages + + def __add__(self, other): + return f"{self.pages + other.pages} pages" + + def __contains__(self, keyword): + return keyword in self.title or keyword in self.author + + def __getitem__(self, key): + if key == 'title': + return self.title + elif key == 'author': + return self.author + elif key == 'pages': + return self.pages + else: + return f"Invalid key: {key}" + +book1 = Book('Python', 'John Doe', 210) +book2 = Book('Python', 'John Doe', 210) +# book2 = Book('Java', 'Jane Doe', 330) +book3 = Book('PHP', 'Hacker', 192) + +# __str__ method is called automatically when we print the object instead memory address is printed. +print(book1) +print(book2) +print(book3) + +# __eq__ method is called automatically when we compare two objects. +print(book1 == book2) +print(book2 < book3) +print(book2 > book3) +print(book2 + book3) + +print("PHP" in book3) +print("John" in book1) + +print(book1['title']) +print(book1['audio']) \ No newline at end of file diff --git a/workspace/3_oops/8_@property.py b/workspace/3_oops/8_@property.py new file mode 100644 index 0000000..e5d4644 --- /dev/null +++ b/workspace/3_oops/8_@property.py @@ -0,0 +1,59 @@ +# @property +# @property is a built-in decorator in Python which is helpful in defining the properties effortlessly without calling the property() function explicitly. +# Decorator used to dfine a method as a property. (it can be accessed as an attribute instead of a method) +# Benefits: +# - Add additional logic when read, write or delete a property +# - gives you getter, setter, deleter methods + +class Reactangle: + def __init__(self, width, height): + # _width and _height are private variables, they are not accessible from outside the class + self._width = width + self._height = height + + # Getter + @property + def width(self): + return f"{self._width:.1f}cm" + + @property + def height(self): + return f"{self._height:.1f}cm" + + # Setter + @width.setter + def width(self, new_width): + if new_width > 0: + self._width = new_width + else: + print("Width must be greater than 0") + + @height.setter + def height(self, new_height): + if new_height > 0: + self._height = new_height + else: + print("Height must be greater than 0") + + + # Deleter + @width.deleter + def width(self): + del self._width + print("Width has been deleted") + + @height.deleter + def height(self): + del self._height + print("Height has been deleted") + +rectangle = Reactangle(10, 20) + +rectangle.width = 40 +rectangle.height = 70 + +del rectangle.width +del rectangle.height + +# print(rectangle.width) +# print(rectangle.height) \ No newline at end of file diff --git a/workspace/3_oops/9_decorator.py b/workspace/3_oops/9_decorator.py new file mode 100644 index 0000000..20f5cab --- /dev/null +++ b/workspace/3_oops/9_decorator.py @@ -0,0 +1,26 @@ +# Decorator: A function that takes another function as an argument and adds some kind of functionality and returns another function +# without altering the source code of the original function that is passed as an argument. +# Pass the base function as an argument to the decorator function and return the modified function. +# +# @add_sprinkles +# get_icecream("Vanilla") + +def add_sprinkles(func): + # wrapper function is needed to avoid calling the function directly + def wrapper(*args, **kwargs): + print(f"*Adding sprinkles 🎉*") + func(*args, **kwargs) + return wrapper + +def add_fudge(func): + def wrapper(*args, **kwargs): + print(f"*Adding fudge 🍫*") + func(*args, **kwargs) + return wrapper + +@add_sprinkles +@add_fudge +def get_icecream(flavor): + print(f"Here is your {flavor} icecream 🍦") + +get_icecream("Vanilla") \ No newline at end of file diff --git a/workspace/3_oops/exercise_datetime.py b/workspace/3_oops/exercise_datetime.py new file mode 100644 index 0000000..0f9b796 --- /dev/null +++ b/workspace/3_oops/exercise_datetime.py @@ -0,0 +1,47 @@ +# Python Alarm Clock +import time +import datetime +import pygame + +def set_alarm(alarm_time): + # pass + print(f"Alarm set for {alarm_time}") + sound_file = "workspace/assets/alarm_1.mp3" + + is_running = True + + while is_running: + current_time = datetime.datetime.now().strftime("%H:%M:%S") + print(f"{current_time}") + + if current_time == alarm_time: + print("Wake up! ⏰") + + pygame.mixer.init() + pygame.mixer.music.load(sound_file) + pygame.mixer.music.play() + + while pygame.mixer.music.get_busy(): + time.sleep(1) + + is_running = False + + time.sleep(1) + + + + + # while True: + # current_time = datetime.datetime.now() + # if current_time >= alarm_time: + # print("Wake up!") + # pygame.mixer.init() + # pygame.mixer.music.load("alarm.mp3") + # pygame.mixer.music.play() + # break + # time.sleep(1) + + +if __name__ == "__main__": + alarm_time = input("Enter the alarm time in the format 'HH:MM:SS': ") + set_alarm(alarm_time) diff --git a/workspace/3_oops/output.csv b/workspace/3_oops/output.csv new file mode 100644 index 0000000..1623ec3 --- /dev/null +++ b/workspace/3_oops/output.csv @@ -0,0 +1,5 @@ +Name,Age,Grade +John Doe,25,A +Jane Doe,24,B +Jim Doe,23,C +Jill Doe,22,D diff --git a/workspace/3_oops/output.json b/workspace/3_oops/output.json new file mode 100644 index 0000000..dd9e7d9 --- /dev/null +++ b/workspace/3_oops/output.json @@ -0,0 +1,5 @@ +{ + "name": "John Doe", + "age": 25, + "grade": "A" +} \ No newline at end of file diff --git a/workspace/3_oops/output.txt b/workspace/3_oops/output.txt new file mode 100644 index 0000000..4fe93d8 --- /dev/null +++ b/workspace/3_oops/output.txt @@ -0,0 +1,6 @@ +Hello, World! +Hello, World! +John +Doe +Jane +Doe \ No newline at end of file diff --git a/workspace/3_oops/output_x.txt b/workspace/3_oops/output_x.txt new file mode 100644 index 0000000..b45ef6f --- /dev/null +++ b/workspace/3_oops/output_x.txt @@ -0,0 +1 @@ +Hello, World! \ No newline at end of file diff --git a/workspace/3_oops/test.txt b/workspace/3_oops/test.txt new file mode 100644 index 0000000..9d1cf01 --- /dev/null +++ b/workspace/3_oops/test.txt @@ -0,0 +1 @@ +I like MoMo. \ No newline at end of file diff --git a/workspace/4_concurrency/1_multithreading.py b/workspace/4_concurrency/1_multithreading.py new file mode 100644 index 0000000..72151f9 --- /dev/null +++ b/workspace/4_concurrency/1_multithreading.py @@ -0,0 +1,56 @@ +# Multithreading: Running multiple threads simultaneously +# Used to perform multiple tasks at the same time +# Good for I/O bound tasks like reading files or fetching data from the APIs +# threading.Thread(target=my-func) +# Each thread has its own memory space + +import threading +import time + +def walk_dog(name): + time.sleep(8) + print(f"Finish Walking the {name} dog") + +def take_out_trash(): + time.sleep(3) + print("Taking out the trash") + +def get_mail(): + time.sleep(4) + print("Getting the mail") + +# Create threads +# target: function to be executed +# tuple is used to pass arguments to the function +# args: arguments to be passed to the function +chore1 = threading.Thread(target=walk_dog, args=("German Shepherd",)) +# start() method is used to start the thread +chore1.start() + +chore2 = threading.Thread(target=take_out_trash) +chore2.start() + +chore3 = threading.Thread(target=get_mail) +chore3.start() + +# join() method is used to wait for the threads to finish +chore1.join() +chore2.join() +chore3.join() + +print("All chores are done") + +# Output: +# Taking out the trash +# Getting the mail +# Walking the dog + +# Running in the main thread +# walk_dog() +# take_out_trash() +# get_mail() + +# Output: +# Walking the dog +# Taking out the trash +# Getting the mail diff --git a/workspace/4_concurrency/2_0_multiprocessing.py b/workspace/4_concurrency/2_0_multiprocessing.py new file mode 100644 index 0000000..c22227e --- /dev/null +++ b/workspace/4_concurrency/2_0_multiprocessing.py @@ -0,0 +1,32 @@ +# Multiprocessing: It is a package that supports spawning processes using an API similar to the threading module. +# The multiprocessing package offers both local and remote concurrency, effectively side-stepping the Global Interpreter Lock +# by using subprocesses instead of threads. +# Must terminate the process after the execution of the code. + +from multiprocessing import Process, cpu_count + +print() +print(f"Number of CPU cores: {cpu_count()}") + +def print_continent(continent="Asia"): + print(f"Continent: {continent}") + +# confirm that the code is running in the main process +if __name__ == "__main__": + names = ["Asia", "Africa", "Europe", "North America", "South America", "Australia", "Antarctica"] + processes = [] + + # initiate the process with any number of arguments + process = Process(target=print_continent) + processes.append(process) + process.start() + + # initiate the process with arguments + for name in names: + process = Process(target=print_continent, args=(name,)) + processes.append(process) + process.start() + + # terminate the process + for process in processes: + process.join() \ No newline at end of file diff --git a/workspace/4_concurrency/2_1_queue_multiprocessing.py b/workspace/4_concurrency/2_1_queue_multiprocessing.py new file mode 100644 index 0000000..db35d37 --- /dev/null +++ b/workspace/4_concurrency/2_1_queue_multiprocessing.py @@ -0,0 +1,23 @@ +# Python multiprocessing Queue class +# The Queue class is used to share data between processes. +# It is a FIFO (First In First Out) data structure. + +from multiprocessing import Queue + +genres = ["Action", "Adventure", "Comedy", "Drama", "Fantasy", "Horror", "Mystery", "Romance", "Thriller", "Western"] +count = 1 + +# create a queue +queue = Queue() + +print("Put items in the queue") +for genre in genres: + queue.put(genre) + print(f"Item {count} added to the queue: {genre}") + count += 1 + +print("\nGet items from the queue") +count = 0 +while not queue.empty(): + print(f"Item {count} removed from the queue: {queue.get()}") + count += 1 \ No newline at end of file diff --git a/workspace/4_concurrency/2_2_pool_multiprocessing.py b/workspace/4_concurrency/2_2_pool_multiprocessing.py new file mode 100644 index 0000000..f7c5eae --- /dev/null +++ b/workspace/4_concurrency/2_2_pool_multiprocessing.py @@ -0,0 +1,43 @@ +# Multiprocessing Pool +# The Pool class represents a pool of worker processes. +# It has methods that allow tasks to be offloaded to the worker processes in a few different ways. +from multiprocessing import Pool +import time + +work = (["A", 5], ["B", 2], ["C", 1], ["D", 3]) + +def work_log(work_data): + print(f"Process {work_data[0]} waiting {work_data[1]} seconds") + time.sleep(int(work_data[1])) + print(f"Process {work_data[0]} Finished.") + +def pool_handler(): + # create a pool of workers + ''' + By default, the number of workers is the number of cores in the CPU. + You can specify the number of workers by passing the processes argument to the Pool class. + ''' + pool = Pool(processes=2) + ''' + The map() function in the Pool class applies the function to each element in the iterable. + It blocks the main program until all the processes are finished. + ''' + # work_log: function to be applied to each element in the iterable + # work: iterable + pool.map(work_log, work) + +if __name__ == "__main__": + pool_handler() + + +# Output +''' +Process A waiting 5 seconds +Process B waiting 2 seconds +Process B Finished. +Process C waiting 1 seconds +Process C Finished. +Process D waiting 3 seconds +Process A Finished. +Process D Finished. +''' \ No newline at end of file diff --git a/workspace/4_concurrency/3_asynchrony.py b/workspace/4_concurrency/3_asynchrony.py new file mode 100644 index 0000000..8895161 --- /dev/null +++ b/workspace/4_concurrency/3_asynchrony.py @@ -0,0 +1,7 @@ +# asyncio - Asynchronous I/O, event loop, coroutines and tasks +# it is used as a foundation for multiple Python asynchronous frameworks that provide high-performance network and web-servers, +# database connection libraries, distributed task queues, etc. +# asyncio is often a perfect fit for IO-bound and high-level structured network code. + +# When you have a long running operation in Python it'll block the main thread. +# This can limit scaling and responsiveness. You can update your code to use async/await to spin off a separate worker! \ No newline at end of file diff --git a/workspace/4_concurrency/exercise_multiprocessing_1.py b/workspace/4_concurrency/exercise_multiprocessing_1.py new file mode 100644 index 0000000..efa4151 --- /dev/null +++ b/workspace/4_concurrency/exercise_multiprocessing_1.py @@ -0,0 +1,55 @@ +# Python multiprocessing example + +from multiprocessing import Lock, Process,Queue, current_process +import time +# function to add data to queue. Empty exception is handled by the caller +import queue + +def do_job(tasks_to_accomplish, tasks_that_are_done): + while True: + try: + ''' + try to get task from the queue. get_nowait() function will + raise queue.Empty exception if the queue is empty. + queue(False) function would do the same task also. + ''' + task = tasks_to_accomplish.get_nowait() + print(task) + except queue.Empty: + break + else: + ''' + if no exception has been raised, add the task completion + message to task_that_are_done queue + ''' + print(task) + tasks_that_are_done.put(task + ' is done by ' + current_process().name) + time.sleep(.5) + return True + +def main(): + numer_of_task = 10 + number_of_processes = 4 + tasks_to_accomplish = Queue() + tasks_that_are_done = Queue() + processes = [] + + for i in range(numer_of_task): + tasks_to_accomplish.put("Task no " + str(i)) + + # creating processes + for w in range(number_of_processes): + p = Process(target=do_job, args=(tasks_to_accomplish, tasks_that_are_done)) + processes.append(p) + p.start() + + # completing process + for p in processes: + p.join() + + # print the output + while not tasks_that_are_done.empty(): + print(tasks_that_are_done.get()) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/workspace/assets/alarm_1.mp3 b/workspace/assets/alarm_1.mp3 new file mode 100644 index 0000000..46de148 Binary files /dev/null and b/workspace/assets/alarm_1.mp3 differ diff --git a/workspace/assets/alarm_2.mp3 b/workspace/assets/alarm_2.mp3 new file mode 100644 index 0000000..1c790c9 Binary files /dev/null and b/workspace/assets/alarm_2.mp3 differ