← 파이썬 강의 목록으로
⚙️
중급 (Intermediate)
class · __init__ · self · 캡슐화

7주차 — 객체지향 (1) — 클래스와 인스턴스

데이터(속성)와 동작(메서드)을 묶는 클래스 정의, __init__ 생성자와 self의 의미, 인스턴스 변수 vs 클래스 변수, 그리고 _var/__var 캡슐화 관례.

classOOPselfencapsulation
소요 시간
2시간
난이도
📊 중급
선수 조건
🎯 6주차
결과물
BankAccount, Stack, Student 같은 클래스 직접 설계

이 강의에서 배우는 것

  • 1클래스를 정의하고 인스턴스를 만든다
  • 2__init__ 생성자와 self 의 의미를 이해한다
  • 3인스턴스 메서드와 속성을 다룬다
  • 4캡슐화 관례 (_, __)

1. 클래스란

데이터(속성)와 동작(메서드)을 묶은 설계도. 이를 바탕으로 만든 게 인스턴스(객체).

python
class Account:
    def __init__(self, owner, balance=0):
        self.owner = owner       # 인스턴스 속성
        self.balance = balance

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

    def withdraw(self, amount):
        if amount > self.balance:
            raise ValueError("잔액 부족")
        self.balance -= amount

a = Account("홍길동", 1000)    # 인스턴스 생성
a.deposit(500)
print(a.balance)   # 1500

2. self 의 의미

메서드의 첫 인자는 항상 self — 호출된 인스턴스 자신.

python
a = Account("홍길동")
a.deposit(100)        # 실제로는 Account.deposit(a, 100)

self 를 통해 인스턴스 속성에 접근.

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

python
class Counter:
    total = 0   # 클래스 변수 — 모든 인스턴스가 공유

    def __init__(self):
        Counter.total += 1
        self.id = Counter.total   # 인스턴스 변수 — 각자 다름

c1 = Counter()
c2 = Counter()
print(c1.id, c2.id, Counter.total)   # 1 2 2

4. 캡슐화 관례

파이썬은 진짜 private이 없습니다. 관례로 표현:

  • _var — "내부용" (외부에서 쓰지 말라는 신호)
  • __var — name mangling (_ClassName__var 로 변환됨)
python
class Account:
    def __init__(self, owner, balance):
        self._owner = owner       # 내부용
        self.__balance = balance  # name mangled

    def get_balance(self):
        return self.__balance

a = Account("홍길동", 1000)
print(a._owner)              # 가능 (관례로 자제)
# print(a.__balance)         # AttributeError
print(a._Account__balance)   # 동작은 함 (권장 X)

5. 객체 비교

기본 == 은 같은 객체인지 (id 비교). 의미적 비교는 __eq__ 오버라이딩 필요 (다음 주차).

6. 자주 하는 실수

  1. self 빠뜨림 — def deposit(amount): → 자동으로 인스턴스가 들어가는데 매개변수 없으면 TypeError
  2. 클래스 변수에 가변 객체 — 모든 인스턴스가 공유돼서 의도치 않게 같이 변함
  3. __init__ 에서 다른 이름의 속성 사용 — 오타로 인한 attribute 누락
python
class Bag:
    items = []   # 위험!  → __init__ 안에서 self.items = []

7. FAQ

Q1. 모든 코드를 클래스로 짜야 하나요?

아닙니다. 상태 + 동작이 묶여야 의미 있을 때만. 단순 함수가 더 명확하면 함수.

Q2. __init__ 외에 다른 매직 메서드는?

다음 주차에서 __str__, __eq__, __len__ 등 학습.

Q3. 클래스 이름 명명 규칙은?

PascalCase (BankAccount, UserProfile). 함수·변수는 snake_case.

💻 예제 (examples)

실제로 실행해 결과를 확인할 수 있는 예제 코드입니다.

01_class_basic.py클래스 정의·인스턴스 생성
CODE
class Account:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self.balance = balance

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

    def withdraw(self, amount):
        if amount > self.balance:
            raise ValueError("잔액 부족")
        self.balance -= amount

a = Account("홍길동", 1000)
a.deposit(500)
print(f"{a.owner}: {a.balance}")
a.withdraw(800)
print(f"잔액: {a.balance}")
▶ 실행 결과
홍길동: 1500
잔액: 700
02_self.pyself 가 동작하는 모습
CODE
class Greeter:
    def __init__(self, name):
        self.name = name

    def hello(self):
        return f"안녕, 나는 {self.name}!"

