Skip to content

Latest commit

 

History

History

Folders and files

README.md

step07_class — 클래스와 객체

학습 순서 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      # ← 대화형. 종료는 q

짚고 갈 지점

self 가 뭔가

class Student:
    def __init__(self, name):
        self.name = name        # 이 객체가 계속 들고 있을 값

    def total(self):
        return self.kor + self.eng

self 는 그 객체 자신입니다. 정의할 때는 첫 매개변수로 반드시 쓰지만, 부를 때는 넘기지 않습니다.

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 += 1

a.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] — 지금까지 써 온 것들이 전부 매직 메서드였습니다. 내장 자료형이 하던 일을 내 클래스도 할 수 있게 만드는 장치입니다.

if __name__ == '__main__':

calculator.py 마지막 줄입니다.

if __name__ == '__main__':
    main()
  • 이 파일을 직접 실행하면 → __name__ 이 '__main__' 이라 main() 이 돈다
  • 다른 파일에서 import 하면 → __name__ 이 'calculator' 라 main() 이 안 돈다

이게 없으면 from calculator import Calculator 하는 순간 입력 프롬프트가 뜹니다. 클래스만 가져다 쓰려는데 프로그램이 통째로 실행되는 거죠. step09_module 에서 다시 다룹니다.

calculator.py — 원본은 무엇이 문제였나

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 → 결과 : 10

연습 과제

  1. classEx1.py 의 Bad 클래스에서 self. 를 붙여 b.value 가 동작하게 고치세요.
  2. Account 클래스에 거래 내역(리스트)을 추가하고, 입출금할 때마다 기록해 history() 로 출력하세요.
  3. Circle 에 @property 로 둘레(perimeter)를 추가하세요.
  4. Animal 을 상속한 Bird 를 만들고 fly() 를 추가하세요. speak() 도 오버라이딩하세요.
  5. magicMethod.py 의 Point2 에 __sub__(뺄셈)과 __mul__(스칼라 곱)을 추가하세요.
  6. test1.py 의 Student 를 상속해 ScholarStudent(장학생)를 만드세요. 평균이 90 이상일 때만 만들어지도록 __init__ 에서 검사하세요.
  7. calculator.py 에 나머지(%)와 거듭제곱(**)을 추가하세요. ops 딕셔너리에 한 줄씩만 더하면 됩니다 — 이게 이 구조의 장점입니다.