Python Object-Oriented Programming Core Concepts and Practical Examples

Procedural programming focuses on the step-by-step workflow to implement a feature, requiring developers to handle every execution stage manually. Object-oriented programming shifts focus to reusable entities that can complete the target task, eliminating the need to implement every underlying logic manually.

Common real-world comparisons:

  1. Laundry Procedural (hand wash): Take off dirty clothes, prepare a basin, pour water, add detergent, soak for 30 mins, scrub clothes, wring out, drain water, rinse repeatedly, wring again, hang to dry. OOP (machine wash): Take off dirty clothes, load into washing machine, start wash cycle, take out clean clothes to hang.

  2. Purchase a laptop Procedural: Confirm requirement, research hardware parameters, compare multiple models, check promotion events, negotiate price with seller, place order, receive package, test device, confirm receipt. OOP: Confirm requirement, ask assistant to handle purchase, receive laptop.

  3. Meal preparation Procedural: Feel hungry, buy groceries, wash and sort ingredients, cut vegetables, preheat stove, cook, serve food, eat, wash dishes. OOP: Feel hungry, order takeout, eat.


Classes and Objects

Two core concepts form the foundation of object-oriented programming: classes and objects. Objects are concrete runtime entities, while classes are abstract templates that group objects with identical attributes and behaviors.

Classes

Classes in object-oriented languages are designed to model real-world entities, consisting of two core components:

  • Attributes: Descriptive features of an entity that define what the entity is.
  • Behaviors: Callable capabilities of an entity that define what the entity can do. A class is an abstract template, for example a mobile phone design blueprint, while an object is a concrete instance produced from the template, i.e. a working physical mobile phone. One class can generate any number of unique object instances.

Objects

Objects are tangible, concrete instances of classes that exist in runtime memory. Every object is created based on its parent class template.

Class Composition

A standard Python class includes three parts:

  1. Class identifier (class name)
  2. Attribute set (data variables)
  3. Method set (callable behaviors)

Define a Class

The basic syntax for defining a class in Python is as follows:

class ClassName:
    # method and attribute definitions

Example: Define a Champion class for game characters

# New-style class definition (recommended for Python 3)
class Champion(object):
    def introduce(self):
        print("Every champion has their own unique origin story.")

Notes:

  • Python 3 supports both classic (old-style) and new-style classes, new-style classes inherit from object explicitly and are the recommended practice.
  • object is the root parent class for all built-in and custom classes in Python.
  • Class names follow the UpperCamelCase naming convention.
  • Instance methods require self as the first parameter, which points to the current object instance when called. You can rename self to any valid variable name, but this convention is universally adopted.

Create Object Instances

You can generate multiple independent objects from a defined class, using the following syntax:

object_instance1 = ClassName()
object_instance2 = ClassName()

Example: Create a champion instance for Tryndamere

class Champion(object):
    def introduce(self):
        print("self points to the current object instance:", self)
        print("Each instance is a unique champion entity.")

tryndamere = Champion()
tryndamere.introduce()
# Print the memory address of the instance, matches the self output above
print(tryndamere)
# Print decimal format of the memory address
print(id(tryndamere))

The self parameter always points to the object that calls the current instance method, as demonstrated in the following example:

class WashingMachine:
    def run_wash(self):
        print("Starting laundry cycle")
        print("Current machine instance:", self)

haier_model1 = WashingMachine()
print("Haier machine 1 address:", haier_model1)
haier_model1.run_wash()

haier_model2 = WashingMachine()
print("Haier machine 2 address:", haier_model2)

The output confirms that self and the object instance share the same memory address.

Add and Access Object Attributes

Attributes are the characteristic values of an object, which can be set and retrieved both inside and outside the class definition.

class Champion(object):
    def move(self):
        print("Moving to target location")
    def attack(self):
        print("Launching standard attack")

tryndamere = Champion()
# Add custom attributes to the instance
tryndamere.champ_name = "Tryndamere"
tryndamere.health = 2800
tryndamere.attack_damage = 480
tryndamere.armor = 220

