학습 순서 7번. 새로 작성한 챕터입니다. 원본 수업에서 클래스는
test/class_ex_01.py한 파일에만 있었고, 그마저 버그가 있었습니다. 여기서 제대로 배우고 그 파일을 직접 고칩니다.
- 클래스로 데이터와 그 데이터로 하는 일을 한 덩어리로 묶는다
__init__과self가 무엇인지 안다- 클래스 변수와 인스턴스 변수를 구분한다
- 상속으로 기능을 물려받고 오버라이딩으로 바꾼다
- 매직 메서드(
__str__·__len__·__eq__)로 내 클래스를 파이썬답게 만든다 if __name__ == '__main__':의 의미를 안다
| 파일 | 내용 |
|---|---|
classEx1.py |
★ 클래스 기본, __init__, self, 딕셔너리와의 차이 |
classEx2.py |
클래스 변수 vs 인스턴스 변수, 밑줄 관례, @property |
inheritEx.py |
상속, 오버라이딩, super(), isinstance |
magicMethod.py |
__str__·__repr__·__eq__·__add__·__len__·__getitem__ |
calculator.py |
★★ test/class_ex_01.py 의 고친 정본 |
test1.py |
연습문제 — Student 클래스 |
python3 step07_class/classEx1.py # 입력 불필요
python3 step07_class/calculator.py # ← 대화형. 종료는 qclass Student:
def __init__(self, name):
self.name = name # 이 객체가 계속 들고 있을 값
def total(self):
return self.kor + self.engself 는 그 객체 자신입니다. 정의할 때는 첫 매개변수로 반드시 쓰지만, 부를 때는 넘기지 않습니다.
s1.total() # 이렇게 부르면
Student.total(s1) # 파이썬이 이렇게 바꿔 실행한다self. 를 빠뜨리면 값이 사라집니다.
def __init__(self, value):
value = value # self. 가 없다 → 그냥 지역 변수. 함수 끝나면 증발step06 의 스코프 이야기와 같습니다. 함수 안에서 만든 변수는 함수가 끝나면 사라지고, self. 를 붙여야 객체에 남습니다.
step05 에서 학생을 이렇게 표현했습니다.
{'이름': '홍길동', '국어': 85, '영어': 73}딕셔너리는 데이터만 담습니다. 총점을 구하려면 매번 밖에서 계산해야 하고, 그 계산식이 코드 여기저기 흩어집니다.
클래스는 데이터와 "그 데이터로 하는 일" 을 함께 담습니다. 총점 구하는 법을 바꾸고 싶으면 total() 한 곳만 고치면 됩니다.
그렇다고 항상 클래스가 낫지는 않습니다. 그냥 값 몇 개를 묶어 옮길 뿐이라면 딕셔너리가 가볍고 충분합니다. 동작이 붙기 시작하면 클래스로 옮길 때입니다.
class Counter:
count = 0 # 클래스 변수 — 모든 객체가 함께 쓴다
def __init__(self, name):
self.name = name # 인스턴스 변수 — 객체마다 따로
Counter.count += 1a.name·b.name 은 각자 다르지만 a.count·b.count 는 전부 같은 값입니다. 하나를 바꾸면 전부 바뀝니다.
step05·step06 에서 본 "기본값 리스트가 공유되는 함정"과 같은 종류의 놀라움입니다.
self._balance = balance파이썬에는 진짜 private 이 없습니다. acc._balance = 999999 라고 쓰면 그냥 됩니다.
하지만 그러면 deposit/withdraw 안의 검사(잔액 부족, 음수 입금)를 통째로 건너뜁니다. 메서드를 통해서만 바꾸게 하는 이유가 여기 있습니다 — 규칙을 한 곳에 모아 두기 위해서입니다.
class Dog(Animal):
def speak(self): # 부모와 같은 이름 = 오버라이딩
print(f'{self.name} : 멍멍!')Dog 는 Animal 이므로 상속이 맞습니다. 하지만 Car 는 Engine 이 아닙니다 — 자동차가 엔진을 가지고 있는 거죠. 이럴 땐 상속이 아니라 속성으로 담습니다(합성).
super() 는 부모 것을 부를 때 씁니다.
def __init__(self, name, indoor):
super().__init__(name) # 부모 __init__ 먼저
self.indoor = indoor # 내 것 추가print(p) # <__main__.Point object at 0x10a...> 읽을 수 없다__str__ 을 정의하면 print 가 그걸 씁니다.
| 메서드 | 언제 불리나 |
|---|---|
__init__ |
객체를 만들 때 |
__str__ |
print(), str() |
__repr__ |
개발자용 표현. 리스트 안에 있을 때 |
__eq__ |
== |
__add__ |
+ |
__len__ |
len() |
__getitem__ |
[], for 순회 |
__contains__ |
in |
len([1,2,3]), 'a' + 'b', 3 in [1,2,3] — 지금까지 써 온 것들이 전부 매직 메서드였습니다. 내장 자료형이 하던 일을 내 클래스도 할 수 있게 만드는 장치입니다.
calculator.py 마지막 줄입니다.
if __name__ == '__main__':
main()- 이 파일을 직접 실행하면 →
__name__이'__main__'이라main()이 돈다 - 다른 파일에서
import하면 →__name__이'calculator'라main()이 안 돈다
이게 없으면 from calculator import Calculator 하는 순간 입력 프롬프트가 뜹니다. 클래스만 가져다 쓰려는데 프로그램이 통째로 실행되는 거죠. step09_module 에서 다시 다룹니다.
test/class_ex_01.py 를 실행하고 + / 10 / 0 을 입력하면 이런 일이 벌어졌습니다.
연산 종류 (+,-,*,/) : +
첫 번째 입력값 : 10
두 번째 입력값 : 0
숫자를 입력하세요. ← 숫자를 넣었는데?
원인 1 — 괄호가 붙어 있었다.
test = lambda x: {"+": cal1.add(), "-": cal1.sub(), "*": cal1.mul(), "/": cal1.div()}.get(x, ...)
# ^^^^^^^^^^ 실행 결과cal1.add() 는 "덧셈 함수"가 아니라 "덧셈을 실행한 결과값" 입니다. 그래서 dict 를 만드는 순간 네 연산이 전부 실행됩니다. .get(x) 는 이미 계산이 끝난 값 중 하나를 고를 뿐입니다.
두 번째 값이 0 이면, 덧셈을 요청했어도 div() 가 돌아 ZeroDivisionError 가 납니다.
원인 2 — 예외를 뭉뚱그려 잡았다.
except Exception as e:
print("숫자를 입력하세요.") # e 를 출력조차 안 한다ZeroDivisionError 를 잡아 놓고 "숫자를 입력하세요"라고 말합니다. 버그가 사용자에게 거짓말로 도착합니다.
고친 방법
ops = {'+': cal.add, '-': cal.sub, '*': cal.mul, '/': cal.div} # 괄호 없음!
func = ops.get(op)
...
try:
print('결과 :', func()) # 고른 다음에 부른다
except ZeroDivisionError:
print('→ 0으로 나눌 수 없습니다.')step06_function 의 첫 번째 교훈 — 괄호가 있으면 실행, 없으면 함수 자체 — 가 여기서 값을 합니다.
같은 시나리오를 새 버전으로 실행해 보세요.
python3 step07_class/calculator.py
# + / 10 / 0 → 결과 : 10classEx1.py의Bad클래스에서self.를 붙여b.value가 동작하게 고치세요.Account클래스에 거래 내역(리스트)을 추가하고, 입출금할 때마다 기록해history()로 출력하세요.Circle에@property로 둘레(perimeter)를 추가하세요.Animal을 상속한Bird를 만들고fly()를 추가하세요.speak()도 오버라이딩하세요.magicMethod.py의Point2에__sub__(뺄셈)과__mul__(스칼라 곱)을 추가하세요.test1.py의Student를 상속해ScholarStudent(장학생)를 만드세요. 평균이 90 이상일 때만 만들어지도록__init__에서 검사하세요.calculator.py에 나머지(%)와 거듭제곱(**)을 추가하세요.ops딕셔너리에 한 줄씩만 더하면 됩니다 — 이게 이 구조의 장점입니다.