← 파이썬 강의 목록으로
⚙️
중급 (Intermediate)
class · __init__ · self · 캡슐화 · extends · super · __str__ · __eq__ · classmethod · re · 메타문자 · 그룹 · sub · compile · OOP + JSON + 모듈 분리 + 패키지 구조

7~10주차 — 객체지향 — 클래스와 인스턴스 · 객체지향 — 상속·다형성·매직 메서드 · 정규표현식 · 중급 종합 실습

데이터(속성)와 동작(메서드)을 묶는 클래스 정의, __init__ 생성자와 self의 의미, 인스턴스 변수 vs 클래스 변수, 그리고 _var/__var 캡슐화 관례. 부모 클래스의 속성·메서드를 물려받는 상속, super() 호출, 다형성, __str__/__eq__/__add__ 같은 매직 메서드 오버라이딩, classmethod와 staticmethod까지. re 모듈로 패턴 매칭, 자주 쓰는 메타문자(\d \w \s + * ?), 그룹 캡처(이름 그룹 포함), 치환(sub), raw string, compile 활용까지. OOP·예외·파일 I/O를 모두 사용한 미니 프로젝트 (단어장/가계부/로그 분석/연락처) 중 하나를 모듈 분리된 패키지 구조로 완성합니다.

classOOPselfencapsulationinheritancesupermagicclassmethodregexrepatternsub프로젝트패키지JSON
소요 시간
⏱ 2시간 + 2시간 + 2시간 + 3~4시간
난이도
📊 중급
선수 조건
🎯 6주차
결과물
BankAccount, Stack, Student 같은 클래스 직접 설계 / Shape→Rectangle/Circle, Vector + 매직 메서드 / 이메일·전화번호·로그에서 패턴 추출 / models/storage/cli 가 분리된 작은 패키지 앱

이 강의에서 배우는 것

  • 1클래스를 정의하고 인스턴스를 만든다
  • 2__init__ 생성자와 self 의 의미를 이해한다
  • 3인스턴스 메서드와 속성을 다룬다
  • 4캡슐화 관례 (_, __)
  • 5클래스 상속과 super() 를 사용한다
  • 6다형성을 코드로 보인다
  • 7매직 메서드를 오버라이딩한다
  • 8클래스 메서드와 정적 메서드를 구분한다
  • 9re 모듈로 패턴 매칭한다
  • 10자주 쓰는 메타문자(\d, \w, +, *, ?)를 안다
  • 11그룹 캡처를 활용한다
  • 12치환(re.sub)을 사용한다
  • 13OOP·예외·파일 I/O를 모두 사용해 미니 프로젝트를 완성한다
  • 14모듈 분리와 패키지 구조를 적용한다

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

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

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.

8주차 — 객체지향 (2) — 상속·다형성·매직 메서드

부모 클래스의 속성·메서드를 물려받는 상속, super() 호출, 다형성, __str__/__eq__/__add__ 같은 매직 메서드 오버라이딩, classmethod와 staticmethod까지.

1. 상속

python
class Animal:
    def __init__(self, name):
        self.name = name
    def speak(self):
        return "..."

class Dog(Animal):     # Animal 상속
    def speak(self):   # 메서드 오버라이딩
        return "멍멍"

d = Dog("바둑이")
print(d.name, d.speak())   # 바둑이 멍멍

2. super()

python
class Puppy(Dog):
    def __init__(self, name, age):
        super().__init__(name)   # 부모 __init__ 호출
        self.age = age

3. 다형성

같은 메서드 이름을 다양한 클래스에서 다르게 구현. 호출자는 타입을 신경 쓰지 않음.

python
animals = [Dog("바둑이"), Cat("나비"), Cow("얼룩이")]
for a in animals:
    print(a.speak())   # 멍멍 / 야옹 / 음매

4. 매직 메서드

이름이 __xxx__ 로 시작·끝나는 특별 메서드. 파이썬 문법과 연결됨.