# Access attribute values
print(f"Champion {tryndamere.champ_name} Health: {tryndamere.health}")
print(f"Champion {tryndamere.champ_name} Attack Damage: {tryndamere.attack_damage}")
print(f"Champion {tryndamere.champ_name} Armor: {tryndamere.armor}")

# Call instance methods
tryndamere.move()
tryndamere.attack()

Magic Methods

Methods with names wrapped in double underscores __xx__ are called magic methods in Python, which have pre-defined special functions.

__init__() Initialization Method

The __init__() method is used to initialize instance attributes when an object is created, and is called automatically without manual invocation.

class Champion(object):
    def __init__(self):
        # Initialize default attribute values during instantiation
        self.champ_name = "Tryndamere"
        self.health = 2800
        self.attack_damage = 480
        self.armor = 220
    def move(self):
        print(f"{self.champ_name} is moving to target location")
    def attack(self):
        print("Launching standard attack")

tryndamere = Champion()
tryndamere.move()
tryndamere.attack()

Notes:

  • __init__() runs automatically immediately after an object is created.
  • The self parameter is passed automatically by the Python interpreter, no manual input is required.

Parameterized __init__() Method

You can add custom parameters to __init__() to set different attribute values for different instances:

class Champion(object):
    def __init__(self, name, skill, health, ad, armor):
        self.champ_name = name
        self.skill = skill
        self.health = health
        self.attack_damage = ad
        self.armor = armor
    def move(self):
        print(f"{self.champ_name} is moving to target location")
    def cast_skill(self):
        print(f"Casting {self.skill}")
    def show_stats(self):
        print(f"Champion {self.champ_name} | Health: {self.health} | AD: {self.attack_damage} | Armor: {self.armor}")

tryndamere = Champion("Tryndamere", "Spinning Slash", 2800, 480, 220)
garen = Champion("Garen", "Demacian Justice", 4500, 280, 420)

tryndamere.show_stats()
garen.show_stats()
# Instance attributes are stored independently for each object
print(id(tryndamere.champ_name))
print(id(garen.champ_name))
# Instance methods are shared across all instances of the same class to save memory
print(id(tryndamere.move))
print(id(garen.move))

Notes:

  • All instance attributes and methods inside the class are accessed via self.
  • All attributes and methods outside the class are accessed via the object instance name.
  • Each instance has independent storage for its attributes, while methods are shared across all instances. The interpreter uses self to identify which instance called the method.

__str__() Method

By default, printing an object outputs its memory address. If you define a __str__() method in the class, printing the object will return the string value specified in the __str__() return statement.

class Champion(object):
    def __init__(self, name, skill, health, ad, armor):
        self.champ_name = name
        self.skill = skill
        self.health = health
        self.attack_damage = ad
        self.armor = armor
    def move(self):
        print(f"{self.champ_name} is moving to target location")
    def cast_skill(self):
        print(f"Casting {self.skill}")
    def __str__(self):
        return f"<Champion: {self.champ_name}> | Health: {self.health} | AD: {self.attack_damage} | Armor: {self.armor}"

tryndamere = Champion("Tryndamere", "Spinning Slash", 2800, 480, 220)
garen = Champion("Garen", "Demacian Justice", 4500, 280, 420)

print(tryndamere)
print(garen)
# Print class docstring
print(Champion.__doc__)

The __str__() method must return a string value, which is typically used as a human-readable description of the object instance.

__del__() Destructor Method

The __del__() method is called automatically when an object instance is deleted and its memory is reclaimed.

class Champion(object):
    def __init__(self, name):
        print("__init__() method executed")
        self.champ_name = name
    def __del__(self):
        print("__del__() method executed")
        print(f"{self.champ_name} has been removed from the game")

tryndamere = Champion("Tryndamere")
print(f"Deleting instance with address {id(tryndamere)}")
del tryndamere

print("-"*20)

garen = Champion("Garen")
garen_ref1 = garen
garen_ref2 = garen

print(f"Deleting garen reference, address {id(garen)}")
del garen
print(f"Deleting garen_ref1 reference, address {id(garen_ref1)}")
del garen_ref1
print(f"Deleting garen_ref2 reference, address {id(garen_ref2)}")
del garen_ref2

