Practical on Unit V
1.Program to access class variable using class object.
2.Program to access class members using class object.
3.Program to illustrating the use of __int__() method.
4.Program to differentiate between class and object variable.
5.Program to illustrating the use of __del__() method.
6.Program to illustrating the difference between public and private
variable.
7.The program should subtract the DOB from todays date to find out
whether a person is eligibleto vote or not.
8. Create class EMPLOYEE for storing details (Name, Designation,
gender, Date of Joining and Salary). Define function members to
compute a)total number of employees in an organization
b) count of male and female employee c) Employee with salary more
than 10,000 d) Employee with designation “Asst Manager”
Structured and object oriented: Features of Object oriented
programming-classes, objects, methods
and message passing, inheritance, polymorphism, containership,
reusability, delegation, data abstraction and encapsulation.
Classes and Objects: classes and objects, class method and self-
argument, __init__() method, class
variables and object variables, __del__() method, public and private
members, Built in function to
check, Get, Set and Delete class attribute, Garbage collection, class
methods, Static Method.
Features of Object oriented programming-
classes, objects, methods and message passing, inheritance,
polymorphism, containership, reusability, delegation, data
abstraction and encapsulation.
Object-Oriented Programming (OOP) in Python provides a way to
structure programs using objects, which encapsulate both data and
behavior. Below are the key features of OOP in Python:
1. Classes and Objects
Class:
  ● Collection of data members and member function
  ● It defines attributes (variables) and methods (functions) that the
    objects created from the class will have.
  ● A blueprint for creating objects.
  ● We use the class keyword to create a class in Python.
  ● Syntax for declaring class:
     class ClassName:
          # class definition
         Here, we have created a class named ClassName.
         E.g class Car:
  ● Object:
     An instance of a class with attributes (data) and methods
     (functions).
     Syntax for Creating object:
     Object name= class name()
     e.g.car1 = Car("Toyota", "Corolla")
class Car:
      def __init__(self, brand, model):
         self.brand = brand # Attribute(Data members)
         self.model = model # Attribute(Data Members)
         def display(self):   #method /function
             print(f"Car: {self.brand} \n {self.model}")
# Creating an object
car1 = Car("Toyota", "Corolla") #Object Creation
car1.display() # Method/function Call
Key Concepts of Classes and Objects
  1. __init__ Method (Constructor):
        ○ This is a special method that runs automatically when a new
          object is created.
        ○ It is used to initialize object attributes.
  2. Attributes (Instance Variables):
        ○ Variables that belong to an instance of a class.
        ○ Defined inside the __init__ method using self.
  3. Methods:
        ○ Functions defined inside a class that operate on objects.
  4. Self Keyword (self):
        ○ Represents the instance of the class.
        ○ Used to access attributes and methods within the class.
2. Methods and Message Passing
  ● Methods: Functions defined inside a class that operate on objects.
  ● Message Passing: Objects communicate by calling each other’s
    methods.
class Animal:
      def speak(self):
         print("Animal speaks")
class Dog(Animal):
      def speak(self):
      print("Dog barks")
# Message passing
dog = Dog()
dog.speak() # Calls the Dog’s speak method
3. Inheritance
  ● Allows a class (child) to derive properties and behavior from
    another class (parent).
  ● Promotes code reuse and hierarchical classification.
Example:
class Vehicle:
      def __init__(self, brand):
          self.brand = brand
      def show_brand(self):
         print(f"Brand: {self.brand}")
class Car(Vehicle):
      def __init__(self, brand, model):
         super().__init__(brand)
        self.model = model
      def show_details(self):
      print(f"Car: {self.brand} {self.model}")
# Using inheritance
car = Car("Honda", "Civic")
car.show_details()
4. Polymorphism
  ● A subclass provides a specific implementation of a method already
    defined in the parent class.
class Shape:
      def area(self):
        return 0
class Circle(Shape):
      def __init__(self, radius):
          self.radius = radius
      def area(self):
         return 3.14 * self.radius * self.radius
