This document provides an overview of different types of classes in Python, including their implementation and best practices.
- Basic Classes and Instances
- Class Variables
- Instance Attributes
- Class Methods
- Static Methods
- Dataclasses
- Abstract Base Classes
- Inheritance
- Polymorphism
- Encapsulation
- Magic Methods
- Property Decorator
- Iterator Class
- Best Practices
A class is a blueprint to create instances. Instances are created based on a blueprint called a class. A class defines attributes as data and methods as functions associated with it.
class Employee:
def __init__(self, first, last, pay):
self.first = first # instance variable
self.last = last # instance variable
self.pay = pay
# Creating instances
emp1 = Employee("Arthur", "Klark", 1000)
emp2 = Employee("Brigitte", "Klark", 2000)Class variables are variables that are shared between all instances of a class. They are declared within the class but outside of any class methods.
class Employee:
raise_amount = 1.04 # class variable
def __init__(self, first, last, pay):
self.first = first
self.last = last
self.pay = pay
def apply_raise(self):
self.pay = int(self.pay * self.raise_amount)
emp1 = Employee('Arthur', 'Klark', 1000)
emp1.apply_raise()
print(emp1.pay) # Output: 1040Instance attributes are unique to each instance of a class. They are defined within methods, typically in the __init__ method.
class Employee:
def __init__(self, first, last, pay):
self.first = first # instance attribute
self.last = last # instance attribute
self.pay = pay # instance attribute
self.email = f"{first}.{last}@company.com" # instance attribute
# Each instance has its own copy of these attributes
emp1 = Employee("Arthur", "Klark", 1000)
emp2 = Employee("Brigitte", "Klark", 2000)Class methods receive the class as the first argument instead of an instance. They are defined using the @classmethod decorator.
class Employee:
raise_amount = 1.04
@classmethod
def set_raise_amount(cls, amount):
cls.raise_amount = amount
@classmethod
def from_string(cls, emp_str):
"""Create an employee from a string in format 'first-last-pay'"""
first, last, pay = emp_str.split('-')
return cls(first, last, int(pay))
# Usage
Employee.set_raise_amount(1.07)
emp_str = 'Johnny-Mnemonic-100000'
new_emp = Employee.from_string(emp_str)Static methods don't automatically receive any argument and only take the arguments that are explicitly provided. They are defined using the @staticmethod decorator.
import datetime
class Employee:
@staticmethod
def is_workday(day):
if day.weekday() == 5 or day.weekday() == 6:
return False
return True
# Usage
my_date = datetime.date(2017, 10, 22)
print(Employee.is_workday(my_date)) # Output: FalseDataclasses are a decorator and functions for automatically adding generated special methods to classes. They reduce boilerplate code.
from dataclasses import dataclass
@dataclass
class Employee:
first: str
last: str
pay: int
email: str = None
def __post_init__(self):
if self.email is None:
self.email = f"{self.first}.{self.last}@company.com"
# Usage
emp = Employee("Arthur", "Klark", 1000)
print(emp) # Automatically generates __repr__Abstract Base Classes (ABCs) define a blueprint for other classes. They can't be instantiated and require subclasses to implement abstract methods.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)Inheritance allows a new class to be based on an existing class, inheriting its attributes and methods.
class Employee:
def __init__(self, first, last, pay):
self.first = first
self.last = last
self.pay = pay
class Developer(Employee):
raise_amount = 1.10 # Override class variable
def __init__(self, first, last, pay, prog_lang):
super().__init__(first, last, pay)
self.prog_lang = prog_lang
class Manager(Employee):
def __init__(self, first, last, pay, employees=None):
super().__init__(first, last, pay)
self.employees = employees if employees is not None else []
def add_emp(self, emp):
if emp not in self.employees:
self.employees.append(emp)
def print_emp(self):
for emp in self.employees:
print(f"-> {emp.fullname()}")
# Usage
dev1 = Developer('Arthur', 'Klark', 1000, 'Python')
mng1 = Manager('Robert', 'Doyle', 500, [dev1])Polymorphism allows objects of different classes to be treated as objects of a common superclass.
class Animal:
def speak(self):
pass
class Dog(Animal):
def speak(self):
return "Woof!"
class Cat(Animal):
def speak(self):
return "Meow!"
def animal_sound(animal):
print(animal.speak())
# Usage
dog = Dog()
cat = Cat()
animal_sound(dog) # Output: Woof!
animal_sound(cat) # Output: Meow!Encapsulation is the bundling of data and methods that operate on that data within a single unit, and restricting access to some of the object's components.
class BankAccount:
def __init__(self, balance):
self._balance = balance # Protected attribute
self.__pin = "1234" # Private attribute
def get_balance(self):
return self._balance
def deposit(self, amount):
if amount > 0:
self._balance += amount
def withdraw(self, amount, pin):
if pin == self.__pin and amount <= self._balance:
self._balance -= amount
return True
return FalseMagic methods (or dunder methods) are special methods that start and end with double underscores. They allow you to define how objects behave with built-in operations.
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return Vector(self.x - other.x, self.y - other.y)
def __str__(self):
return f"Vector({self.x}, {self.y})"
def __len__(self):
return 2
# Usage
v1 = Vector(2, 4)
v2 = Vector(1, 3)
v3 = v1 + v2
print(v3) # Output: Vector(3, 7)The @property decorator makes a method accessible as an attribute instead of a method.
class Employee:
def __init__(self, first, last):
self.first = first
self.last = last
@property
def fullname(self):
return f"{self.first} {self.last}"
# Usage
emp = Employee('Arthur', 'Klark')
print(emp.fullname) # Note: no parentheses neededAn iterator class implements the iter and next methods to provide a sequence of values.
class SquaresIterator:
def __init__(self, max_root_value):
self.max_root_value = max_root_value
self.current_root_value = 0
def __iter__(self):
return self
def __next__(self):
if self.current_root_value >= self.max_root_value:
raise StopIteration
square_value = self.current_root_value ** 2
self.current_root_value += 1
return square_value
# Usage
for num in SquaresIterator(5):
print(num) # Output: 0, 1, 4, 9, 16- Use type hints for better code clarity
- Validate inputs to prevent errors
- Use proper docstrings for documentation
- Handle exceptions appropriately
- Use inheritance when appropriate
- Keep methods focused and single-purpose
- Use property decorators for computed attributes
- Implement proper iterator protocols when needed
- Use dataclasses to reduce boilerplate code
- Implement proper encapsulation for data protection
- Use abstract base classes for interface definition
- Leverage polymorphism for flexible code design
- Implement appropriate magic methods for custom behavior
- Python 3.6+
- No external dependencies required (except for dataclasses which are built-in since Python 3.7)