Notes:

  • Each time a variable references an object, the object's reference count increases by 1.
  • Each del call on a reference reduces the reference count by 1. The object is only deleted and memory reclaimed when the reference count reaches 0.

Practical Example 1: Roasted Sweet Potato Simulator

Requirements:

  1. Roast status changes with cooking time:
    • 0-2 minutes: Raw
    • 2-4 minutes: Half-cooked
    • 4-7 minutes: Fully cooked
    • Over 7 minutes: Burnt
  2. Users can add custom seasonings during cooking.

Implementation Code:

class RoastedSweetPotato:
    def __init__(self):
        self.cook_duration = 0
        self.status = "Raw"
        self.seasonings = []
    
    def cook(self, time):
        self.cook_duration += time
        if 0 <= self.cook_duration < 2:
            self.status = "Raw"
        elif 2 <= self.cook_duration <4:
            self.status = "Half-cooked"
        elif 4 <= self.cook_duration <7:
            self.status = "Fully cooked"
        else:
            self.status = "Burnt"
    
    def add_seasoning(self, seasoning):
        self.seasonings.append(seasoning)
    
    def __str__(self):
        return f"Sweet potato cooked for {self.cook_duration} minutes, status: {self.status}, added seasonings: {self.seasonings}"

potato = RoastedSweetPotato()
print(potato)
potato.cook(2)
potato.add_seasoning("Soy sauce")
print(potato)
potato.cook(2)
potato.add_seasoning("Chili powder")
print(potato)
potato.cook(2)
print(potato)
potato.cook(2)
print(potato)

Practical Example 2: Furniture Moving Simulator

Requirements:

Place furniture items into a house if the furniture area is smaller than the remaining free area of the house.

Implementation Code:

class Furniture:
    def __init__(self, item_name, area_occupied):
        self.name = item_name
        self.area = area_occupied

class House:
    def __init__(self, location, total_area):
        self.location = location
        self.total_area = total_area
        self.free_area = total_area
        self.furniture_list = []
    
    def __str__(self):
        return f"House at {self.location}, total area: {self.total_area}㎡, remaining free area: {self.free_area}㎡, furniture: {self.furniture_list}"
    
    def place_furniture(self, furniture_item):
        if self.free_area >= furniture_item.area:
            self.furniture_list.append(furniture_item.name)
            self.free_area -= furniture_item.area
            print(f"Successfully placed {furniture_item.name}")
        else:
            print(f"Not enough space to place {furniture_item.name}, required {furniture_item.area}㎡, remaining {self.free_area}㎡")

king_bed = Furniture("King size bed", 8)
sofa = Furniture("L-shaped sofa", 12)
apartment = House("Shanghai Pudong", 120)
print(apartment)
apartment.place_furniture(king_bed)
print(apartment)
apartment.place_furniture(sofa)
print(apartment)
basketball_court = Furniture("Full basketball court", 200)
apartment.place_furniture(basketball_court)
print(apartment)

Inheritance

Inheritance in Python refers to the relationship where a child class inherits all public attributes and methods from its parent class by default. All classes in Python inherit from the root object class by default.

# Parent class
class Base:
    def __init__(self):
        self.count = 10
    def show_count(self):
        print(self.count)

# Child class inherits from Base
class Derived(Base):
    pass

instance = Derived()
instance.show_count() # Output 10

Single Inheritance

A child class inherits from exactly one parent class.

# Master class (parent)
class PancakeMaster:
    def __init__(self):
        self.recipe = "Traditional pancake recipe"
    def make_pancake(self):
        print(f"Making pancake using {self.recipe}")

# Apprentice class (child) inherits from PancakeMaster
class Apprentice(PancakeMaster):
    pass

da_qiu = Apprentice()
print(da_qiu.recipe)
da_qiu.make_pancake()

Even if the child class does not define an __init__ method or instance methods, it automatically inherits all accessible members from the parent class.

Multiple Inheritance

A child class can inherit from multiple parent classes at the same time.

class PancakeMaster:
    def __init__(self):
        self.recipe = "Traditional pancake recipe"
    def make_pancake(self):
        print(f"Making pancake using {self.recipe}")