g = Greeter("홍길동")
print(g.hello())              # 일반 호출
print(Greeter.hello(g))       # 동일 — self 직접 전달
▶ 실행 결과
안녕, 나는 홍길동!
안녕, 나는 홍길동!
03_class_var.py클래스 변수 vs 인스턴스 변수
CODE
class Counter:
    total = 0   # 모든 인스턴스가 공유

    def __init__(self):
        Counter.total += 1
        self.id = Counter.total

c1 = Counter()
c2 = Counter()
c3 = Counter()
print(c1.id, c2.id, c3.id, Counter.total)
▶ 실행 결과
1 2 3 3
04_encapsulation.py_var / __var 관례
CODE
class Account:
    def __init__(self, owner, balance):
        self._owner = owner       # 관례상 내부용
        self.__balance = balance  # name mangled

    def get_balance(self):
        return self.__balance

a = Account("홍길동", 1000)
print(a._owner)
print(a.get_balance())
# print(a.__balance)  # AttributeError
print(a._Account__balance)  # 동작하지만 비권장
▶ 실행 결과
홍길동
1000
1000

📝 과제 (exercises)

직접 풀어보고, 막힐 때 정답을 펼쳐 비교해보세요.

과제 1

BankAccount 클래스

목표: 잔액 조회/입금/출금 메서드 + 잔액 부족 시 예외.

요구사항
  • owner, balance 속성
  • deposit, withdraw 메서드
  • 잔액 부족 시 ValueError
입출력 예시
잔액: 1500
잔액: 700
거부: 잔액 부족
정답 코드 펼치기 / 접기
SOLUTION
class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self.balance = balance

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

    def withdraw(self, amount):
        if amount > self.balance:
            raise ValueError("잔액 부족")
        self.balance -= amount

a = BankAccount("홍길동", 1000)
a.deposit(500); print(f"잔액: {a.balance}")
a.withdraw(800); print(f"잔액: {a.balance}")
try:
    a.withdraw(10000)
except ValueError as e:
    print(f"거부: {e}")
▶ 실행 결과
잔액: 1500
잔액: 700
거부: 잔액 부족
과제 2

Stack 클래스

목표: 후입선출(LIFO) 자료구조를 클래스로 구현.

요구사항
  • push, pop, peek, size, is_empty 메서드
  • 내부 저장은 리스트
입출력 예시
size=3
peek=3
pop=3
size=2
is_empty=False
정답 코드 펼치기 / 접기
SOLUTION
class Stack:
    def __init__(self):
        self._items = []

    def push(self, x):
        self._items.append(x)

    def pop(self):
        return self._items.pop()

    def peek(self):
        return self._items[-1] if self._items else None

    def size(self):
        return len(self._items)

    def is_empty(self):
        return len(self._items) == 0

s = Stack()
for x in [1, 2, 3]: s.push(x)
print(f"size={s.size()}")
print(f"peek={s.peek()}")
print(f"pop={s.pop()}")
print(f"size={s.size()}")
print(f"is_empty={s.is_empty()}")
▶ 실행 결과
size=3
peek=3
pop=3
size=2
is_empty=False
과제 3

Student 클래스 (점수 평균/최고)

목표: 이름 + 점수 리스트로 평균과 최고를 계산.

요구사항
  • name, scores 속성
  • add_score, average, highest 메서드
입출력 예시
홍길동의 평균: 87.0
최고점: 95
정답 코드 펼치기 / 접기
SOLUTION
class Student:
    def __init__(self, name):
        self.name = name
        self.scores = []

    def add_score(self, s):
        self.scores.append(s)

    def average(self):
        return sum(self.scores) / len(self.scores) if self.scores else 0

    def highest(self):
        return max(self.scores) if self.scores else 0

s = Student("홍길동")
for v in [85, 92, 78, 95, 85]: s.add_score(v)
print(f"{s.name}의 평균: {s.average()}")
print(f"최고점: {s.highest()}")
▶ 실행 결과
홍길동의 평균: 87.0
최고점: 95
예제 코드 / 강의 자료

전체 강의 자료와 예제 코드는 GitHub에서 자유롭게 받아볼 수 있습니다.

GitHub에서 보기 ↗