메서드트리거
__str__str(x), print(x)
__repr__REPL에서 표시, repr(x)
__len__len(x)
__eq__x == y
__add__x + y
__getitem__x[i]
__iter__/__next__for ... in x
python
class Point:
    def __init__(self, x, y):
        self.x, self.y = x, y
    def __repr__(self):
        return f"Point({self.x}, {self.y})"
    def __add__(self, other):
        return Point(self.x + other.x, self.y + other.y)
    def __eq__(self, other):
        return (self.x, self.y) == (other.x, other.y)

p = Point(1, 2) + Point(3, 4)
print(p)              # Point(4, 6)
print(p == Point(4, 6))   # True

5. classmethod / staticmethod

python
class Date:
    def __init__(self, y, m, d):
        self.y, self.m, self.d = y, m, d

    @classmethod
    def from_string(cls, s):
        y, m, d = map(int, s.split("-"))
        return cls(y, m, d)        # 인스턴스 생성

    @staticmethod
    def is_valid(y, m, d):
        return 1 <= m <= 12 and 1 <= d <= 31

print(Date.from_string("2026-05-09").y)
print(Date.is_valid(2026, 5, 9))

6. 자주 하는 실수

  1. super().__init__() 누락 — 부모 속성이 초기화 안 됨
  2. 매직 메서드를 일반 호출 — p.add(q) 가 아니라 p + q
  3. __eq__ 만 만들고 __hash__ 빼먹음 — set/dict 키로 못 씀
  4. 다중 상속 남용 — 가능하지만 복잡. 가능하면 컴포지션

7. FAQ

Q1. 추상 클래스(인터페이스) 만들려면?

abc.ABC 와 @abstractmethod 사용.

Q2. __str__ vs __repr__ ?

__str__ 은 사람용, __repr__ 은 개발자용 (디버깅). 보통 __repr__ 만 정의해도 됨.

Q3. 상속 vs 컴포지션?

"is-a" 관계면 상속, "has-a" 면 속성 보유 (컴포지션). 의심되면 컴포지션.

9주차 — 정규표현식

re 모듈로 패턴 매칭, 자주 쓰는 메타문자(\d \w \s + * ?), 그룹 캡처(이름 그룹 포함), 치환(sub), raw string, compile 활용까지.

1. 기본 사용법

python
import re

# 검색 (첫 매치)
m = re.search(r"\d{3}-\d{4}-\d{4}", "전화: 010-1234-5678")
print(m.group())   # 010-1234-5678

# 모두 찾기
print(re.findall(r"\d+", "사과 5개, 바나나 3개"))   # ['5', '3']

# 매치 여부
if re.match(r"^\d+$", "12345"):
    print("숫자만")

# 치환
print(re.sub(r"\d+", "?", "사과 5개, 바나나 3개"))   # 사과 ?개, 바나나 ?개

2. 자주 쓰는 메타문자

패턴의미
.임의 한 글자
\d숫자 ([0-9])
\D숫자 아님
\w영숫자, 언더스코어
\s공백
^시작
$끝
[abc]a, b, c 중 하나
[^abc]a, b, c 가 아닌 것
[a-z]범위

3. 수량자

패턴의미
*0회 이상
+1회 이상
?0 또는 1회
{n}정확히 n회
{n,m}n~m회
? (수량자 뒤)최소 매칭 (탐욕 X)

4. 그룹 캡처

(...) 로 그룹화. m.group(N) 또는 m.groups() 로 추출.

python
log = "ERROR 2026-05-09 14:32 인증 실패"
m = re.match(r"(\w+) (\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}) (.+)", log)
print(m.groups())   # ('ERROR', '2026-05-09', '14:32', '인증 실패')

# 명명 그룹
m = re.match(r"(?P<level>\w+) (?P<date>\d{4}-\d{2}-\d{2})", log)
print(m.group("level"), m.group("date"))

5. raw string

이스케이프 충돌 방지. 정규식은 항상 r"..." 권장.

python
re.findall(r"\d+", text)    # 권장
re.findall("\\d+", text)    # 동일하지만 헷갈림

6. 컴파일

같은 패턴을 여러 번 쓰면 미리 컴파일.