class TrainingSchool:
    def __init__(self):
        self.recipe = "Modern commercial pancake recipe"
    def make_pancake(self):
        print(f"Making pancake using {self.recipe}")

# Child class inherits from both TrainingSchool and PancakeMaster
class Apprentice(TrainingSchool, PancakeMaster):
    pass

da_qiu = Apprentice()
print(da_qiu.recipe)
da_qiu.make_pancake()

Note: If multiple parent classes have attributes or methods with the same name, the child class uses the member from the first parent class listed in the inheritance order by default, following the order specified in the class's __mro__ attribute.

Child Class Override Parent Class Members

A child class can define attributes and methods with the same name as parent class members to override them.

class PancakeMaster:
    def __init__(self):
        self.recipe = "Traditional pancake recipe"
    def make_pancake(self):
        print(f"Making pancake using {self.recipe}")

class TrainingSchool:
    def __init__(self):
        self.recipe = "Modern commercial pancake recipe"
    def make_pancake(self):
        print(f"Making pancake using {self.recipe}")

class Apprentice(TrainingSchool, PancakeMaster):
    def __init__(self):
        self.recipe = "Original secret pancake recipe"
    def make_pancake(self):
        print(f"Making pancake using {self.recipe}")

da_qiu = Apprentice()
print(da_qiu.recipe)
da_qiu.make_pancake()
print(Apprentice.__mro__)

When a child class has a member with the same name as a parent class member, the child class's implementation takes priority.

Call Parent Class Members from Child Class

You can explicitly call parent class attributes and methods even if they are overridden by the child class.

class PancakeMaster:
    def __init__(self):
        self.recipe = "Traditional pancake recipe"
    def make_pancake(self):
        print(f"Making pancake using {self.recipe}")

class TrainingSchool:
    def __init__(self):
        self.recipe = "Modern commercial pancake recipe"
    def make_pancake(self):
        print(f"Making pancake using {self.recipe}")

class Apprentice(TrainingSchool, PancakeMaster):
    def __init__(self):
        self.recipe = "Original secret pancake recipe"
    def make_pancake(self):
        self.__init__()
        print(f"Making pancake using {self.recipe}")
    def make_traditional_pancake(self):
        PancakeMaster.__init__(self)
        PancakeMaster.make_pancake(self)
    def make_commercial_pancake(self):
        TrainingSchool.__init__(self)
        TrainingSchool.make_pancake(self)

da_qiu = Apprentice()
da_qiu.make_pancake()
da_qiu.make_traditional_pancake()
da_qiu.make_commercial_pancake()
da_qiu.make_pancake()

The self parameter always points to the child class instance, even when calling parent class methods.

Multi-level Inheritance

Inheritance relationships can be passed across multiple levels of classes.

class PancakeMaster:
    def __init__(self):
        self.recipe = "Traditional pancake recipe"
    def make_pancake(self):
        print(f"Making pancake using {self.recipe}")

class TrainingSchool(PancakeMaster):
    def __init__(self):
        self.recipe = "Modern commercial pancake recipe"
    def make_pancake(self):
        print(f"Making pancake using {self.recipe}")

class Apprentice(TrainingSchool):
    def __init__(self):
        self.recipe = "Original secret pancake recipe"
    def make_pancake(self):
        self.__init__()
        print(f"Making pancake using {self.recipe}")
    def make_traditional_pancake(self):
        PancakeMaster.__init__(self)
        PancakeMaster.make_pancake(self)
    def make_commercial_pancake(self):
        TrainingSchool.__init__(self)
        TrainingSchool.make_pancake(self)

# Grandchild class inherits from Apprentice
class JuniorApprentice(Apprentice):
    pass

xiao_qiu = JuniorApprentice()
xiao_qiu.make_pancake()
xiao_qiu.make_commercial_pancake()
xiao_qiu.make_traditional_pancake()

super() Method

The super() method provides a simplified way to call parent class members, automatically following the __mro__ inheritance order.

class PancakeMaster:
    def __init__(self):
        self.recipe = "Traditional pancake recipe"
    def make_pancake(self):
        print(f"Making pancake using {self.recipe}")