circle = Circle(5)
print(circle.area()) # Calls overridden method in Circle class
5. Containership (Composition)
  ● Containership (Composition): A class contains objects of another
    class to reuse functionality.
class Engine:
      def start(self):
      print("Engine started")
class Car:
      def __init__(self):
      self.engine = Engine() # Car has an Engine
      def start_car(self):
      self.engine.start() # Delegating to Engine
# Containership in action
car = Car()
car.start_car()
6. Reusability
  ● OOP allows reusing existing code through inheritance and
    containership.
7. Delegation
  ● Delegation allows one object to delegate a task to another.
class Printer:
      def print_message(self, message):
      print(message)
class Computer:
      def __init__(self):
      self.printer = Printer()
      def delegate_printing(self, message):
      self.printer.print_message(message) # Delegating printing to Printer class
# Delegation
pc = Computer()
pc.delegate_printing("Hello, OOP!")
8. Data Abstraction
  ● Hiding complex implementation details and exposing only
    necessary parts.
from abc import ABC, abstractmethod
class Animal(ABC):
      @abstractmethod
      def make_sound(self):
      pass # Abstract method
class Dog(Animal):
      def make_sound(self):
      return "Bark"
dog = Dog()
print(dog.make_sound())
9. Encapsulation
  ● Hiding data using private (__) attributes.
class BankAccount:
      def __init__(self, balance):
      self.__balance = balance # Private variable
      def deposit(self, amount):
      self.__balance += amount
      def get_balance(self):
      return self.__balance
# Encapsulation in action
account = BankAccount(1000)
account.deposit(500)
print(account.get_balance()) # Accessing through method
======================================================
======================================================
Class method and self-argument-
In Python, a class method and the self argument are related to object-
oriented programming but serve different purposes.
1. self Argument
  ● The self parameter is used in instance methods of a class.
  ● It represents the instance (object) of the class and allows access to
    its attributes and methods.
Example:
class Car:
      def __init__(self, brand):
      self.brand = brand # Instance attribute
      def show_brand(self):
      print(f"The car brand is {self.brand}")
# Creating an instance
my_car = Car("Toyota")
# Calling an instance method
my_car.show_brand() # Output: The car brand is Toyota
2. Class Methods
  ● Defined using the @classmethod decorator.
  ● Take cls as the first parameter instead of self, representing the
    class itself.
  ● Can modify class-level attributes and work with the class rather
    than instances.
Example:
class Car:
      brand = "Generic" # Class attribute
      def __init__(self, model):
      self.model = model
      @classmethod
      def change_brand(cls, new_brand):
      cls.brand = new_brand # Modifies class-level attribute
# Changing brand for all instances
Car.change_brand("Tesla")
# Creating instances
car1 = Car("Model S")
car2 = Car("Model 3")
print(car1.brand) # Output: Tesla
print(car2.brand) # Output: Tesla
=======================================================
=======================================================
 __init__() method
The __init__() method in Python is a special method (also called a
constructor) used to initialize objects of a class.
It is called automatically when a new instance of a class is created.
Syntax:
class MyClass:
      def __init__(self, param1, param2):
        self.param1 = param1
        self.param2 = param2
How it Works:
  ● The __init__() method takes self as its first argument, which refers
    to the instance being created.
  ● Additional parameters can be passed to initialize instance
    attributes.
Example:
class Person:
      def __init__(self, name, age):
        self.name = name
        self.age = age
# Creating an instance
p1 = Person("Alice", 25)
# Accessing attributes
print(p1.name) # Output: Alice
print(p1.age) # Output: 25
Key Points:
  ● The __init__() method is optional, but if defined, it is automatically
    executed when an object is created.
  ● It does not return anything (None).
  ● It is used to set initial values for object attributes.
===================================================
Class Variable and Object variable-
1. Class Variables
In Python, class variables and instance (object) variables are two
different types of variables used in object-oriented programming. Here's
how they differ:
1. Class Variables
  ● Defined inside a class but outside any instance methods.
  ● Shared among all instances of the class.
  ● Changes to a class variable affect all instances unless an instance
    overrides it.
  ● Declared directly in the class body.