python
email_re = re.compile(r"[\w.]+@[\w.]+\.\w+")
emails = email_re.findall("연락: a@b.com, c@d.co.kr")
print(emails)

7. 자주 하는 실수

  1. 이스케이프 누락 — r 안 붙여서 \d → d. 항상 r"..."
  2. 탐욕 매칭 — <.+> 가 <a><b> 전체를 매칭. 최소 매칭은 <.+?>
  3. match vs search — match 는 처음만, search 는 어디서든
  4. 너무 복잡한 정규식 — 가독성 떨어지면 그냥 코드로 분기

8. FAQ

Q1. 정규식 디버깅 어떻게?

https://regex101.com 같은 사이트에서 패턴 테스트.

Q2. 정규식이 느린가요?

일반적으로 빠름. 다만 catastrophic backtracking 패턴은 매우 느림.

Q3. 이메일 완벽 검증 패턴?

RFC 정확 검증은 매우 길다. 실용적 수준이면 [\w.+-]+@[\w-]+\.[\w.-]+.

10주차 — 중급 종합 실습

OOP·예외·파일 I/O를 모두 사용한 미니 프로젝트 (단어장/가계부/로그 분석/연락처) 중 하나를 모듈 분리된 패키지 구조로 완성합니다.

1. 프로젝트 설계 시 고려사항

  1. 데이터 모델 — 어떤 클래스들로 표현?
  2. 영구 저장 — JSON? CSV? 어떤 형태?
  3. 사용자 인터페이스 — 명령어 형태? 메뉴 형태?
  4. 모듈 분리 — models.py, storage.py, cli.py 등

2. 권장 패키지 구조

text
my_app/
├── __init__.py
├── models.py       # 클래스 정의 (Word, Book 등)
├── storage.py      # JSON 저장/로드
├── cli.py          # 메인 루프, 명령어 처리
└── main.py         # 진입점

3. 자주 하는 실수

  1. 모든 코드를 main.py 한 파일에 — 분리 안 하면 200줄 넘어가면 미궁
  2. JSON 직렬화 안 되는 객체 — dataclass + asdict() 활용
  3. 저장 시점이 잘못됨 — 매 변경 시 vs 종료 시 — 정책 정하기
  4. 예외를 너무 좁게 또는 너무 넓게 — 회복 가능한 곳에서만 except

4. FAQ

Q1. dataclass 를 써도 되나요?

권장. 단순 데이터 클래스는 dataclass 가 편함. (고급 1주차에서 자세히)

Q2. 단일 파일 vs 패키지?

단일 파일은 50줄 이하만. 그 이상이면 분리.

5. 다음 단계

중급 과정을 마쳤습니다! 🎉

  • 고급 과정 — 타입 힌트, 데코레이터, 동시성, 테스트
  • 실습 도전 — Lv3 중급종합 (가계부, 도서 관리, 일기장, 텍스트 어드벤처)

💻 예제 (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.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 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
01_inherit.py— 상속 + super
CODE
class Animal:
    def __init__(self, name):
        self.name = name
    def speak(self):
        return "..."

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)
        self.breed = breed
    def speak(self):
        return "멍멍"

d = Dog("바둑이", "진돗개")
print(d.name, d.breed, d.speak())
▶ 실행 결과
바둑이 진돗개 멍멍
02_polymorphism.py— 다형성
CODE
class Animal:
    def speak(self): return "..."

class Dog(Animal):
    def speak(self): return "멍멍"

class Cat(Animal):
    def speak(self): return "야옹"

class Cow(Animal):
    def speak(self): return "음매"

for a in [Dog(), Cat(), Cow()]:
    print(a.speak())
▶ 실행 결과
멍멍
야옹
음매
03_magic.py— 매직 메서드 (Point)
CODE
class Point:
    def __init__(self, x, y):
        self.x, self.y = x, y
    def __repr__(self):
        return f"Point({self.x}, {self.y})"
    def __add__(self, other):
        return Point(self.x + other.x, self.y + other.y)
    def __eq__(self, other):
        return (self.x, self.y) == (other.x, other.y)