class TrainingSchool(PancakeMaster):
    def __init__(self):
        self.recipe = "Modern commercial pancake recipe"
    def make_pancake(self):
        print(f"Making pancake using {self.recipe}")
        super().__init__()
        super().make_pancake()

class Apprentice(TrainingSchool):
    def __init__(self):
        self.recipe = "Original secret pancake recipe"
    def make_pancake(self):
        self.__init__()
        print(f"Making pancake using {self.recipe}")
    def make_legacy_pancake(self):
        super().__init__()
        super().make_pancake()

da_qiu = Apprentice()
da_qiu.make_legacy_pancake()

super() is most suitable for single inheritance scenarios, as it automatically handles parent class lookup and avoids hardcoding parent class names.

Polymorphism

Polymorphism refers to the ability to call the same method on different subclass objects and get different execution results, relying on inheritance and method overriding. Implementation steps:

  1. Define a parent class with a public base method
  2. Define child classes that override the parent class method
  3. Pass different subclass instances to the caller to get different results
class WorkingDog:
    def perform_task(self):
        print("Executing assigned task")

class PatrolDog(WorkingDog):
    def perform_task(self):
        print("Pursuing suspect")

class NarcoticsDog(WorkingDog):
    def perform_task(self):
        print("Searching for illegal drugs")

class Officer:
    def assign_task(self, dog):
        dog.perform_task()

patrol_dog = PatrolDog()
narcotics_dog = NarcoticsDog()
officer = Officer()
officer.assign_task(patrol_dog)
officer.assign_task(narcotics_dog)

Polymorphism enables highly flexible and reusable code that can adapt to changing requirements with minimal modification.


Attribute and Method Access Control

Private Attributes and Methods

You can set private access permissions for attributes and methods by adding two leading underscores __ to their names, preventing them from being accessed directly outside the class or inherited by child classes.

class PancakeMaster:
    def __init__(self):
        self.recipe = "Traditional pancake recipe"
    def make_pancake(self):
        print(f"Making pancake using {self.recipe}")

class TrainingSchool:
    def __init__(self):
        self.recipe = "Modern commercial pancake recipe"
    def make_pancake(self):
        print(f"Making pancake using {self.recipe}")

class Apprentice(TrainingSchool, PancakeMaster):
    def __init__(self):
        self.recipe = "Original secret pancake recipe"
        # Private attribute, only accessible inside the class
        self.__savings = 2000000
    # Private method, only accessible inside the class
    def __show_private_info(self):
        print(f"Recipe: {self.recipe}, Savings: {self.__savings}")
    def make_pancake(self):
        self.__init__()
        print(f"Making pancake using {self.recipe}")

class JuniorApprentice(Apprentice):
    pass

da_qiu = Apprentice()
# Direct access to private members will throw an error
# print(da_qiu.__savings)
# da_qiu.__show_private_info()

xiao_qiu = JuniorApprentice()
# Child classes cannot inherit private members from parent classes
# print(xiao_qiu.__savings)

Modify Private Attribute Values

Private attributes can be modified via public getter and setter methods defined inside the class.

class Apprentice(TrainingSchool, PancakeMaster):
    def __init__(self):
        self.recipe = "Original secret pancake recipe"
        self.__savings = 2000000
    # Getter method for private attribute
    def get_savings(self):
        return self.__savings
    # Setter method for private attribute
    def set_savings(self, amount):
        self.__savings = amount
    def make_pancake(self):
        self.__init__()
        print(f"Making pancake using {self.recipe}")

da_qiu = Apprentice()
da_qiu.set_savings(3500000)
print(da_qiu.get_savings())

This approach allows controlled access to private data, ensuring data integrity and validation if needed.

Class Attributes vs Instance Attributes

Class attributes belong to the class itself, shared by all instances of the class, and only store one copy in memory. Instance attributes belong to individual object instances, with independent storage for each instance.

class Person:
    # Class attribute
    nation = "China"
    # Private class attribute
    __planet = "Earth"

