Skip to content

Classes and Objects in Detail

Deep Dive into Object Creation and Usage


Class Structure

python
class ClassName:
    """Class docstring"""

    # Class attribute (shared by all objects)
    class_variable = "shared"

    def __init__(self, param1, param2):
        """Constructor"""
        self.param1 = param1  # Instance attribute
        self.param2 = param2

    def instance_method(self):
        """Instance method"""
        return self.param1

    @classmethod
    def class_method(cls):
        """Class method"""
        return cls.class_variable

    @staticmethod
    def static_method():
        """Static method"""
        return "Does not depend on class or instance"

Instance Attributes vs Class Attributes

python
class Survey:
    # Class attribute (shared by all surveys)
    total_surveys = 0

    def __init__(self, name, year):
        # Instance attributes (unique to each survey)
        self.name = name
        self.year = year
        Survey.total_surveys += 1  # Modify class attribute

survey1 = Survey("Income Survey", 2024)
survey2 = Survey("Health Survey", 2024)

print(survey1.name)           # Income Survey (instance attribute)
print(Survey.total_surveys)   # 2 (class attribute)

Common Special Methods

__init__: Constructor

python
class Respondent:
    def __init__(self, id, age, income):
        self.id = id
        self.age = age
        self.income = income
        print(f"Created respondent {id}")

resp = Respondent(1001, 30, 75000)  # Automatically calls __init__

__str__: String Representation

python
class Respondent:
    def __init__(self, id, age):
        self.id = id
        self.age = age

    def __str__(self):
        return f"Respondent(ID={self.id}, Age={self.age})"

resp = Respondent(1001, 30)
print(resp)  # Respondent(ID=1001, Age=30)

__repr__: Developer Representation

python
class Respondent:
    def __init__(self, id, age):
        self.id = id
        self.age = age

    def __repr__(self):
        return f"Respondent({self.id}, {self.age})"

resp = Respondent(1001, 30)
print(repr(resp))  # Respondent(1001, 30)

__len__: Length

python
class Survey:
    def __init__(self, name):
        self.name = name
        self.responses = []

    def add_response(self, response):
        self.responses.append(response)

    def __len__(self):
        return len(self.responses)

survey = Survey("Test")
survey.add_response({'id': 1})
survey.add_response({'id': 2})
print(len(survey))  # 2

Practical Cases

Case: Student Management System

python
class Student:
    """Student class"""
    school_name = "Peking University"  # Class attribute

    def __init__(self, student_id, name, major, gpa=0.0):
        self.student_id = student_id
        self.name = name
        self.major = major
        self.gpa = gpa
        self.courses = []

    def enroll_course(self, course_name, credits):
        """Enroll in a course"""
        self.courses.append({
            'name': course_name,
            'credits': credits
        })

    def get_total_credits(self):
        """Calculate total credits"""
        return sum(c['credits'] for c in self.courses)

    def update_gpa(self, new_gpa):
        """Update GPA"""
        if 0 <= new_gpa <= 4.0:
            self.gpa = new_gpa
        else:
            print("GPA must be between 0-4.0")

    def __str__(self):
        return f"{self.name} ({self.major}, GPA: {self.gpa})"

# Usage
alice = Student(2024001, "Alice Wang", "Economics", 3.8)
alice.enroll_course("Microeconomics", 4)
alice.enroll_course("Econometrics", 4)

print(alice)
print(f"Total credits: {alice.get_total_credits()}")
print(f"School: {Student.school_name}")

Case: Survey Data Container

python
class SurveyData:
    """Survey data management class"""

    def __init__(self, survey_name):
        self.survey_name = survey_name
        self.responses = []

    def add_response(self, response):
        """Add a response"""
        if self._validate(response):
            self.responses.append(response)
            return True
        return False

    def _validate(self, response):
        """Private method: validate data"""
        required_fields = ['id', 'age', 'income']
        return all(field in response for field in required_fields)

    def get_average_income(self):
        """Calculate average income"""
        if not self.responses:
            return 0
        incomes = [r['income'] for r in self.responses]
        return sum(incomes) / len(incomes)

    def filter_by_age(self, min_age, max_age):
        """Filter by age"""
        return [r for r in self.responses
                if min_age <= r['age'] <= max_age]

    def __len__(self):
        return len(self.responses)

    def __str__(self):
        return f"{self.survey_name}: {len(self)} responses"

# Usage
survey = SurveyData("2024 Income Survey")
survey.add_response({'id': 1, 'age': 30, 'income': 75000})
survey.add_response({'id': 2, 'age': 35, 'income': 85000})

print(survey)
print(f"Average income: ${survey.get_average_income():,.0f}")
print(f"Ages 30-40: {len(survey.filter_by_age(30, 40))} people")

Property Decorator

@property: Computed Attributes

python
class Respondent:
    def __init__(self, income, tax_rate=0.25):
        self.income = income
        self.tax_rate = tax_rate

    @property
    def net_income(self):
        """Access as an attribute, not a method"""
        return self.income * (1 - self.tax_rate)

    @property
    def tax_amount(self):
        return self.income * self.tax_rate

resp = Respondent(100000, 0.3)
print(resp.net_income)   # 70000 (access like an attribute)
print(resp.tax_amount)   # 30000
# No need for resp.net_income()

Encapsulation: Public vs Private

python
class BankAccount:
    def __init__(self, balance):
        self.balance = balance       # Public attribute
        self._transactions = []      # Convention private (single underscore)
        self.__pin = "1234"          # True private (double underscore)

    def deposit(self, amount):
        """Public method"""
        self.balance += amount
        self._log_transaction("deposit", amount)

    def _log_transaction(self, type, amount):
        """Private method (convention)"""
        self._transactions.append({
            'type': type,
            'amount': amount
        })

account = BankAccount(1000)
account.deposit(500)
print(account.balance)         # 1500 (accessible)
print(account._transactions)   # Accessible but not recommended
# print(account.__pin)         # AttributeError

Practice Problems

Exercise 1: Create a Complete Class

python
# Create a Course class
# Attributes: course_code, name, instructor, max_students, enrolled
# Methods:
#   - add_student(student_name): Add student (check capacity limit)
#   - drop_student(student_name): Remove student
#   - is_full(): Check if full
#   - get_enrollment_rate(): Return enrollment rate

Exercise 2: Implement Special Methods

python
# For the Survey class, implement:
# - __str__: Return "Survey: {name} ({count} responses)"
# - __len__: Return number of responses
# - __getitem__: Support survey[0] to access the i-th response

Next Steps

In the next section, we'll learn about OOP applications in data science.

Keep going!


Released under the MIT License. Content © Author.