p = Point(1, 2) + Point(3, 4)
print(p)
print(p == Point(4, 6))
print([Point(0,0), p])  # __repr__ 활용
▶ 실행 결과
Point(4, 6)
True
[Point(0, 0), Point(4, 6)]
04_classmethod.py— classmethod / staticmethod
CODE
class Date:
    def __init__(self, y, m, d):
        self.y, self.m, self.d = y, m, d

    @classmethod
    def from_string(cls, s):
        y, m, d = map(int, s.split("-"))
        return cls(y, m, d)

    @staticmethod
    def is_valid(y, m, d):
        return 1 <= m <= 12 and 1 <= d <= 31

dt = Date.from_string("2026-05-09")
print(dt.y, dt.m, dt.d)
print(Date.is_valid(2026, 13, 1))
▶ 실행 결과
2026 5 9
False
01_basic.py— search / findall / match / sub
CODE
import re

m = re.search(r"\d{3}-\d{4}-\d{4}", "전화: 010-1234-5678")
print(m.group())

print(re.findall(r"\d+", "사과 5개, 바나나 3개"))

if re.match(r"^\d+$", "12345"):
    print("숫자만")

print(re.sub(r"\d+", "?", "사과 5개, 바나나 3개"))
▶ 실행 결과
010-1234-5678
['5', '3']
숫자만
사과 ?개, 바나나 ?개
02_meta.py— 메타문자 + 수량자
CODE
import re

text = "Hello 123 abc456 XYZ"

print(re.findall(r"\d+", text))         # 숫자
print(re.findall(r"[a-z]+", text))      # 소문자
print(re.findall(r"[A-Z]+", text))      # 대문자
print(re.findall(r"\w+", text))         # 단어
print(re.findall(r"\b\w{3}\b", text))   # 정확히 3글자
▶ 실행 결과
['123', '456']
['ello', 'abc']
['H', 'XYZ']
['Hello', '123', 'abc456', 'XYZ']
['XYZ']
03_groups.py— 그룹 + 명명 그룹
CODE
import re

log = "ERROR 2026-05-09 14:32 인증 실패"

m = re.match(r"(\w+) (\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}) (.+)", log)
print(m.groups())

m2 = re.match(r"(?P<level>\w+) (?P<date>\d{4}-\d{2}-\d{2})", log)
print(m2.group("level"), m2.group("date"))
▶ 실행 결과
('ERROR', '2026-05-09', '14:32', '인증 실패')
ERROR 2026-05-09
04_sub_compile.py— sub + compile
CODE
import re

# 전화번호 마스킹
phones = "연락: 010-1234-5678, 02-345-6789"
masked = re.sub(r"(\d{2,3})-\d{3,4}-\d{4}", r"\1-****-****", phones)
print(masked)

# compile + 재사용
email_re = re.compile(r"[\w.]+@[\w.]+\.\w+")
print(email_re.findall("연락: a@b.com, c@d.co.kr, 잘못된 이메일"))
▶ 실행 결과
연락: 010-****-****, 02-****-****
['a@b.com', 'c@d.co.kr']
wordbook_skeleton/models.py— Word, WordBook 클래스
CODE
from dataclasses import dataclass, field, asdict

@dataclass
class Word:
    en: str
    ko: str
    correct: int = 0
    wrong: int = 0

@dataclass
class WordBook:
    words: list = field(default_factory=list)

    def add(self, en, ko):
        self.words.append(Word(en, ko))

    def to_json(self):
        return [asdict(w) for w in self.words]
▶ 실행 결과
(라이브러리 — 직접 실행 안 함)
wordbook_skeleton/main.py— 진입점
CODE
from .models import WordBook
from .storage import load, save

def main():
    book = load()
    while True:
        cmd = input("> ").strip()
        if cmd == "quit":
            save(book)
            break
        elif cmd.startswith("add "):
            en, ko = cmd[4:].split(":")
            book.add(en.strip(), ko.strip())
        elif cmd == "list":
            for w in book.words:
                print(f"{w.en} = {w.ko}")

