Python
Exploring Classes

Exploring Python's Class

Class Variables and Instance Variables

There are two types of variables that can be declared in a class: class variables and instance variables.

class Dog:
    # This is a class variable
    species = "Canis familiaris"
 
    def __init__(self, name, age):
	    # These are instance variables
        self.name = name
        self.age = age

Class Variables

Class variables, also known as class attributes, are variables that are shared by all instances of a class. They are defined within the class but outside any of the class's methods. Class variables are not as common as instance variables.

▶ In this Dog class, species is a class variable. All instances of Dog will have the same species.

Instance Variables

Instance variables, also known as instance attributes, are variables that are unique to each instance. They are defined within methods and are usually set in the __init__ method, which is called when an object is created.

▶ In the Dog class above, name and age are instance variables. They are set in the __init__ method and can have different values for different Dog objects.

Accessing Class and Instance Variables

You can access class variables and instance variables using dot notation. Here's how you can access the species, name, and age of a Dog object:

# Create a Dog object
d = Dog('Fido', 3)
 
# Access the species, name, and age
print(d.species)  # Outputs: Canis familiaris
print(d.name)     # Outputs: Fido
print(d.age)      # Outputs: 3

▶ In the above code, d.species gives us the class variable species, and d.name and d.age give us the instance variables name and age.

Remember, if you change a class variable, it will change for all instances of the class. But if you change an instance variable, it will only change for that instance.

Instance methods vs. Class methods vs. Static methods

There are three types of methods that can be defined within a Python's class: Instance Methods, Class Methods, and Static Methods. Each of these methods serves a different purpose and has different behavior. Let's explore each one along with examples:

Instance Methods

Instance methods are the most common type of methods in Python classes. They operate on instance-level data and have access to instance variables, class variables and other instance methods. The first parameter of an instance method is always 'self', which is a reference to the instance that is calling the method.

class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age
 
    def introduce(self):
        return f"Hi, I'm {self.name}, and I'm {self.age} years old."
 
# Creating Dog instances
dog1 = Dog("Buddy", 3)
 
# Calling the instance method
print(dog1.introduce())  # Output: Hi, I'm Buddy, and I'm 3 years old.
  • The Dog class is defined. It has a constructor method (__init__) that takes name and age as parameters. Inside the constructor, instance attributes name and age are assigned with the provided values.
  • An instance method named introduce is defined. This method uses the instance attributes name and age to create a string that introduces the dog's name and age.
  • One instance of the Dog class is created dog1, with a name and an age.
  • The introduce method is called on instance (dog1), and it generates a personalized introduction for the dog using its attributes.

Class Methods

Class methods are defined using the @classmethod decorator. They operate on class-level data and have access to class variables and other class methods. The first parameter of a class method is always 'cls', which is a reference to the class that is calling the method.

class MyClass:
    class_attribute = "This is a class attribute"
    class_instances = 0 
 
    def __init__(self, instance_attribute):
        self.instance_attribute = instance_attribute
        MyClass.class_instances += 1
 
    @classmethod
    def class_method(cls):
        print(f"Accessing class attribute: {cls.class_attribute}")
        print(f"Class has run {cls.class_instances} times")
 
    def instance_method(self):
        print(f"Accessing instance attribute: {self.instance_attribute}")
 
# Creating an instance of MyClass
obj = MyClass("This is an instance attribute")
 
# Using the class method to access class attribute
obj.class_method() # or MyClass.class_method()
# Using the instance method to access instance attribute
obj.instance_method()
Accessing class attribute: This is a class attribute 
Class has run 1 times 
Accessing instance attribute: This is an instance attribute
  • The MyClass class is defined with a class attribute class_attribute and another class attribute class_instances to keep track of the number of times the class has been instantiated.
  • The __init__ constructor is defined. It initializes the instance with an instance_attribute and increments the class_instances class attribute to count the number of instances created. Each time an instance is created, class_instances is incremented.
  • A class method class_method is defined using the @classmethod decorator. This method accesses the class attribute class_attribute and also prints the number of times the class has been instantiated using the class_instances class attribute.
  • An instance method instance_method is defined. This method accesses the instance attribute instance_attribute.
  • Class and instance method is being called that print the messages.

Static Methods

Static methods are defined using the @staticmethod decorator. They don't have access to instance-specific or class-specific data, and they behave like regular functions but are defined within a class for organization purposes.

class MathUtils:
    @staticmethod
    def multiply(x, y):
        return x * y
 
    @staticmethod
    def square(x):
        return x ** 2
 
