클래스 복습 문제: 상속과
super(), 파일을 쓰는 클래스, 클래스 변수로 상태 관리. 파이썬 열흘 돌아보기.
파이썬 열 번째 날이다. 새 개념보다는 클래스 문제 세 개를 푸는 복습이었다. 상속(super()), 파일 입출력, 클래스 변수와 상태 검사를 한 문제씩 다시 짚었다.
super(): 탈것Vehicle을 상속해서 Car와 Bicycle을 만들고, 각자 move()를 오버라이딩한다.
class Vehicle:
def __init__(self, name, speed): # 변수에 처음 값을 대입 → 초기화
self.name = name
self.speed = speed
def move(self):
return f"{self.name}은 시속{self.speed} 이다"
class Car(Vehicle):
def move(self):
return super().move() + "소나타 자동차"
class Bicycle(Vehicle):
def move(self):
return super().move() + "자전거"
car = Car("소나타", 120)
bike = Bicycle("자전거", 20)
print(car.move()) # 소나타은 시속120 이다소나타 자동차
print(bike.move()) # 자전거은 시속20 이다자전거
흐름은 이렇다.
Car("소나타", 120)을 만들면 Car에는 __init__이 없어서 부모 Vehicle의 __init__이 호출되고 name, speed가 초기화된다.car.move()는 Car의 move()가 실행된다. 그 안의 super().move()가 부모의 move()를 불러 "소나타은 시속120 이다"를 얻고, 뒤에 문자열을 덧붙인다.bike.move()도 같은 방식이다.__init__을 따로 정의하지 않은 자식 클래스는 부모의 __init__을 그대로 쓴다. Day.5에서 자식이 __init__을 다시 정의할 때만 super().__init__()이 필요하다고 정리한 내용의 반대 경우다.
위 결과를 보면 문장이 어색하다. 소나타은처럼 조사가 어긋나고, 이름(소나타)이 뒤에 또 붙었다. 이름은 이미 self.name에 들어 있으므로 덧붙일 것은 종류만 있으면 된다.
class Vehicle:
def __init__(self, name, speed):
self.name = name
self.speed = speed
def move(self):
return f"{self.name}: 시속 {self.speed}km"
class Car(Vehicle):
def move(self):
return super().move() + " (자동차)"
class Bicycle(Vehicle):
def move(self):
return super().move() + " (자전거)"
for v in [Car("소나타", 120), Bicycle("따릉이", 20), Vehicle("탈것", 0)]:
print(v.move())
# 소나타: 시속 120km (자동차)
# 따릉이: 시속 20km (자전거)
# 탈것: 시속 0km
for문에서 세 객체에 똑같이 v.move()를 호출했는데 각자의 move()가 실행됐다. Day.8의 다형성이다. 한글 문장에서 이름 + 은/는을 코드로 붙이면 받침에 따라 조사가 달라져서 어긋나기 쉽기 때문에, 위처럼 조사를 피하는 형식으로 바꿨다.
상속 관계는 코드로도 확인할 수 있다.
print(isinstance(Car("a", 1), Vehicle)) # True (Car 객체는 Vehicle이기도 하다)
print(issubclass(Bicycle, Vehicle)) # True
print(Car.__mro__)
# (<class 'Car'>, <class 'Vehicle'>, <class 'object'>)
__mro__는 메소드를 찾는 순서다. Car → Vehicle → object 순으로 찾는다. Day.7의 "모든 클래스는 object를 상속한다"를 눈으로 볼 수 있다.
날짜와 함께 일기를 파일에 쌓고, 한꺼번에 읽어 온다.
from datetime import date
class Diary:
def __init__(self, filename="diary.txt"): # 파일 이름의 기본값
self.filename = filename
def write(self, content):
today = date.today()
# a 모드: 기존 내용 뒤에 이어쓰기
with open(self.filename, "a", encoding="utf-8") as f:
f.write(f"{today} : {content}\n")
# 파일에 저장한 문장들을 읽어 온다
def read_all(self):
try:
with open(self.filename, "r", encoding="utf-8") as f:
print(f.read())
except FileNotFoundError:
print("파일이없어 일기 못읽어옴")
diary = Diary()
diary.write("파이썬 복습")
diary.write("파이썬 오늘 정리함")
diary.read_all()
# 2026-10-03 : 파이썬 복습
# 2026-10-03 : 파이썬 오늘 정리함
(날짜는 실행한 날로 나온다.)
date.today()는 오늘 날짜 객체고, f-string에 넣으면 2026-09-18 형태의 문자열이 된다.filename="diary.txt"처럼 매개변수 기본값을 두면 Diary()로 만들 때 파일 이름을 안 넘겨도 된다. 다른 파일을 쓰고 싶으면 Diary("work.txt")로 만들면 된다.'a' 모드는 이어쓰기라서 실행할 때마다 줄이 계속 쌓인다. 'w'였다면 매번 지워진다. (Day.4)read_all은 파일이 없으면 FileNotFoundError를 except로 처리한다. 아직 한 번도 쓴 적 없는 Diary("nofile.txt").read_all()을 하면 메시지만 출력되고 프로그램은 계속된다.Day.4의 파일 입출력, Day.6의 예외 처리, Day.5에서 본 time처럼 표준 모듈(datetime)을 가져다 쓰는 것을 한 클래스 안에 모은 문제다.
주문의 상태가 정해진 값 중 하나일 때만 바꿀 수 있게 한다.
class Order:
ORDER_STATUS = ["결제대기", "결제완료", "배송준비", "배송중", "배송완료"] # 클래스 변수
def __init__(self, order_id, customer):
self.order_id = order_id
self.customer = customer
self.status = "결제대기" # 처음 상태
def update_status(self, new_status):
# new_status가 리스트 안에 없으면 "잘못된 상태"
if new_status not in self.ORDER_STATUS:
print("잘못된 상태")
return
self.status = new_status
print(f"{self.order_id} 상태가 {new_status} 로 변경되었다")
def show(self):
print(f"주문번호 : {self.order_id}, 주문자 : {self.customer}, 상태 : {self.status}")
order1 = Order("A001", "Tom")
order2 = Order("A002", "Amy")
order1.update_status("결제완료") # A001 상태가 결제완료 로 변경되었다
order1.update_status("배송준비") # A001 상태가 배송준비 로 변경되었다
order2.update_status("배송중") # A002 상태가 배송중 로 변경되었다
order2.update_status("문앞배송") # 잘못된 상태
order1.show() # 주문번호 : A001, 주문자 : Tom, 상태 : 배송준비
order2.show() # 주문번호 : A002, 주문자 : Amy, 상태 : 배송중
ORDER_STATUS는 클래스 변수다. 주문 객체마다 따로 갖지 않고 모두가 공유한다. 이름을 대문자로 쓴 것은 "바꾸지 않는 값(상수)"이라는 관례다. 확인해 보면 Order.ORDER_STATUS is order1.ORDER_STATUS가 True로, 같은 리스트다. (Day.7의 클래스 변수)not in으로 유효한 상태인지 검사하고, 아니면 return으로 바로 끝낸다. 이 경우 status는 바뀌지 않는다. 문앞배송은 목록에 없어서 변경되지 않았다.input()으로 받았는데, 실행할 때마다 입력해야 해서 값을 직접 넣었다.위 코드는 결제대기에서 바로 배송중으로 바꿔도 통과한다. (order2가 그렇게 바뀌었다) 상태에 순서가 있으니 리스트의 위치(index)를 비교하면 거꾸로 돌아가는 것은 막을 수 있다.
class Order2(Order):
def update_status(self, new_status):
if new_status not in self.ORDER_STATUS:
print("잘못된 상태")
return
if self.ORDER_STATUS.index(new_status) < self.ORDER_STATUS.index(self.status):
print("이전 단계로는 돌아갈 수 없다")
return
super().update_status(new_status) # 나머지는 부모의 기능을 그대로
o = Order2("B1", "Kim")
o.update_status("배송중") # B1 상태가 배송중 로 변경되었다
o.update_status("결제완료") # 이전 단계로는 돌아갈 수 없다
o.update_status("문앞배송") # 잘못된 상태
Day.5의 상속 + 오버라이딩 + super()로 부모는 건드리지 않고 검사만 덧붙였다. 반대로 건너뛰는 것(결제대기 → 배송중)까지 막을지는 업무 규칙에 달려 있어서, 여기서는 거꾸로 가는 것만 막았다.
열흘간 배운 것을 한 장으로 정리하면 이렇다.
| Day | 주제 |
|---|---|
| 1 | 자료형 9가지, 슬라이싱, 문자열 포맷(%, format, f-string), 리스트 메소드 |
| 2 | 튜플, 딕셔너리, 집합, 참/거짓 판별, id/copy/is, if/elif, in, while |
| 3 | for/range, 리스트 컴프리헨션, 함수, *args/**kwargs, 기본값 매개변수, input |
| 4 | 변수 스코프, lambda, 내장 함수, 파일 입출력, 클래스 기초 |
| 5 | 상속, super(), 오버라이딩, 내장 함수와 time/random/pickle, 모듈 |
| 6 | 패키지, 예외 처리와 사용자 정의 예외, 클로저, 데코레이터 |
| 7 | __str__, 이터레이터, 제너레이터, JSON, sys |
| 8 | 예외·상속 실습, 다형성, asyncio, threading, sys.argv |
| 9 | 정규 표현식(re), os 모듈 |
| 10 | 클래스 복습: 상속, 파일, 클래스 변수 |
JavaScript(Day.1~10)에서 Promise와 async/await로 끝낸 비동기를 파이썬에서도 asyncio로 다시 만났다. 언어는 달라도 async/await로 기다리고 동시에 실행한다는 구조는 같았다.
__init__을 정의하지 않으면 부모의 __init__이 그대로 쓰인다. 메소드를 오버라이딩하면서 부모 기능도 쓰려면 super().메소드()를 호출한다.'a' 모드와 with, 파일이 없을 때는 FileNotFoundError 처리를 함께 쓴다.in/not in으로 검사하면 잘못된 값을 걸러 낼 수 있다.은/는)가 어긋나지 않는지 출력해서 확인한다.다음부터는 React와 Git이다.
Tags: Python 클래스 상속 super 다형성 파일입출력 클래스변수 복습 개발자 학습기록