class Car:
      wheels = 4 # Class variable (shared by all instances)
      def __init__(self, brand):
         self.brand = brand # Instance variable (unique to each object)
car1 = Car("Toyota")
car2 = Car("Honda")
print(car1.wheels) # Output: 4
print(car2.wheels) # Output: 4
# Changing class variable
Car.wheels = 6
print(car1.wheels) # Output: 6
print(car2.wheels) # Output: 6
2. Instance (Object) Variables
  ● Defined inside the __init__ method or any instance method using
    self.
  ● Unique to each object (not shared).
  ● Changing an instance variable only affects that specific object.
Example:
class Car:
      wheels = 4 # Class variable
      def __init__(self, brand, color):
         self.brand = brand # Instance variable
         self.color = color # Instance variable
car1 = Car("Toyota", "Red")
car2 = Car("Honda", "Blue")
print(car1.brand, car1.color) # Output: Toyota Red
print(car2.brand, car2.color) # Output: Honda Blue
# Changing an instance variable only affects that object
car1.color = "Black"
print(car1.color) # Output: Black
print(car2.color) # Output: Blue (unchanged)
======================================================
__del__()
The __del__() method in Python is a special method known as the
destructor. It is called when an object is about to be destroyed (i.e., when
it is garbage collected). The primary use of __del__() is to perform any
necessary cleanup, such as closing file connections or releasing resources.
Syntax
class MyClass:
      def __del__(self):
      print("Destructor called. Object is being deleted.")
obj = MyClass()
del obj # Explicitly deleting the object
Key Points About __del__()
Automatic Invocation:
      The __del__() method is automatically called   when an object is
no longer in use.
Garbage Collection:
       In Python, memory management is handled by garbage collection.
When an object has no references, the garbage collector deletes it and
calls __del__().
Explicit Deletion:
     You can force the deletion of an object using del object_name, but
this doesn’t guarantee immediate execution of __del__(), as Python
decides when to run garbage collection.
  Use Cases:
    Closing file handles
    Releasing network connections
    Cleaning up temporary resources
Example
class FileHandler:
   def __init__(self, filename):
     self.file = open(filename, 'w')
     print(f"File {filename} opened.")
   def __del__(self):
     self.file.close()
     print("File closed.")
# Creating an instance
handler = FileHandler("example.txt")
# Deleting the instance
del handler # Calls __del__()
======================================================
======================================================
In object-oriented programming (OOP), public and private members
define the accessibility of variables (fields) and methods (functions)
inside a class.
Public Members
  ● Those variables that are defined in the class and can be accessed
     from within the class and from outside the class.
  ● All members in a python class are public default.
  ● Accessible from anywhere: Other classes and external code can
     access public members.
class Car:
      def __init__(self, brand):
         self.brand = brand # Public member
      def show_brand(self):
         print("Brand:", self.brand)
car = Car("Toyota")
print(car.brand) # Allowed
car.show_brand() # Allowed
Private Members
  ● Accessible only inside the class: They cannot be accessed directly
    from outside.
  ● Those variables that are defined in the classand can be accessed
    from within the class and from outside the class.
  ● The private variable are used with double underscore(_ _)prefix
class Car:
      def __init__(self, brand):
      self.__brand = brand # Private member (double underscore)
     def set_brand(self, brand):
     self.__brand = brand
     def show_brand(self):
     print("Brand:", self.__brand)
car = Car("Toyota")
# print(car.__brand) # ❌ AttributeError: private
variable
car.set_brand("Honda") # ✅ Allowed via setter
method
car.show_brand()
  ● In Python, a setter method, often used with properties, is a method
    that allows you to set or modify the value of an attribute within a
    class, potentially with validation or other logic applied during the
    setting process.