if __name__ == "__main__":
    main()
▶ 실행 결과
> add apple:사과
> list
apple = 사과
> quit

📝 과제 (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
과제 4

Shape 추상 → Rectangle, Circle

목표: Shape 클래스를 상속해 Rectangle, Circle 구현.

요구사항
  • Shape — area() 메서드 (기본 구현은 0 또는 abstract)
  • Rectangle, Circle — area 오버라이딩
  • 도형 리스트를 순회해 면적 출력
입출력 예시
Rectangle: 200
Circle: 78.54
▶정답 코드 펼치기 / 접기
SOLUTION
import math

class Shape:
    def area(self):
        return 0

class Rectangle(Shape):
    def __init__(self, w, h):
        self.w, self.h = w, h
    def area(self):
        return self.w * self.h

class Circle(Shape):
    def __init__(self, r):
        self.r = r
    def area(self):
        return math.pi * self.r ** 2

shapes = [Rectangle(10, 20), Circle(5)]
for s in shapes:
    print(f"{type(s).__name__}: {s.area():.2f}")
▶ 실행 결과
Rectangle: 200.00
Circle: 78.54
과제 5

Playlist 클래스 + 매직 메서드

목표: 노래 리스트로 __len__, __getitem__, __add__ 구현.

요구사항
  • songs 속성에 곡 리스트
  • len(p), p[0], p1 + p2 동작
입출력 예시
len: 3
첫 곡: 봄날
합치기 후 len: 5
▶정답 코드 펼치기 / 접기
SOLUTION
class Playlist:
    def __init__(self, songs=None):
        self.songs = list(songs) if songs else []

    def __len__(self):
        return len(self.songs)

    def __getitem__(self, i):
        return self.songs[i]

    def __add__(self, other):
        return Playlist(self.songs + other.songs)

p1 = Playlist(["봄날", "Spring Day", "FAKE LOVE"])
p2 = Playlist(["DNA", "Butter"])
print(f"len: {len(p1)}")
print(f"첫 곡: {p1[0]}")
print(f"합치기 후 len: {len(p1 + p2)}")
▶ 실행 결과
len: 3
첫 곡: 봄날
합치기 후 len: 5
과제 6

Vector 클래스 (+, -, ==, repr)

목표: 2D 벡터 합·차·비교·표현.

요구사항
  • x, y 속성
  • __add__, __sub__, __eq__, __repr__
입출력 예시
Vector(4, 6)
Vector(-2, -2)
True
▶정답 코드 펼치기 / 접기
SOLUTION
class Vector:
    def __init__(self, x, y):
        self.x, self.y = x, y
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)
    def __sub__(self, other):
        return Vector(self.x - other.x, self.y - other.y)
    def __eq__(self, other):
        return (self.x, self.y) == (other.x, other.y)
    def __repr__(self):
        return f"Vector({self.x}, {self.y})"

a = Vector(1, 2)
b = Vector(3, 4)
print(a + b)
print(a - b)
print(a + b == Vector(4, 6))
▶ 실행 결과
Vector(4, 6)
Vector(-2, -2)
True
과제 7

이메일 추출

목표: 긴 텍스트에서 이메일 주소만 모두 추출.

요구사항
  • re.findall + 적당한 패턴
입출력 예시
['hong@example.com', 'kim.young@a.co.kr', 'support+xyz@my-site.io']
▶정답 코드 펼치기 / 접기
SOLUTION
import re

text = """문의는 hong@example.com 또는 kim.young@a.co.kr 로 보내주세요.
긴급은 support+xyz@my-site.io"""

emails = re.findall(r"[\w.+-]+@[\w-]+\.[\w.-]+", text)
print(emails)
▶ 실행 결과
['hong@example.com', 'kim.young@a.co.kr', 'support+xyz@my-site.io']
과제 8

휴대폰 번호 검증

목표: 010-XXXX-XXXX 형식이 맞는지 True/False.

요구사항
  • re.fullmatch 사용 권장
