⚙️
중급 (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) # 15002. 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 24. 캡슐화 관례
파이썬은 진짜 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. 자주 하는 실수
- self 빠뜨림 — def deposit(amount): → 자동으로 인스턴스가 들어가는데 매개변수 없으면 TypeError
- 클래스 변수에 가변 객체 — 모든 인스턴스가 공유돼서 의도치 않게 같이 변함
- __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
잔액: 70002_self.py— self 가 동작하는 모습
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 304_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