# Using static methods
product = MathUtils.multiply(5, 3)
squared = MathUtils.square(4)
 
print(product)  # Output: 15
print(squared)  # Output: 16
  • The MathUtils class is defined. It contains two static methods: multiply and square
  • multiply static method takes two arguments x and y. Inside the method, the product of x and y is calculated and the result is returned as the output of the method.
  • square static method takes a single argument x. Inside the method, the square of x is calculated and the result is returned as the output of the method.
  • The static methods are called directly on the class MathUtils. The multiply method is used to calculate the product of 5 and 3, and the square method is used to calculate the square of 4.

To summarize:

  • Instance methods are used for operations that need access to instance-specific data.
  • Class methods are used for operations that involve class-level data and don't need access to instance-specific data.
  • Static methods are used for operations that don't depend on either instance-specific or class-specific data.

Comprehensive Example

# Define a class called BankAccount
class BankAccount:
    # Class variable to keep track of total accounts
    total_accounts = 0  
 
    # Initializer for the class
    def __init__(self, account_number, balance):
        # Instance variables for account number and balance
        self.account_number = account_number
        self.balance = balance
        # Increment total_accounts on instance creation
        BankAccount.total_accounts += 1  
 
    # Instance method for deposit
    def deposit(self, amount):
        self.balance += amount
 
    # Instance method for withdrawal
    def withdraw(self, amount):
        if self.balance >= amount:
            self.balance -= amount
        else:
            print("Insufficient balance")
 
    # Instance method to get balance
    def get_balance(self):
        return self.balance
 
    # Class method to get total number of accounts
    @classmethod
    def get_total_accounts(cls):
        return cls.total_accounts
 
    # Static method to check if an account number is valid
    @staticmethod
    def is_valid_account_number(account_number):
        return len(account_number) == 5
 
# Creating BankAccount instances
account1 = BankAccount("12345", 1000)
account2 = BankAccount("67890", 500)
 
# Performing transactions using instance methods
account1.deposit(200)
account1.withdraw(50)
account2.deposit(1000)
account2.withdraw(700)
 
# Displaying balance for accounts using instance methods
print(f"Closing balance of account 1 is ${account1.get_balance()}")
print(f"Closing balance of account 2 is ${account2.get_balance()}")
 
# Validating account numbers using static method
print("Is '12345' a valid account number?", BankAccount.is_valid_account_number("12345"))  
print("Is '789' a valid account number?", BankAccount.is_valid_account_number("789"))        
 
# Displaying total number of accounts using class method
print("Total accounts:", BankAccount.get_total_accounts())  
 
Closing balance of account 1 is $1150 
Closing balance of account 2 is $800 
Is '12345' a valid account number? True 
Is '789' a valid account number? False 
Total accounts: 2

Explanation

Let's break down the code step by step and explain each part:

class BankAccount:
    total_accounts = 0 
  • The BankAccount class is defined.
  • total_accounts is a class variable that keeps track of the total number of bank accounts created.
    def __init__(self, account_number, balance):
        self.account_number = account_number
        self.balance = balance
        BankAccount.total_accounts += 1 
  • The __init__ method is the class constructor that initializes a bank account with an account_number and balance.
  • account_number and balance are instance attributes specific to each account.
  • BankAccount.total_accounts is incremented every time a new account instance is created, maintaining a count of total accounts.
    def deposit(self, amount):
        self.balance += amount
  • The deposit method takes an amount parameter and increases the balance of the account by that amount.
    def withdraw(self, amount):
        if self.balance >= amount:
            self.balance -= amount
        else:
            print("Insufficient balance")
  • The withdraw method takes an amount parameter and checks if the account has sufficient balance before subtracting the amount.
  • If the balance is insufficient, it prints an error message.
    def get_balance(self):
        return self.balance
  • The get_balance method returns the current balance of the account.
    @classmethod
    def get_total_accounts(cls):
        return cls.total_accounts
  • The get_total_accounts class method returns the total number of bank accounts created. It's a class-level operation.
    @staticmethod
    def is_valid_account_number(account_number):
        return len(account_number) == 5
  • The is_valid_account_number static method takes an account_number parameter and checks if it has a length of 5 characters.
  • It returns True if the account number is valid (5 characters), otherwise False.

The code continues by creating two bank account instances (account1 and account2), performing transactions using instance methods (deposit and withdraw), displaying account balances, validating account numbers using the static method, and finally, displaying the total number of accounts using the class method.