입출력 예시
010-1234-5678: True
02-1234-5678: False
01012345678: False
▶정답 코드 펼치기 / 접기
SOLUTION
import re

def is_phone(s):
    return bool(re.fullmatch(r"010-\d{4}-\d{4}", s))

for v in ["010-1234-5678", "02-1234-5678", "01012345678"]:
    print(f"{v}: {is_phone(v)}")
▶ 실행 결과
010-1234-5678: True
02-1234-5678: False
01012345678: False
과제 9

로그에서 ERROR만 추출

목표: 여러 줄 로그 중 ERROR 라인의 시각·메시지만.

요구사항
  • 각 줄 형식: LEVEL YYYY-MM-DD HH:MM 메시지
  • LEVEL == ERROR 인 것만
입출력 예시
[14:32] 인증 실패
[14:35] DB 연결 끊김
▶정답 코드 펼치기 / 접기
SOLUTION
import re

log = """INFO 2026-05-09 14:30 서버 시작
ERROR 2026-05-09 14:32 인증 실패
WARN 2026-05-09 14:34 느린 응답
ERROR 2026-05-09 14:35 DB 연결 끊김"""

for line in log.splitlines():
    m = re.match(r"ERROR \d{4}-\d{2}-\d{2} (\d{2}:\d{2}) (.+)", line)
    if m:
        print(f"[{m.group(1)}] {m.group(2)}")
▶ 실행 결과
[14:32] 인증 실패
[14:35] DB 연결 끊김
과제 10

단어장 앱 (CRUD + JSON + 퀴즈 모드)

목표: 영-한 단어장을 JSON으로 영구 저장 + 무작위 퀴즈.

요구사항
  • 패키지 구조: models.py / storage.py / cli.py / main.py
  • 명령: add, list, del, quiz, quit
  • quiz 모드는 5문제, 정답 즉시 알림 + 통계 업데이트
입출력 예시
> add apple:사과
> add book:책
> list
1. apple = 사과 (○0/×0)
2. book = 책 (○0/×0)
> quiz
[1/5] apple? 사과
정답!
...
종합: 4/5
▶정답 코드 펼치기 / 접기
SOLUTION
# main.py — 통합 예시
import json, random
from pathlib import Path
from dataclasses import dataclass, field, asdict

PATH = Path("wordbook.json")

@dataclass
class Word:
    en: str
    ko: str
    correct: int = 0
    wrong: int = 0

def load():
    if not PATH.exists(): return []
    return [Word(**w) for w in json.loads(PATH.read_text(encoding="utf-8"))]

def save(words):
    PATH.write_text(json.dumps([asdict(w) for w in words], ensure_ascii=False, indent=2), encoding="utf-8")

def quiz(words):
    if not words: return
    sample = random.sample(words, min(5, len(words)))
    score = 0
    for i, w in enumerate(sample, 1):
        ans = input(f"[{i}/{len(sample)}] {w.en}? ").strip()
        if ans == w.ko:
            print("정답!"); w.correct += 1; score += 1
        else:
            print(f"오답. 정답: {w.ko}"); w.wrong += 1
    print(f"종합: {score}/{len(sample)}")

words = load()
while True:
    cmd = input("> ").strip()
    if cmd == "quit": save(words); break
    elif cmd.startswith("add "):
        en, ko = cmd[4:].split(":"); words.append(Word(en.strip(), ko.strip()))
    elif cmd == "list":
        for i, w in enumerate(words, 1):
            print(f"{i}. {w.en} = {w.ko} (○{w.correct}/×{w.wrong})")
    elif cmd == "quiz": quiz(words)
▶ 실행 결과
> add apple:사과
> add book:책
> quiz
[1/2] apple? 사과
정답!
[2/2] book? 가방
오답. 정답: 책
종합: 1/2
> quit
과제 11

간이 가계부 (수입/지출 + JSON + 카테고리)

목표: 수입/지출 기록 + 카테고리별 합계 + JSON 영속화.

요구사항
  • 데이터 클래스 Entry(type, amount, category, memo, date)
  • 명령: add, list, summary, quit
  • summary는 카테고리별 합계 + 잔액