======================================================
======================================================
Built in function to
check, Get, Set and Delete class attribute
In Python, built-in functions exist to check, get, set, and delete class
attributes dynamically. These functions are:
   1. hasattr(obj, attr_name) – Checks if an attribute exists.
   2. getattr(obj, attr_name, default) – Gets the attribute value.
   3. setattr(obj, attr_name, value) – Sets the attribute to a value.
   4. delattr(obj, attr_name) – Deletes the attribute.
 class Person:
       def __init__(self, name, age):
       self.name = name
       self.age = age
 p = Person("Alice", 25)
 # Check if attribute exists
 print(hasattr(p, "name")) # True
 # Get attribute value
 print(getattr(p, "name")) # Alice
 print(getattr(p, "gender", "Not Found")) # Not Found (default value)
 # Set a new attribute
 setattr(p, "gender", "Female")
 print(p.gender) # Female
 # Delete an attribute
 delattr(p, "age")
 print(hasattr(p, "age")) # False
Garbage collection
  ● Garbage collection (GC) in Python is an automatic memory
    management feature that helps free up memory occupied by
    objects that are no longer in use. It is mainly handled by Python's
    built-in garbage collector, which is part of the gc module.
  ● Python's gc module provides functions to interact with the garbage
    collector.
1. Check if GC is enabled
import gc
print(gc.isenabled()) # Returns True if GC is enabled
2. Manually Trigger Garbage Collection
gc.collect() # Forces collection of unused objects
3. Disable and Enable GC
gc.disable() # Turns off garbage collection
gc.enable() # Turns it back on
=-
======================================================
=====================================================
Class Methods (@classmethod)
  ● A class method works with the class itself rather than instances of
    the class.
  ● It is defined using the @classmethod decorator.
  ● The first parameter is conventionally named cls, which represents
    the class.
  class MyClass:
        class_variable = "Hello"
       @classmethod
       def class_method(cls):
       return f"Class method called. class_variable = {cls.class_variable}"
  print(MyClass.class_method()) # Calling without an instance
     Key Points:
  ● Uses @classmethod decorator.
  ● The first parameter (cls) refers to the class, not an instance.
  ● Can modify class attributes but not instance attributes.
======================================================
======================================================
Static Methods (@staticmethod)
  ● A static method does not take self or cls as the first parameter.
  ● It behaves like a regular function but belongs to the class's
    namespace.
  ● It is defined using the @staticmethod decorator.
Example:
class MathOperations:
      @staticmethod
      def add(a, b):
      return a + b
print(MathOperations.add(5, 3)) # Output: 8
Key Points:
  ● Uses @staticmethod decorator.
  ● No access to class (cls) or instance (self) attributes.
  ● Used for utility functions related to the class.
  ●
Differences Between Class Method and Static Method
        Feature               Class Method               Static Method
                            (@classmethod)             (@staticmethod)
Takes cls as first         Yes                      No
parameter?
Can modify class           Yes                      No
attributes?
Can modify instance        No                       No
attributes?
Can be called on class? Yes                         Yes
Can be called on an        Yes                      Yes
instance?
======================================================
======================================================
      8. Create class EMPLOYEE for storing details (Name, Designation,
      gender, Date of Joining and Salary). Define function members to
      compute a)total number of employees in an organization
class EMPLOYEE:
      total_employees = 0 # Class variable to track total employees
     def __init__(self, name, designation, gender, date_of_joining, salary):
     self.name = name
     self.designation = designation
     self.gender = gender
     self.date_of_joining = date_of_joining
     self.salary = salary
     EMPLOYEE.total_employees += 1 # Increment total employees count
     @classmethod
     def get_total_employees(cls):
     return cls.total_employees
     def display_details(self):
     print(f"Name: {self.name}, Designation: {self.designation}, Gender: {self.gender},
           Date of Joining: {self.date_of_joining}, Salary: {self.salary}")
# Example Usage
emp1 = EMPLOYEE("Alice", "Manager", "Female", "2022-05-10", 75000)
emp2 = EMPLOYEE("Bob", "Developer", "Male", "2021-08-15", 60000)
emp3 = EMPLOYEE("Charlie", "Analyst", "Male", "2023-01-20", 50000)
print("Total Employees:", EMPLOYEE.get_total_employees())