Python Classes | Object-Oriented Programming (OOP) Explained

Python Classes | Object-Oriented Programming (OOP) Explained

이 글의 핵심

Hands-on guide to Python classes: constructors, self, class vs instance attributes, inheritance, properties, and dunder methods.

Introduction: What is OOP?

Object-oriented programming models programs as objects that combine state and behavior.

Procedural vs OOP (sketch):

# Procedural
def create_account(owner, balance):
    return {"owner": owner, "balance": balance}

def deposit(account, amount):
    account["balance"] += amount

# OOP
class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner
        self.balance = balance

    def deposit(self, amount):
        self.balance += amount

    def withdraw(self, amount):
        if amount > self.balance:
            return False
        self.balance -= amount
        return True

Benefits: encapsulation, reuse via inheritance, clearer boundaries for change.


1. Classes and instances

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def greet(self):
        return f"Hi, I'm {self.name}."

    def is_adult(self):
        return self.age >= 18

p = Person("Alice", 25)
print(p.greet())

Understanding self

class Counter:
    def __init__(self):
        self.count = 0

    def increment(self):
        self.count += 1
        return self.count

c1 = Counter()
c2 = Counter()

Class vs instance attributes

class Student:
    school = "Example High"

    def __init__(self, name):
        self.name = name

2. Constructors and __del__

class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self.balance = balance

    def deposit(self, amount):
        self.balance += amount
        return self.balance

__del__ exists but rely on context managers (with) for cleanup instead of destructor semantics.


3. Inheritance and super()

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        return "sound"

class Dog(Animal):
    def speak(self):
        return f"{self.name}: woof!"

class Cat(Animal):
    def speak(self):
        return f"{self.name}: meow!"

print(isinstance(Dog("x"), Animal))
class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

class Manager(Employee):
    def __init__(self, name, salary, team_size):
        super().__init__(name, salary)
        self.team_size = team_size

Multiple inheritance and MRO

class Flyer:
    def fly(self):
        return "flying"

class Swimmer:
    def swim(self):
        return "swimming"

class Duck(Flyer, Swimmer):
    pass

print(Duck.__mro__)

4. Encapsulation and @property

Convention: _protected, __private (name mangling to _ClassName__attr).

class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner
        self.__balance = balance

    def get_balance(self):
        return self.__balance

    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
            return True
        return False
class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def radius(self):
        return self._radius

    @radius.setter
    def radius(self, value):
        if value < 0:
            raise ValueError("radius must be non-negative")
        self._radius = value

    @property
    def area(self):
        return 3.14159 * self._radius ** 2

5. Magic methods (dunder)

MethodPurpose
__init__Initialize instance
__str__ / __repr__String forms
__len__, __getitem__Container protocol
__add__, __eq__Operators / equality
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 __repr__(self):
        return f"Vector({self.x}, {self.y})"

6. @classmethod and @staticmethod

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    @classmethod
    def from_birth_year(cls, name, birth_year):
        from datetime import date
        age = date.today().year - birth_year
        return cls(name, age)

class MathUtils:
    @staticmethod
    def is_even(n):
        return n % 2 == 0

7. Abstract base classes

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

class Rectangle(Shape):
    def __init__(self, w, h):
        self.w, self.h = w, h

    def area(self):
        return self.w * self.h

8. Polymorphism

Same interface, different behavior—often via overridden methods or shared ABCs.


9. Practical example: library system

from datetime import datetime, timedelta

class Book:
    def __init__(self, isbn, title, author, available=True):
        self.isbn = isbn
        self.title = title
        self.author = author
        self.available = available

    def __str__(self):
        status = "available" if self.available else "on loan"
        return f"[{self.isbn}] {self.title}{self.author} ({status})"

    def __eq__(self, other):
        return self.isbn == other.isbn

class Member:
    def __init__(self, member_id, name):
        self.member_id = member_id
        self.name = name
        self.borrowed_books = []

    def __str__(self):
        return f"Member {self.name} ({self.member_id})"

class Library:
    def __init__(self):
        self.books = {}
        self.members = {}
        self.loan_records = []

    def add_book(self, book):
        self.books[book.isbn] = book
        print(f"Added book: {book.title}")

    def register_member(self, member):
        self.members[member.member_id] = member
        print(f"Registered member: {member.name}")

    def borrow_book(self, member_id, isbn):
        if member_id not in self.members:
            return "Unknown member"
        if isbn not in self.books:
            return "Unknown book"
        book = self.books[isbn]
        member = self.members[member_id]
        if not book.available:
            return f"'{book.title}' is already on loan"
        book.available = False
        member.borrowed_books.append(isbn)
        due = datetime.now() + timedelta(days=14)
        self.loan_records.append({
            "member": member_id,
            "book": isbn,
            "borrow_date": datetime.now(),
            "due_date": due,
        })
        return (
            f"{member.name} borrowed '{book.title}' "
            f"(due {due.strftime('%Y-%m-%d')})"
        )

    def return_book(self, member_id, isbn):
        if isbn not in self.books:
            return "Unknown book"
        book = self.books[isbn]
        member = self.members[member_id]
        if isbn not in member.borrowed_books:
            return "This book was not borrowed by this member"
        book.available = True
        member.borrowed_books.remove(isbn)
        return f"{member.name} returned '{book.title}'"

    def list_available_books(self):
        return [b for b in self.books.values() if b.available]

# Example usage
library = Library()
library.add_book(Book("978-1", "Python Basics", "A. Author"))
library.register_member(Member("M001", "Dana"))
print(library.borrow_book("M001", "978-1"))
print(library.return_book("M001", "978-1"))

OOP principles (short)

  • Encapsulation: hide internals; expose methods/properties.
  • Inheritance: extend behavior; use super() intentionally.
  • Polymorphism: program to interfaces (duck typing or ABCs).
  • Abstraction: hide complexity behind simple APIs.

Common mistakes

  1. Forgetting self in __init__ / methods.
  2. Mutable class attributes shared across instances—use instance attributes in __init__.
  3. Skipping super().__init__(...) when the parent must initialize.
  4. Assigning to a @property with no setter.

Summary

  1. Class = blueprint; instance = concrete object.
  2. __init__ initializes; self is the instance.
  3. Inheritance + super(); understand MRO for multiple inheritance.
  4. Encapsulation with naming, __, and @property.
  5. Dunder methods customize operators and built-ins.
  6. @classmethod / @staticmethod for factories and utilities.
  7. ABC + @abstractmethod for contracts.

Next steps

  • Python modules and packages
  • Python exception handling
  • Python decorators