입출력 예시
> add 지출 5000 식비 점심
> add 수입 100000 월급 11월
> summary
수입: 100,000원
지출: 5,000원
잔액: 95,000원

[지출 카테고리]
식비: 5,000원
▶정답 코드 펼치기 / 접기
SOLUTION
import json
from pathlib import Path
from datetime import date
from dataclasses import dataclass, asdict

PATH = Path("ledger.json")

@dataclass
class Entry:
    type: str
    amount: int
    category: str
    memo: str
    date: str

def load():
    if not PATH.exists(): return []
    return [Entry(**e) for e in json.loads(PATH.read_text(encoding="utf-8"))]

def save(entries):
    PATH.write_text(json.dumps([asdict(e) for e in entries], ensure_ascii=False, indent=2), encoding="utf-8")

def summary(entries):
    inc = sum(e.amount for e in entries if e.type == "수입")
    exp = sum(e.amount for e in entries if e.type == "지출")
    print(f"수입: {inc:,}원")
    print(f"지출: {exp:,}원")
    print(f"잔액: {inc - exp:,}원")
    print("\n[지출 카테고리]")
    cat = {}
    for e in entries:
        if e.type == "지출":
            cat[e.category] = cat.get(e.category, 0) + e.amount
    for k, v in cat.items():
        print(f"{k}: {v:,}원")

entries = load()
while True:
    cmd = input("> ").strip()
    if cmd == "quit": save(entries); break
    elif cmd == "summary": summary(entries)
    elif cmd.startswith("add "):
        t, amt, cat, *memo = cmd[4:].split()
        entries.append(Entry(t, int(amt), cat, " ".join(memo), str(date.today())))
▶ 실행 결과
> add 지출 5000 식비 점심
> add 수입 100000 월급
> summary
수입: 100,000원
지출: 5,000원
잔액: 95,000원

[지출 카테고리]
식비: 5,000원
과제 12

로그 분석기 (정규식 + CSV 리포트)

목표: 로그 파일에서 LEVEL/시각/메시지를 정규식으로 분리해 CSV로 리포트.

요구사항
  • 로그 형식: LEVEL YYYY-MM-DD HH:MM 메시지
  • 결과를 report.csv (level, time, message) 로 저장
  • ERROR 개수, WARN 개수, INFO 개수 집계 출력
입출력 예시
처리: 4건
ERROR: 2, WARN: 1, INFO: 1
report.csv 저장됨
▶정답 코드 펼치기 / 접기
SOLUTION
import re, csv
from pathlib import Path

log_text = """INFO 2026-05-09 14:30 서버 시작
ERROR 2026-05-09 14:32 인증 실패
WARN 2026-05-09 14:34 느린 응답
ERROR 2026-05-09 14:35 DB 연결 끊김"""

pattern = re.compile(r"(\w+) \d{4}-\d{2}-\d{2} (\d{2}:\d{2}) (.+)")

rows = []
for line in log_text.splitlines():
    m = pattern.match(line)
    if m:
        rows.append(m.groups())

counts = {}
for level, _, _ in rows:
    counts[level] = counts.get(level, 0) + 1

with open("report.csv", "w", encoding="utf-8", newline="") as f:
    w = csv.writer(f)
    w.writerow(["level", "time", "message"])
    w.writerows(rows)

print(f"처리: {len(rows)}건")
print(", ".join(f"{k}: {v}" for k, v in counts.items()))
print("report.csv 저장됨")
▶ 실행 결과
처리: 4건
INFO: 1, ERROR: 2, WARN: 1
report.csv 저장됨
예제 코드 / 강의 자료

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

GitHub에서 보기 ↗

댓글 0

아직 댓글이 없습니다. 첫 댓글을 남겨 주세요.

댓글은 회원만 쓸 수 있습니다.

광고·욕설·비방·개인정보·불법 내용이 담긴 댓글은 숨기거나 지웁니다. 댓글은 닉네임과 함께 공개됩니다. 개인정보처리방침