03_파이썬(Python)

파이썬(Python) 기본 문법: 객체지향 프로그래밍 — 은행 계좌 만들기

tothebest 2025. 8. 23. 16:15
728x90

안녕하세요

이번 글에서는 클래스와 객체, 생성자, 캡슐화, 메서드, 속성(Property) 등을
간단한 BankAccount(은행 계좌) 예제로 익혀보겠습니다.

 

1. 클래스와 객체 기본

  • 클래스(class): 설계도
  • 객체(object): 설계도로부터 생성된 실제 물건(인스턴스)
class BankAccount:
    # 생성자: 객체가 만들어질 때 자동 실행
    def __init__(self, owner: str, balance: int = 0):
        self.owner = owner
        self._balance = balance  # 관례상 _로 시작하면 내부용(캡슐화 암시)

    # 입금
    def deposit(self, amount: int):
        if amount <= 0:
            raise ValueError("입금액은 0보다 커야 합니다.")
        self._balance += amount

    # 출금
    def withdraw(self, amount: int):
        if amount <= 0:
            raise ValueError("출금액은 0보다 커야 합니다.")
        if amount > self._balance:
            raise ValueError("잔액이 부족합니다.")
        self._balance -= amount

    # 잔액 조회(읽기 전용 속성으로 제공)
    @property
    def balance(self) -> int:
        return self._balance

    # 객체를 보기 좋게 표현
    def __repr__(self):
        return f"BankAccount(owner='{self.owner}', balance={self._balance})"


# 사용 예시
acc = BankAccount("Alice", 1000)
acc.deposit(500)
acc.withdraw(300)
print(acc.balance)   # 1200
print(acc)           # BankAccount(owner='Alice', balance=1200)

 

포인트

  • __init__ : 생성자
  • _balance : 외부 직접 수정 지양(캡슐화 의도)
  • @property : 읽기 전용 속성으로 제공 → acc.balance처럼 함수 호출 없이 접근
  • __repr__ : 객체 출력 시 가독성 향상

 

2. 캡슐화와 유효성 검사

직접 속성 수정보다 메서드를 통해서만 변경하도록 설계하면
유효성 검사(음수 금지, 한도 확인 등)를 일관되게 적용할 수 있습니다.

acc = BankAccount("Bob", 0)

try:
    acc.deposit(-10)  # 예외 발생
except ValueError as e:
    print("에러:", e)

try:
    acc.withdraw(1)   # 예외 발생 (잔액 부족)
except ValueError as e:
    print("에러:", e)

 

3. 클래스 변수 vs 인스턴스 변수

  • 인스턴스 변수: 객체마다 다른 값 (self.owner, self._balance)
  • 클래스 변수: 모든 객체가 공유하는 값 (예: 계좌 개수 카운트)
class BankAccount:
    account_count = 0  # 클래스 변수

    def __init__(self, owner: str, balance: int = 0):
        self.owner = owner
        self._balance = balance
        BankAccount.account_count += 1

    @classmethod
    def created_count(cls) -> int:
        return cls.account_count

 

a1 = BankAccount("A")
a2 = BankAccount("B")
print(BankAccount.created_count())  # 2

 

  • @classmethod : 클래스 자체에 작동(첫 인자 cls)
  • 모든 인스턴스가 공유하는 정보(총 생성 수 등)에 적합

 

 

4. 정적 메서드와 유틸리티

  • 정적 메서드(@staticmethod) : 인스턴스/클래스 상태와 무관한 도우미 함수
class BankAccount:
    # ... (생략)
    @staticmethod
    def is_valid_amount(amount: int) -> bool:
        return isinstance(amount, int) and amount > 0

print(BankAccount.is_valid_amount(100))  # True

 

 

5. 상속으로 기능 확장

저축예금(SavingAccount) 같은 파생 클래스를 만들어 기능을 확장해 봅니다.

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

    def deposit(self, amount: int):
        if amount <= 0:
            raise ValueError("입금액은 0보다 커야 합니다.")
        self._balance += amount

    def withdraw(self, amount: int):
        if amount <= 0:
            raise ValueError("출금액은 0보다 커야 합니다.")
        if amount > self._balance:
            raise ValueError("잔액이 부족합니다.")
        self._balance -= amount

    @property
    def balance(self) -> int:
        return self._balance


class SavingAccount(BankAccount):
    def __init__(self, owner: str, balance: int = 0, rate: float = 0.02):
        super().__init__(owner, balance)  # 부모 초기화
        self.rate = rate

    def add_interest(self):
        # 단리 이자 적용
        interest = int(self.balance * self.rate)
        # 부모 메서드 재사용
        self.deposit(interest)


acc = SavingAccount("Carol", 10_000, rate=0.05)
acc.add_interest()
print(acc.balance)  # 10,500

 

  • SavingAccount는 BankAccount를 상속받아 이자 기능을 추가
  • super()로 부모 초기화/메서드 재사용

 

 

6. 테스트용 미니 시나리오

실제 동작을 한 번에 확인해봅시다.

def scenario():
    a = BankAccount("Alice", 5_000)
    b = SavingAccount("Bob", 20_000, rate=0.03)

    a.deposit(2_000)     # 7,000
    try:
        a.withdraw(10_000)  # 실패
    except ValueError as e:
        print("[Alice] 출금 실패:", e)

    b.add_interest()     # 20,000 * 0.03 = 600 → 20,600
    b.withdraw(600)      # 20,000

    print("[Alice] 잔액:", a.balance)
    print("[Bob]   잔액:", b.balance)

scenario()

 

 

7. 데이터 클래스(dataclass)로 보일러플레이트 줄이기 (선택)

간단한 값 객체는 @dataclass로 초기화/표현을 자동 생성할 수 있습니다.
(주의: 엄격한 캡슐화가 필요한 경우엔 일반 클래스가 더 적합할 수 있습니다.)

from dataclasses import dataclass

@dataclass
class Customer:
    name: str
    level: int = 1

c = Customer("Alice")
print(c)  # Customer(name='Alice', level=1)

 

▣ 정리

  • 클래스/객체로 상태와 행위를 함께 묶는다.
  • 캡슐화를 통해 유효성 검사를 일관되게 적용하고, 속성은 @property로 안전하게 노출.
  • 클래스 변수/메서드는 공통 메타 정보(생성 수 등)에 적합.
  • 상속으로 기능을 확장하고, 필요하면 super()로 부모 로직 재사용.
  • 단순 데이터 컨테이너는 @dataclass로 보일러플레이트를 줄일 수 있다.

 

 

감사합니다.

728x90