p1 = Person()
# Access public class attribute via instance or class
print(p1.nation)
print(Person.nation)
# Private class attributes cannot be accessed directly outside the class
# print(p1.__planet)
# print(Person.__planet)

Instance attributes are defined inside the __init__ method:

class Person:
    nation = "China"
    def __init__(self):
        # Instance attributes
        self.name = "Xiao Wang"
        self.age = 22

p1 = Person()
p1.age = 24
print(p1.nation)
print(p1.name)
print(p1.age)
print(Person.nation)
# Instance attributes cannot be accessed via the class
# print(Person.name)
# print(Person.age)

Modifying class attributes via the class affects all instances, while modifying a class attribute via an instance creates a new instance attribute that masks the class attribute for that specific instance:

class Person:
    nation = "China"

print(Person.nation)
p1 = Person()
print(p1.nation)
p1.nation = "Japan"
print(p1.nation) # Outputs instance attribute value
print(Person.nation) # Class attribute remains unchanged
del p1.nation # Delete the instance attribute
print(p1.nation) # Outputs class attribute value again

Class Methods and Static Methods

Class Methods

Class methods are marked with the @classmethod decorator, take cls (the class itself) as the first parameter, and can be accessed via both the class and instance objects.

class Person:
    nation = "China"
    @classmethod
    def get_nation(cls):
        return cls.nation
    @classmethod
    def set_nation(cls, new_nation):
        cls.nation = new_nation

p1 = Person()
print(p1.get_nation())
print(Person.get_nation())
Person.set_nation("Canada")
print(p1.get_nation())
print(Person.get_nation())

Class methods are typically used to access or modify class attributes.

Static Methods

Static methods are marked with the @staticmethod decorator, do not require any mandatory default parameters, and can be accessed via both the class and instance objects.

class Person:
    nation = "China"
    @staticmethod
    def get_nation():
        return Person.nation

p1 = Person()
print(p1.get_nation())
print(Person.get_nation())

Static methods are used for utility functions that do not need to access instance or class attributes.


Practical Example: Counter-Strike Simple Simulation

Requirements:

  1. Guns have different models and damage values, can hold bullets, and shoot enemies.
  2. Players have health points, can equip guns, shoot enemies, and take damage.
  3. When a player's health drops to 0, they are marked as dead.

Implementation Code:

class Firearm:
    def __init__(self, model, damage_per_shot):
        self.model = model
        self.damage = damage_per_shot
        self.ammo = 0
    def __str__(self):
        return f"Model: {self.model}, Damage: {self.damage}, Ammo remaining: {self.ammo}"
    def reload(self, bullet_count):
        self.ammo += bullet_count
    def fire(self, target):
        if self.ammo <= 0:
            print("Out of ammo, cannot fire")
            return
        self.ammo -= 1
        target.take_damage(self)

class GamePlayer:
    def __init__(self, role, player_name):
        self.role = role
        self.name = player_name
        self.health = 100
        self.weapon = None
        self.is_dead = False
    def __str__(self):
        return f"Role: {self.role}, Name: {self.name}, Health: {self.health}, Status: {'Dead' if self.is_dead else 'Alive'}, Weapon: {self.weapon}"
    def shoot_enemy(self, target):
        if not self.weapon:
            print("No weapon equipped, cannot attack")
            return
        if self.weapon.ammo <= 0:
            print("Out of ammo, reloading 5 bullets")
            self.weapon.reload(5)
            return
        self.weapon.fire(target)
    def take_damage(self, enemy_weapon):
        self.health -= enemy_weapon.damage
        if self.health < 0:
            self.health = 0
        print(f"Player {self.name} took {enemy_weapon.damage} damage, remaining health: {self.health}")
        if self.health == 0:
            self.is_dead = True
            print(f"Player {self.name} has been eliminated")

# Main game logic
ak47 = Firearm("AK-47", 35)
policeman = GamePlayer("Police", "Xiao Ming")
terrorist = GamePlayer("Terrorist", "Cobra")
policeman.weapon = ak47

while not terrorist.is_dead:
    policeman.shoot_enemy(terrorist)

Object-Oriented Practical Project: Student Management System

System Requirements

