27. OOP ‐ Class Methods - MantsSk/CA_PTUA14 GitHub Wiki
Class methods
Python’s classmethod()
function is a powerful tool that allows developers to manipulate class-level data in a clean and efficient way. With its ability to create factory methods, alternative constructors, manage class-level state, and implement class-level operations, classmethod()
provides a flexible and elegant solution to many programming challenges. We will explore how to use classmethod()
in Python to improve your code’s organization and readability. Using classmethod()
will enhance your Python skills and make your code more efficient and maintainable.
classmethod()
in Python?
What is classmethod()
is a built-in function in Python that is used to define a method that operates on the class instead of the instance of the class. It is used to create a method that can be called directly on the class, rather than on an instance of the class. This means that the method can be used to manipulate class-level data or perform operations that are not specific to any one instance of the class.
The syntax of classmethod()
is as follows:
class MyClass:
@classmethod
def my_class_method(cls, arg1, arg2):
# code here
The classmethod()
function is applied as a decorator to a method of a class. The first argument of a class method is always the class itself, represented by the parameter cls
. This allows the method to access and modify class-level data.
Here's an example of a class method that calculates how many vehicles we created:
class Vehicle:
total_vehicles = 0
def __init__(self):
Vehicle.total_vehicles += 1
@classmethod
def get_total_vehicles(cls):
return cls.total_vehicles
v1 = Vehicle()
v1 = Vehicle()
print("Total Vehicles:", Vehicle.get_total_vehicles()) # Would print the total number of Vehicle instances
classmethod()
?
Why use The main benefit of using classmethod()
is that it allows you to define methods that operate on the class itself, rather than on an instance of the class. This can be useful in a variety of situations, such as:
- Creating factory methods–
classmethods
can be used to create factory methods that create and return new instances of a class with a specific configuration:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def from_birth_year(cls, name, birth_year):
age = cls.get_age(birth_year)
return cls(name, age)
@staticmethod
def get_age(birth_year):
return datetime.date.today().year - birth_year
person = Person.from_birth_year('John', 1990)
print(person.name) # John
print(person.age) # 33
In this example, the from_birth_year()
class method acts as a factory method that creates a new Person
instance from a birth year. It calculates the age of the person using a static method get_age()
and returns a new instance of the Person
class.
- Managing class-level state –
classmethods
can be used to manage class-level state. This means that you can define a method that modifies or accesses a variable that is shared by all instances of the class (just like in previously shown example)
class Car:
total_cars_sold = 0
def __init__(self, make, model):
self.make = make
self.model = model
Car.total_cars_sold += 1
@classmethod
def get_total_cars_sold(cls):
return cls.total_cars_sold
car1 = Car('Toyota', 'Camry')
car2 = Car('Honda', 'Civic')
print(Car.get_total_cars_sold()) # 2
In this example, the total_cars_sold
class-level variable is incremented every time a new instance of the Car
class is created. The get_total_cars_sold()
class method returns the current value of the total_cars_sold
variable.
- Implementing class-level operations –
classmethods
can be used to implement class-level operations, such as sorting or filtering a list of instances of the class:
class Student:
all_students = []
def __init__(self, name: str, grade: float):
self.name = name
self.grade = grade
Student.all_students.append(self)
@classmethod
def get_highest_grade(cls):
return max(cls.all_students, key=lambda student: student.grade)
@classmethod
def get_lowest_grade(cls):
return min(cls.all_students, key=lambda student: student.grade)
student1: Student = Student('John', 90)
student2: Student = Student('Jane', 95)
student3: Student = Student('Alice', 80)
print(Student.get_highest_grade().name) # Jane
print(Student.get_lowest_grade().name) # Alice
The all_students
attribute is declared as a class-level list that holds Student
objects. The name attribute of the Student
instances is declared as a string, and the grade attribute is declared as a float. The get_highest_grade
and get_lowest_grade
class methods return the Student
object with the highest and lowest grade, respectively. The student1
, student2
, and student3
instances are explicitly annotated with the Student
class.
- Static vs Class methods – Using @classmethod allows the method to access class-level data and it works well with inheritance. The cls variable in class methods allows for dynamic access to class properties. Here are examples of the same code implemented both in static and class methods.
Class Method:
class Vehicle:
total_vehicles = 0
def __init__(self):
Vehicle.total_vehicles += 1
@classmethod
def get_total_vehicles(cls):
return cls.total_vehicles
class Car(Vehicle):
total_cars = 0
def __init__(self):
super().__init__()
Car.total_cars += 1
@classmethod
def get_total_vehicles(cls):
return super().get_total_vehicles() + cls.total_cars
class Truck(Vehicle):
total_trucks = 0
def __init__(self):
super().__init__()
Truck.total_trucks += 1
@classmethod
def get_total_vehicles(cls):
return super().get_total_vehicles() + cls.total_trucks
v1 = Vehicle()
c1 = Car()
c2 = Car()
t1 = Truck()
print("Total Vehicles:", Vehicle.get_total_vehicles()) # Would print the total number of Vehicle instances
print("Total Cars:", Car.get_total_vehicles()) # Would include all Vehicles + Cars
print("Total Trucks:", Truck.get_total_vehicles()) # Would include all Vehicles + Trucks
Static Method:
class Vehicle:
total_vehicles = 0
def __init__(self):
Vehicle.total_vehicles += 1
@staticmethod
def get_total_vehicles():
return Vehicle.total_vehicles
class Car(Vehicle):
total_cars = 0
def __init__(self):
super().__init__()
Car.total_cars += 1
@staticmethod
def get_total_vehicles():
return Vehicle.get_total_vehicles() + Car.total_cars
class Truck(Vehicle):
total_trucks = 0
def __init__(self):
super().__init__()
Truck.total_trucks += 1
@staticmethod
def get_total_vehicles():
return Vehicle.get_total_vehicles() + Truck.total_trucks
v1 = Vehicle()
c1 = Car()
c2 = Car()
t1 = Truck()
print("Total Vehicles:", Vehicle.get_total_vehicles()) # Would print the total number of Vehicle instances
print("Total Cars:", Car.get_total_vehicles()) # Would include all Vehicles + Cars
print("Total Trucks:", Truck.get_total_vehicles()) # Would include all Vehicles + Trucks
Notice how static methods require more explicit class name references. Each get_total_vehicles method explicitly references the specific class (Vehicle, Car, or Truck). This makes the code less flexible and more prone to error if the class hierarchy changes.
Script that uses instance methods, class methods, and static methods:
class Person:
population = 0
def __init__(self, name, age):
self.name = name
self.age = age
Person.population += 1
def introduce_self(self):
print(f"Hello, my name is {self.name} and I am {self.age} years old.")
@classmethod
def get_population(cls):
return cls.population
@staticmethod
def is_adult(age):
return age >= 18
# Creating instances
alice = Person("Alice", 30)
bob = Person("Bob", 17)
# Using instance method
alice.introduce_self()
bob.introduce_self()
# Using class method
print("Population:", Person.get_population())
# Using static method
print("Is Alice an adult?", Person.is_adult(alice.age))
print("Is Bob an adult?", Person.is_adult(bob.age))
How the Script Works: Instance Method (introduce_self): Each Person instance can introduce itself. This method uses the instance's own data (name and age).
Class Method (get_population): This method provides the count of Person instances. It uses the class variable population, which is shared across all instances.
Static Method (is_adult): This method determines if a given age is considered adult. It's a utility method that operates independently of any instance or class variables.
Exercises:
-
Create a class method to return the factorial of a given number.
-
Create a class method to return the reversed string of a given string.
-
Create a Python class named BankAccount that demonstrates the use of instance methods, class methods, and static methods. The class and its methods should provide functionality as described below. After implementing the class, create instances and use all the methods to showcase their functionality.
Class Specification
-
Class Name: BankAccount
-
Instance Attributes: owner: The name of the account owner (a string). balance: The account balance (a float). Instance Method: show_balance: Prints the current balance of the account.
-
Class Attribute: total_accounts: A class-level attribute that tracks the total number of BankAccount instances created.
-
Class Method: get_total_accounts: Returns the total number of BankAccount instances.
-
Static Method: validate_amount: Accepts an amount and returns True if it is a positive number; otherwise, it returns False.
Task for Students:
- Implement the BankAccount class as per the specifications given above.
- Create Instances: Create at least two instances of BankAccount with different owners and balances.
- Use Instance Method: Use the show_balance method for each instance to display the account balance.
- Use Class Method: Use the get_total_accounts method to display the total number of bank accounts created.
- Use Static Method: Use the validate_amount method to validate a couple of amounts (one positive and one negative) to demonstrate how the method works.
-
Create a simple bank account class,
BankAccount
, with the following specifications:- The
BankAccount
class should have an attribute balance which starts at0
. - It should have an instance method
deposit
that allows an amount to be added to the balance. - It should have an instance method
withdraw
that allows an amount to be taken from the balance. If the balance is less than the withdrawal amount, print a message that says "Insufficient funds". - Add a class method
from_balance
that takes a starting balance as an argument and returns a newBankAccount
instance with that starting balance. - Add a static method transfer that takes two
BankAccount
instances and an amount as arguments. It should withdraw the amount from the first account and deposit it into the second account.
- The
-
Create a
SpaceStation
class with the following specifications:- The
SpaceStation
class should have an attributeastronauts
which is a list of dictionaries. Each dictionary represents an astronaut and has keys:name
,nationality
, andmission_duration
. - It should have an instance method
add_astronaut
that takes aname
,nationality
, andmission duration
, creates a new astronaut dictionary, and adds it to the astronauts list. - It should have an instance method
find_astronaut
that takes aname
and returns the astronaut dictionary with thatname
, orNone
if no such astronaut is found. - Add a class method
from_astronaut_list
that takes a list of astronauts (each represented as a dictionary) and returns a newSpaceStation
instance with those astronauts. - Add a static method
is_long_term_mission
that takes an astronaut dictionary and returnsTrue
if the astronaut's mission duration is more than6
months, andFalse
otherwise. - Add an instance method
remove_astronaut
that takes aname
and removes the astronaut with that name from the astronauts list.
- The
-
Create a class to represent a library system. The library system should have a collection of books that can be borrowed by users. Users can register to the library system, borrow books, and return books. The library system should keep track of the books borrowed by users, and the number of available copies of each book. Specifications:
- Library class should have instance attribute 'books', that is a dictionary of books. Example format:
{'Title': available_copy_count, 'Harry Potter': 1
} - Library class should have instance attribute 'user', that is a dictionary of users. Example format:
{'user_id': ['name', ['list', 'of', 'book', 'titles'], '1': ['John', ['Harry Potter', 'The adventures of Tom Sawyer']}
- Library class should have instance method 'add_book', that adds a new book to 'books' list
- Library class should have instance method 'register_user', that adds a new user to 'users' list
- Library class should have instance method 'borrow_book', that adds a selected book's title to selected user's list of books and decreases the number of available book copies
- Library class should have instance method 'return_book', that removes selected book from the user and increases the number of available book copies
- Library class should have instance method 'register_user', that adds a new user to 'users' list
- Library class should have class method 'create_library_system', that accepts user and book lists and returns a new library system with the users and books
- Library class should have static method 'are_copies_available', that accepts book list and a title as parameters and returns True if the specified book has available copies and False if not