Project

Python Foundations: A Self-Learning Journey

A comprehensive, self-guided learning project designed to provide a fundamental understanding of Python programming language.

Empty image or helper icon

Python Foundations: A Self-Learning Journey

Description

The project provides a step-by-step guide on how to begin learning the basics of Python and advances to more complex topics. It structures the learning journey in a detailed and logical manner, taking into account the learning curve of an individual. Each unit is independent and self-descriptive, thus providing a clear learning path and promoting understanding of Python language. This project is designed for those seeking to build strong programming essentials without losing sight of the basic concepts.

Python Language Basics

This guide will get you started with the basic Python implementations covering setup, various data types & operation, control flow, function and import usage.

Python Environment Setup

Python comes pre-installed on most Unix-like systems, such as Ubuntu and macOS. However, you can install it manually with the following steps:

  • Open terminal and run the command python3 --version. If Python is installed, you will see the version number of your Python3.

  • If Python is not installed, use the following commands to install it:

    • For Ubuntu/Linux systems: sudo apt-get install python3.6.

    • For macOS via Homebrew: brew install python3.

  • Verify if pip (Python's package installer) is installed by running pip --version in the terminal.

  • If pip is not installed, use the following commands to install it:

    • For Ubuntu/Linux systems: sudo apt-get install python3-pip.

    • For macOS via Homebrew: brew install pip.

Python Implementation

Let's dive into the Python coding.

# Let's start with the simplest command possible. Print "Hello World".
print("Hello, World!")

Running this code will output the text:

Hello, World!

Variables and Types

# Numbers
x = 3
print(type(x)) # Prints ""

# String
hello = 'hello'    # String literals can use single quotes
world = "world"    # or double quotes.
print(hello)       # Prints "hello".

# Boolean
t = True
f = False
print(type(t)) # Prints ""

Lists, Dictionaries and Tuples

# List
xs = [3, 1, 2]
print(xs)        # Prints "[3, 1, 2]"
xs.append('bar') # Add a new element to the end of the list

# Dictionaries
d = {'cat': 'cute', 'dog': 'furry'}  # Create a new dictionary
print(d['cat'])       # Get an entry from a dictionary; prints "cute"

# Tuples
t = (5, 6)       # Create a tuple
print(type(t)) # Prints ""

Control Flow, Functions and Imports

# If-Else
x = 10
if x > 0:
    print('x is positive')
elif x < 0:
    print('x is negative')
else:
    print('x is zero')

# For loop
for i in range(5):   # range() function generates a sequence of numbers
    print(i)

# While loop
i = 0
while i < 5:
    print(i)
    i += 1

# Function
def sign(x):
    if x > 0:
        return 'positive'
    elif x < 0:
        return 'negative'
    else:
        return 'zero'

# Imports
import math
print(math.sqrt(16))  # Prints "4.0"

You now have a practical implementation of Python basic language features to start your journey in Python programming.

Python Data Types and Variables

Python data types are very crucial elements in Python as they define the ways in which operations can be performed on a set of data. Python has numerous data types including basic types: integer, float, strings, and also complex data types: list, set, dictionary, and tuples.

Integers and Floats

Integers in Python are whole numbers and floats are real numbers. We can perform mathematical operations with them.

# Declaring integer variables
a = 5
b = 10

# Declaring float variables
x = 3.14
y = 2.71

# Performing operations
sum_of_integers = a + b
product_of_floats = x * y

print(sum_of_integers)
print(product_of_floats)

Strings

Strings in Python are a sequence of characters enclosed within single quotes (' ') or double quotes (" ").

# Declaring string variables
string1 = "Hello"
string2 = 'World'

# Strings can be concatenated using the '+' operator
concatenated_string = string1 + ' ' + string2
print(concatenated_string)

Lists

A List in Python is defined by having values between square brackets [] and the values are separated by commas. Lists can contain any type of element, and they can also be of mixed types.

# Declaring list
my_list = ['apple', 'banana', 100, 200.50]

# Accessing list elements by their indices
first_item = my_list[0]
second_item = my_list[1]

print(first_item)
print(second_item)

Sets

A Set is an unordered collection of items with no duplicate elements. Sets are mutable, which means lists can be changed in-place.

# Declaring set
my_set = {1,2,3,4,5}

# Adding element to a set
my_set.add(6)

print(my_set)

Dictionary

A Dictionary is an unordered collection of key-value pairs. Think of real-world dictionaries: when you look up a word (the key), you get the definition of that word (the value). Dictionaries in Python are enclosed within {} brackets.

# Declaring dictionary
my_dict = {"name": "John", "age": 30, "city": "New York"}

# Accessing dictionary values by their keys
person_name = my_dict["name"]
person_age = my_dict["age"]

print(person_name)
print(person_age)

Tuples

A tuple in Python is similar to a list. The main difference is that a tuple is immutable (which means tuples cannot be changed after their declaration).

# Declaring tuple
my_tuple = (10, 20, 30, 40, 50)

# Accessing tuple elements by their indices
third_item = my_tuple[2]
fourth_item = my_tuple[3]

print(third_item)
print(fourth_item)

This brief tutorial on Python's Data types and variables provides a concise implementation that takes you on a quick tour of Python's primitive data types. You can use these implementations as starting points for your project.

Control Flow and Looping in Python

Python programs get structured through indentation, i.e. the use of whitespace at the beginning of lines. The depth of indentation varies according to the context.

Control flow and looping are key aspects of any programming language, and Python is no exception. Let's have a look at a few examples.

if - elif - else Control Flow

The if - elif - else statements are used in Python for decision making.

Below is its basic syntax:

if test expression:
    statement(s)
elif test expression:
    statement(s)
else:
    statement(s)

Let's see an example.

weather = 'Rainy'

if weather == 'Sunny':
    print('It\'s a perfect day for a picnic.')
elif weather == 'Rainy':
    print('You might need an umbrella.')
else:
    print('The weather is unpredictable.')

for loop

The for loop in Python is used to iterate over a sequence (list, tuple, string) or other iterable objects.

Below is its basic syntax:

for val in sequence:
    statement(s)

Let's see an example.

fruits = ['apple', 'banana', 'mango']

for fruit in fruits:
    print(fruit)

while loop

The while loop in Python is used to iterate over a block of code as long as the test expression (condition) is true.

Below is its basic syntax:

while test expression:
    statement(s)

Let's see an example.

i = 1

while i <= 5:
    print(i)
    i += 1

break and continue statements

In Python, the break and continue statements can alter the flow of a normal loop.

The break statement terminates the loop containing it, and the control of the program flows to the statement immediately after the body of the loop.

The continue statement is used to skip the rest of the code inside a loop for the current iteration only. The loop does not terminate but continues with the next iteration.

Let's see an example:

# breaks when value is 5.
for num in range(10):
    if num == 5:
        break
    print(num)
# continues when value is 5.
for num in range(10):
    if num == 5:
        continue
    print(num)

Section 1: Python Functions

Python functions are reusable pieces of code that perform a specific task. They can be defined using the def keyword. Below is an example of a simple function.

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

To use this function, we would call it by its name as follows:

print_hello()

1.1: Functions with Arguments

Functions can also take in arguments or parameters that alter how the function behaves. Here's an example of a function that takes in a name and prints a customized greeting.

def print_hello(name):
    print(f"Hello, {name}!")

We can call this function by passing in a name:

print_hello("Alice")

1.2: Function Return Values

Functions can also return a value using the return keyword. This allows us to store the result of a function in a variable.

def add_numbers(num1, num2):
    return num1 + num2

We can call this function and store its result in a variable:

sum_result = add_numbers(5, 7)
print(sum_result)

Section 2: Code Reusability

In software engineering, code reusability is the use of the same code in multiple functions. Python supports code reusability via functions, classes and modules.

2.1: Reusable Functions

We've already seen how to define functions. Essentially, any piece of code that you find yourself using more than once is a good candidate for a function.

def calculate_average(num_list):
    return sum(num_list)/len(num_list)

This calculate_average function can be used anywhere in the program where we wish to calculate an average of a list of numbers.

2.2: Code Reusability through Python Modules

Another way to achieve code reusability in python is by using modules. A module is a file containing Python definitions and statements intended for use in other Python programs.

Below is an example of how to create a module.

Creating a Python Module

To create a module, simply create a new .py file and define functions, classes or variables in it. For example, we can create a utilities.py file and define calculate_average function in it.

# This code would go in utilities.py
def calculate_average(num_list):
    return sum(num_list)/len(num_list)

We could then use this function in another program by importing the utilities module.

# importing utilities module
import utilities

numbers = [1, 2, 3, 4, 5]
average = utilities.calculate_average(numbers)
print(average)

Using modules allows us to organize our code and reuse functionality across multiple programs.

Working With Collections: Lists, Tuples, Dictionaries

Python’s built-in data structures (lists, tuples, dictionaries) allow you to flexibly store, access, and organize your data. Let's apply these data structures in practical solutions.

Section 1: Lists

A list in Python is a collection of items which can be of different types. It can be altered i.e., you can add, remove, or update elements in a list.

Creating a List

Let's start by creating an empty shopping list.

# Create an empty list
shopping_list = []

Adding Elements

To add an item to a list, we can use the append function.

# Add items to the list
shopping_list.append("milk")
shopping_list.append("cheese")
shopping_list.append("bread")

Accessing Elements

You can access elements in a list using their index, starting from 0.

# Access an item in the list
print(shopping_list[1])  # Output -> cheese

Updating Elements

# Update an item in the list
shopping_list[0] = "almond milk"

Section 2: Tuples

Tuples are similar to lists, but unlike lists, they are immutable. This means that the values in a tuple cannot be changed once it has been defined.

Creating a Tuple

# Define a tuple
week_days = ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday')

Accessing Elements

# Access an item in the tuple
print(week_days[2])  # Output -> Wednesday

Section 3: Dictionaries

A dictionary in Python is an unordered collection of items, stored as key-value pairs.

Creating and Accessing a Dictionary

# Define an empty dictionary
person = {}

# Add key-value pairs
person['name'] = 'John Doe'
person['age'] = 35
person['country'] = 'USA'

# Access a value
print(person['name'])  # Output -> John Doe

Updating and Adding Key-Value Pairs

# Update a value
person['age'] = 36

# Add a new key-value pair
person['occupation'] = 'Engineer'

Deleting Key-Value Pairs

# Delete a key-value pair
del person['occupation']

This forms the basis of interacting with collections in Python. Remember that each of these collection types have their unique traits and more methods that can aid you in your data manipulation and organization.

Python Foundations: Part 6 - File Input/Output Operations

In this guide, we'll walk through a set of practical implementations touching on fundamental file input/output (I/O) operations in Python. This includes opening, reading, writing, and closing files.

Section 1: Opening a File

In Python, you can open a file with the built-in open function. The function requires the file path and mode as arguments. The mode can be 'r' for read, 'w' for write, 'a' for append, and 'b' for binary. Here is how we can open a file:

# Open file in read mode
file = open('file_path', 'r')

Section 2: Reading from a File

You can read a file's content in several ways: read(), readline(), or readlines().

# Read the entire content.
content = file.read()

# Read the content line by line.
line = file.readline()

# Read all lines and return them as a list.
lines = file.readlines()

Section 3: Writing to a File

To write into a file, we should open it in 'write' or 'append' mode. With 'write' mode, the existing file content will be erased. On the other hand, 'append' mode will preserve the original content and add new data at the end of the file.

# Open file in write mode
file = open('file_path', 'w')

# Write to the file
file.write("This is a sample text.")

# Open file in append mode
file = open('file_path', 'a')

# Append to the file
file.write("\nThis is the appended line.")

Section 4: Closing a File

After using a file, it’s advisable to close it to free system resources.

# Close the file
file.close()

Section 5: Using with Keyword

To ensure the file is properly closed after completion, we can use the with keyword. This keyword creates a context where the file is automatically closed when done.

# Using `with` keyword
with open('file_path', 'r') as file:
    content = file.read()

Section 6: Working with JSON Files

For handling structured data like JSON, Python provides the json module.

import json

# Parse JSON file
with open('data.json', 'r') as file:
    data = json.load(file)

# Write JSON file
with open('data_out.json', 'w') as file:
    json.dump(data, file, indent=4)

By using the practical Python code snippets above, you can perform basic file I/O operations in your project. Please replace the dummy file names (file_path, data.json, and data_out.json) with your real file paths to make the code work as expected.

Exception Handling in Python

Python, like many other languages, has a built-in error handling mechanism known as exception handling. When an error occurs, or an "exception" as it is called, Python will normally stop and generate an error message. With the use of exception handling, you can write your program to detect and handle these exceptions. This can make your program more robust and maintainable.

Simple try-except Block

The basic structure of exception handling in Python is the try/except statement. Firstly, the try block is executed. If no error occurs, the except block is skipped. However, if an error does occur, the try block is immediately exited and the except block is executed. Afterward, the program execution continues on as normal.

try:
    print(5/0)
except ZeroDivisionError:
    print("You can't divide by zero!")

In this example, if the "ZeroDivisionError" occurs when trying to execute print(5/0), Python will stop executing the try block and then run the code in the except block.

Using Many Except Blocks

You can have multiple except blocks to handle different kinds of exceptions.

try:
    with open('non_existent_file.txt', 'r') as file:
        print(file.read())
    print(5/0)
except FileNotFoundError:
    print("The file does not exist.")
except ZeroDivisionError:
    print("You can't divide by zero!")

Here, if the file does not exist, a "FileNotFoundError" is raised, and the corresponding message is printed. If there's an attempt to divide by zero, a "ZeroDivisionError" is raised, and its corresponding message is printed.

Using Else

In Python, you can use an optional else clause after all except clauses. The else clause will be executed if and when the try block does not raise any exceptions.

try:
    x = 5/2
except ZeroDivisionError:
    print("You can't divide by zero!")
else:
    print('The division was successful, result is:', x)

In this example, the division does not raise a "ZeroDivisionError", so the else clause is run.

Finally Clause

We can also have an optional finally clause. The code in the finally clause will be executed regardless of whether an exception is raised:

try:
    x = open("text.txt", 'r')
except FileNotFoundError:
    print("File not found.")
finally:
    print("Cleaning up, regardless of any exceptions.")

Even if an exception is raised in the try block, Python will still run the code in the finally clause.

These are the basic principles of exception handling in Python. By using these techniques, you can make your Python code more resilient against errors and easier to debug and maintain.

Object-Oriented Programming: Classes and Objects

Object-Oriented Programming (OOP) is a programming paradigm that provides a means of structuring programs by bundling related properties and behaviors into individual objects. In Python, OOP follows some basic principles:

  1. Inheritance: A process of using details from a new class without modifying existing class.
  2. Encapsulation: Hiding the private details of a class from other objects.
  3. Polymorphism: A concept of using common operation in different ways for different data input.

Class

A class is a code template for creating objects. Objects have member variables and have behaviour associated with them. A class is an example of encapsulation as it encapsulates all the data that is member functions, variables, etc.

Defining a Class in Python

Here's a simple implementation of a class named "Car":

class Car:
    # class attribute
    color = "White"

    # instance method
    def start_engine(self):
        return "The car's engine has started."
        
    # instance method
    def stop_engine(self):
        return "The car's engine has stopped."

Objects

An Object is an instance of a Class. When a class is defined, only the description for the object is defined. Therefore, no memory or storage is allocated.

Creating Objects in Python

You create objects from a class with the following syntax: object_name = ClassName()

Here's how to create an object named "my_car" from the "Car" class:

my_car = Car()

# accessing the class attribute
print(my_car.color)  # Output: White

# calling the instance methods
print(my_car.start_engine())  # Output: The car's engine has started.
print(my_car.stop_engine())  # Output: The car's engine has stopped.

With the above implementation of a class and its object, you can define the characteristics of a car and perform certain actions on it, such as starting and stopping the engine.

The __init__ Method

This is a special method in Python classes and is automatically called to allocate memory when a new object/instance of a class is created. It sets the attributes of the class instance. All classes have the __init__ method.

Here's how to define and instantiate a class with the __init__ method:

class Car:
    # initializer / instance attributes
    def __init__(self, color, make):
        self.color = color
        self.make = make

    # instance method
    def start_engine(self):
        return f"The {self.color} {self.make}'s engine has started."
        
    # instance method
    def stop_engine(self):
        return f"The {self.color} {self.make}'s engine has stopped."
        
my_car = Car("Red", "Toyota")

print(my_car.start_engine())  # Output: The Red Toyota's engine has started.
print(my_car.stop_engine())  # Output: The Red Toyota's engine has stopped.

In this updated version, the "Car" class now accepts two parameters: color and make. These are instance attributes and they are specific to each instance of the class (each object you create from the class).

Advanced Object-Oriented Programming: Inheritance and Polymorphism

Python is an object-oriented programming language that supports inheritance and polymorphism, two fundamental pillars of object-oriented design. Inheritance allows for code re-use by enabling a new class to bring in properties and behaviors from an existing class. Polymorphism allows for a single interface to represent different data types.

In this part, we will provide a practical example demonstrating inheritance and polymorphism in Python.

Inheritance

Inheritance in Python works through the concepts of “parent” and “child” classes — the child inheriting properties and behaviors from the parent class.

Let's make a basic Person class and then extend it to a more specific Employee class.

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def introduce(self):
        print(f"Hello, my name is {self.name} and I am {self.age} years old.")

class Employee(Person):
    def __init__(self, name, age, employee_id):
        super().__init__(name, age)
        self.employee_id = employee_id

    def introduce(self):
        super().introduce()
        print(f"I work at company X and my employee ID is {self.employee_id}.")

Above, the Employee class is a child of the Person class. It extends the person class by adding an employee_id attribute. It also overrides the introduce method to introduce itself as an Employee. The super() function is used to call the __init__ of the parent class and the introduce method of the parent class.

Polymorphism

Polymorphism in Python allows us to define methods in the child class with the same name as defined in their parent class. So, we can say a single interface represents different data types.

Let's continue with our previous example. Suppose the company has different types of employees: Developer, Manager, and Intern. We can create different types of Employees and each type can have its own way of introducing themselves.

class Developer(Employee):
    def introduce(self):
        print(f"Hello, I am a developer. My name is {self.name}. My employee id is {self.employee_id}.")

class Manager(Employee):
    def introduce(self):
        print(f"Hello, I am a manager. My name is {self.name}. My employee id is {self.employee_id}.")

class Intern(Employee):
    def introduce(self):
        print(f"Hello, I am an intern. My name is {self.name}. My employee id is {self.employee_id}.")

Now, each type of Employee knows how to introduce themselves. This is polymorphism — the ability to use a shared method name in an inheritance hierarchy to perform different but conceptually similar tasks.

developer = Developer("John Doe", 30, 123)
manager = Manager("Jane Doe", 35, 456)
intern = Intern("Jimmy Doe", 20, 789)

for employee in [developer, manager, intern]:
    employee.introduce()

In the above code, we iterate through a list of employees and call the introduce method without checking its type. No matter the type, each employee knows how to introduce themselves.

Understanding Modules and Libraries

In this section, we'll focus on understanding Python modules, libraries, as well as how to import and use them in your code.

Modules

A module in Python is simply a way to organize your code. It's a file containing Python code which can define functions, classes, or variables. For example, let's create a calculator.py module:

# calculator.py

def add(x, y):
    return x + y

def subtract(x, y):
    return x - y

def multiply(x, y):
    return x * y

def divide(x, y):
    if y == 0:
        return "Cannot divide by zero"
    else:
        return x / y

Importing Modules

You can use any Python file as a module by executing an import statement in some other Python script.

# main.py

import calculator

print(calculator.add(5, 3))
print(calculator.subtract(10, 5))
print(calculator.multiply(2, 3))
print(calculator.divide(6, 2))

Libraries

A Python library is a collection of modules. When you install a Python library, you're essentially downloading a bundled distribution of related modules that can be used for a particular range of tasks.

Import Libraries

Here's an example using math library:

import math

# Use factorial method in math library
print(math.factorial(5))  

# Use sqrt (square root) method in math library
print(math.sqrt(25))   

From...Import...

Sometimes, you may wish to use a single function or class from a module, in which case, you can import it with from...import statement.

from calculator import add

print(add(20, 15))  # Prints 35

Import with Alias

You can also import a module or library with an alias, which can be useful in cases where you want to shorten the name of a module or handle conflicts between module names.

import math as m
import numpy as np

print(m.sqrt(25))   # Print square root of 25
print(np.sqrt(25))  # Print square root of 25

Conclusion

Understanding how to create, import, and use modules and libraries is crucial in Python, as it enables you to use and reuse code efficiently. It makes programs more manageable, organized, and enhances code reusability. It's a common practice to organize Python code into modules and libraries for better modularity and code clarity.

Working with External Libraries: Numpy and Pandas

Let's get started by importing these libraries if they are installed, else install them via pip.

pip install numpy pandas

and then import them to your Python code:

import numpy as np
import pandas as pd

Numpy :

Numpy provides a high-performance multidimensional array object, and tools for working with these arrays.

Create a numpy array :

arr = np.array([1, 2, 3, 4, 5])
print("Array is of type: ", type(arr))
print("No. of dimensions: ", arr.ndim)
print("Shape of array: ", arr.shape)
print("Size of array: ", arr.size)

Mathematical operations :

arr1 = np.array([[1, 2, 3], [4, 5, 6]])
arr2 = np.array([[1, 2, 3], [4, 5, 6]])

# elementwise addition
print("Addition of two arrays: \n", arr1 + arr2)

# elementwise subtraction
print("Subtraction of two arrays: \n", arr1 - arr2)

# elementwise multiplication
print("Multiplication of two arrays: \n", arr1 * arr2)

# elementwise division
print("Division of two arrays: \n", arr1 / arr2)

Pandas :

Pandas is a library used for data manipulation and analysis. It provides special data structures and data analysis tools.

Create a pandas dataframe :

df = pd.DataFrame({
   'A': ['foo', 'bar', 'foo', 'bar', 'foo', 'bar', 'foo', 'foo'],
   'B': ['one', 'one', 'two', 'three', 'two', 'two', 'one', 'three'],
   'C': np.random.randn(8),
   'D': np.random.randn(8)
})

print(df)

Selection :

print(df['A'])  # Selects 'A' column and returns series
print(df[['A','B']])  # Selects 'A' and 'B' column, returns dataframe

Filtration :

mask = df['C'] > 0  # get a boolean series where C column is greater than 0
print(df[mask])  # Get only the True values where mask is True

Grouping :

print(df.groupby('A').sum())

Apply Function :

upper = lambda x: x.upper()
df['B'] = df['B'].apply(upper)
print(df)

This is a basic introduction to Numpy and Pandas showing how to create arrays and dataframes plus how to perform simple operations on them. Each of the elements introduced here has in-depth functionality which is relevant as you start working with larger and more complex datasets.

The Final Mile: Complete A Mini Python Project

In this part, we'll be combining everything learned so far in Python Foundations to complete a mini project. We'll create a simple command line todo list application.

Setting Up

First, create a new file todo.py for our implementation.

# todo.py

# Here we will have our main application implementation.

Todo List Application Class

class TodoApp:
    def __init__(self):
        self.todo_list = []

    def add_item(self, task):
        self.todo_list.append(task)

    def remove_item(self, index):
        del self.todo_list[index]

    def view_items(self):
        for index, task in enumerate(self.todo_list):
            print(f"{index+1}. {task}")

    def clear_items(self):
        self.todo_list.clear()

File Operation Functions

import os
import json

def load_data(file_name):
    if os.path.exists(file_name):
        with open(file_name, "r") as f:
            data = json.load(f)
            return data
    return []

def save_data(file_name, data):
    with open(file_name, "w") as f:
        json.dump(data, f)

Main Application Loop

def main():
    app = TodoApp()
    FILE_NAME = "todolist.json"
    app.todo_list = load_data(FILE_NAME)
    while True:
        print("\n---Todo Application---")
        print("1. View All Tasks")
        print("2. Add New Task")
        print("3. Remove Existing Task")
        print("4. Clear All Tasks")
        print("5. Exit")
        choice = input("\nSelect an option: ")
        if choice == "1":
            app.view_items()
        elif choice == "2":
            new_task = input("\nEnter new task: ")
            app.add_item(new_task)
            save_data(FILE_NAME, app.todo_list)
        elif choice == "3":
            task_num = int(input("\nEnter task number to remove: ")) - 1
            app.remove_item(task_num)
            save_data(FILE_NAME, app.todo_list)
        elif choice == "4":
            app.clear_items()
            save_data(FILE_NAME, app.todo_list)
        elif choice == "5":
            break
        else:
            print("\nInvalid choice. Please try again.")

Finally, we'll run our main application loop.

if __name__ == "__main__":
    main()

With this, you should be able to run your todo list application from the command line with python todo.py. Your tasks will be saved and loaded from todolist.json.

You can add new tasks, view all tasks, remove specific tasks by their listed number, clear all current tasks, and exit the application.

Remember, the data will be persistent due to file operations. When you exit and run the application again, your tasks saved in todolist.json will be loaded back into the application.