Implement a student management system using object-oriented programming, with features including add, delete, modify, query, display all students, save data to file, and load data from file. The system consists of three code files:

  1. main.py: System entry point
  2. student.py: Student entity class definition
  3. student_manager.py: Core management logic class

main.py Code:

from student_manager import StudentManager

if __name__ == "__main__":
    system = StudentManager()
    system.run()

student.py Code:

class Student:
    def __init__(self, stu_id, stu_name, stu_score):
        self.id = stu_id
        self.name = stu_name
        self.score = stu_score
    def __str__(self):
        return f"{self.id},{self.name},{self.score}"

student_manager.py Code:

from student import Student

class StudentManager:
    def __init__(self):
        self.student_storage = {}
    def run(self):
        self.load_data("students.txt")
        while True:
            self.display_menu()
            choice = int(input("Please enter your operation choice: "))
            if choice == 1:
                self.add_student()
            elif choice == 2:
                self.show_all_students()
            elif choice == 3:
                self.search_student()
            elif choice ==4:
                self.update_student()
            elif choice ==5:
                self.delete_student()
            elif choice ==0:
                self.save_data("students.txt")
                print("Exiting system, goodbye!")
                break
            else:
                print("Invalid choice, please try again!\n")
    @staticmethod
    def display_menu():
        print("*"*35)
        print("Welcome to Student Management System V1.0")
        print("1. Add new student")
        print("2. Show all students")
        print("3. Search student by ID")
        print("4. Update student information")
        print("5. Delete student")
        print("0. Exit system")
        print("*"*35)
    def add_student(self):
        stu_id = input("Enter student ID: ")
        if stu_id in self.student_storage:
            print(f"Student with ID {stu_id} already exists!\n")
            return
        stu_name = input("Enter student name: ")
        stu_score = input("Enter student score: ")
        new_student = Student(stu_id, stu_name, stu_score)
        self.student_storage[stu_id] = new_student
        print(f"Student {stu_id} added successfully!\n")
    def show_all_students(self):
        if not self.student_storage:
            print("No student records found in the system!\n")
            return
        print(f"{'ID':<12}{'Name':<15}{'Score':<8}")
        print("-"*35)
        for stu in self.student_storage.values():
            print(f"{stu.id:<12}{stu.name:<15}{stu.score:<8}")
        print("-"*35 + "\n")
    def search_student(self):
        stu_id = input("Enter student ID to search: ")
        if stu_id not in self.student_storage:
            print(f"Student with ID {stu_id} not found!\n")
            return
        stu = self.student_storage[stu_id]
        print(f"{'ID':<12}{'Name':<15}{'Score':<8}")
        print("-"*35)
        print(f"{stu.id:<12}{stu.name:<15}{stu.score:<8}")
        print("-"*35 + "\n")
    def update_student(self):
        stu_id = input("Enter student ID to update: ")
        if stu_id not in self.student_storage:
            print(f"Student with ID {stu_id} not found!\n")
            return
        new_name = input("Enter new name: ")
        new_score = input("Enter new score: ")
        stu = self.student_storage[stu_id]
        stu.name = new_name
        stu.score = new_score
        print(f"Student {stu_id} updated successfully!\n")
    def delete_student(self):
        stu_id = input("Enter student ID to delete: ")
        if stu_id not in self.student_storage:
            print(f"Student with ID {stu_id} not found!\n")
            return
        del self.student_storage[stu_id]
        print(f"Student {stu_id} deleted successfully!\n")
    def save_data(self, file_path):
        with open(file_path, "w", encoding="utf-8") as f:
            for stu in self.student_storage.values():
                f.write(str(stu) + "\n")
    def load_data(self, file_path):
        try:
            with open(file_path, "r", encoding="utf-8") as f:
                for line in f:
                    line = line.strip()
                    if not line:
                        continue
                    stu_id, stu_name, stu_score = line.split(",")
                    self.student_storage[stu_id] = Student(stu_id, stu_name, stu_score)
        except FileNotFoundError:
            pass

Tags: python Object-Oriented Programming OOP Python Tutorial Programming Basics

Posted on Thu, 23 Jul 2026 16:26:04 +0000 by renesis