7~10주차 — 객체지향 — 클래스와 인스턴스 · 객체지향 — 상속·다형성·매직 메서드 · 정규표현식 · 중급 종합 실습
데이터(속성)와 동작(메서드)을 묶는 클래스 정의, __init__ 생성자와 self의 의미, 인스턴스 변수 vs 클래스 변수, 그리고 _var/__var 캡슐화 관례. 부모 클래스의 속성·메서드를 물려받는 상속, super() 호출, 다형성, __str__/__eq__/__add__ 같은 매직 메서드 오버라이딩, classmethod와 staticmethod까지. re 모듈로 패턴 매칭, 자주 쓰는 메타문자(\d \w \s + * ?), 그룹 캡처(이름 그룹 포함), 치환(sub), raw string, compile 활용까지. OOP·예외·파일 I/O를 모두 사용한 미니 프로젝트 (단어장/가계부/로그 분석/연락처) 중 하나를 모듈 분리된 패키지 구조로 완성합니다.
이 강의에서 배우는 것
- 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. 클래스란
데이터(속성)와 동작(메서드)을 묶은 설계도. 이를 바탕으로 만든 게 인스턴스(객체).
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 — 호출된 인스턴스 자신.
a = Account("홍길동")
a.deposit(100) # 실제로는 Account.deposit(a, 100)self 를 통해 인스턴스 속성에 접근.
3. 인스턴스 변수 vs 클래스 변수
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 로 변환됨)
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 누락
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. 상속
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()
class Puppy(Dog):
def __init__(self, name, age):
super().__init__(name) # 부모 __init__ 호출
self.age = age3. 다형성
같은 메서드 이름을 다양한 클래스에서 다르게 구현. 호출자는 타입을 신경 쓰지 않음.
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 |
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)) # True5. classmethod / staticmethod
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. 자주 하는 실수
- super().__init__() 누락 — 부모 속성이 초기화 안 됨
- 매직 메서드를 일반 호출 — p.add(q) 가 아니라 p + q
- __eq__ 만 만들고 __hash__ 빼먹음 — set/dict 키로 못 씀
- 다중 상속 남용 — 가능하지만 복잡. 가능하면 컴포지션
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. 기본 사용법
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() 로 추출.
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"..." 권장.
re.findall(r"\d+", text) # 권장
re.findall("\\d+", text) # 동일하지만 헷갈림6. 컴파일
같은 패턴을 여러 번 쓰면 미리 컴파일.
email_re = re.compile(r"[\w.]+@[\w.]+\.\w+")
emails = email_re.findall("연락: a@b.com, c@d.co.kr")
print(emails)7. 자주 하는 실수
- 이스케이프 누락 — r 안 붙여서 \d → d. 항상 r"..."
- 탐욕 매칭 — <.+> 가 <a><b> 전체를 매칭. 최소 매칭은 <.+?>
- match vs search — match 는 처음만, search 는 어디서든
- 너무 복잡한 정규식 — 가독성 떨어지면 그냥 코드로 분기
8. FAQ
Q1. 정규식 디버깅 어떻게?
https://regex101.com 같은 사이트에서 패턴 테스트.
Q2. 정규식이 느린가요?
일반적으로 빠름. 다만 catastrophic backtracking 패턴은 매우 느림.
Q3. 이메일 완벽 검증 패턴?
RFC 정확 검증은 매우 길다. 실용적 수준이면 [\w.+-]+@[\w-]+\.[\w.-]+.
10주차 — 중급 종합 실습
OOP·예외·파일 I/O를 모두 사용한 미니 프로젝트 (단어장/가계부/로그 분석/연락처) 중 하나를 모듈 분리된 패키지 구조로 완성합니다.
1. 프로젝트 설계 시 고려사항
- 데이터 모델 — 어떤 클래스들로 표현?
- 영구 저장 — JSON? CSV? 어떤 형태?
- 사용자 인터페이스 — 명령어 형태? 메뉴 형태?
- 모듈 분리 — models.py, storage.py, cli.py 등
2. 권장 패키지 구조
my_app/
├── __init__.py
├── models.py # 클래스 정의 (Word, Book 등)
├── storage.py # JSON 저장/로드
├── cli.py # 메인 루프, 명령어 처리
└── main.py # 진입점3. 자주 하는 실수
- 모든 코드를 main.py 한 파일에 — 분리 안 하면 200줄 넘어가면 미궁
- JSON 직렬화 안 되는 객체 — dataclass + asdict() 활용
- 저장 시점이 잘못됨 — 매 변경 시 vs 종료 시 — 정책 정하기
- 예외를 너무 좁게 또는 너무 넓게 — 회복 가능한 곳에서만 except
4. FAQ
Q1. dataclass 를 써도 되나요?
권장. 단순 데이터 클래스는 dataclass 가 편함. (고급 1주차에서 자세히)
Q2. 단일 파일 vs 패키지?
단일 파일은 50줄 이하만. 그 이상이면 분리.
5. 다음 단계
중급 과정을 마쳤습니다! 🎉
- 고급 과정 — 타입 힌트, 데코레이터, 동시성, 테스트
- 실습 도전 — Lv3 중급종합 (가계부, 도서 관리, 일기장, 텍스트 어드벤처)
💻 예제 (examples)
실제로 실행해 결과를 확인할 수 있는 예제 코드입니다.
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
잔액: 700class 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 직접 전달
안녕, 나는 홍길동!
안녕, 나는 홍길동!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 3class 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
1000class 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())
바둑이 진돗개 멍멍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())
멍멍
야옹
음매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)]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
Falseimport 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']
숫자만
사과 ?개, 바나나 ?개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']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-09import 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']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]
(라이브러리 — 직접 실행 안 함)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)
직접 풀어보고, 막힐 때 정답을 펼쳐 비교해보세요.
BankAccount 클래스
목표: 잔액 조회/입금/출금 메서드 + 잔액 부족 시 예외.
- owner, balance 속성
- deposit, withdraw 메서드
- 잔액 부족 시 ValueError
잔액: 1500
잔액: 700
거부: 잔액 부족▶정답 코드 펼치기 / 접기
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
거부: 잔액 부족Stack 클래스
목표: 후입선출(LIFO) 자료구조를 클래스로 구현.
- push, pop, peek, size, is_empty 메서드
- 내부 저장은 리스트
size=3
peek=3
pop=3
size=2
is_empty=False▶정답 코드 펼치기 / 접기
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=FalseStudent 클래스 (점수 평균/최고)
목표: 이름 + 점수 리스트로 평균과 최고를 계산.
- name, scores 속성
- add_score, average, highest 메서드
홍길동의 평균: 87.0
최고점: 95▶정답 코드 펼치기 / 접기
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
최고점: 95Shape 추상 → Rectangle, Circle
목표: Shape 클래스를 상속해 Rectangle, Circle 구현.
- Shape — area() 메서드 (기본 구현은 0 또는 abstract)
- Rectangle, Circle — area 오버라이딩
- 도형 리스트를 순회해 면적 출력
Rectangle: 200
Circle: 78.54▶정답 코드 펼치기 / 접기
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.54Playlist 클래스 + 매직 메서드
목표: 노래 리스트로 __len__, __getitem__, __add__ 구현.
- songs 속성에 곡 리스트
- len(p), p[0], p1 + p2 동작
len: 3
첫 곡: 봄날
합치기 후 len: 5▶정답 코드 펼치기 / 접기
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: 5Vector 클래스 (+, -, ==, repr)
목표: 2D 벡터 합·차·비교·표현.
- x, y 속성
- __add__, __sub__, __eq__, __repr__
Vector(4, 6)
Vector(-2, -2)
True▶정답 코드 펼치기 / 접기
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이메일 추출
목표: 긴 텍스트에서 이메일 주소만 모두 추출.
- re.findall + 적당한 패턴
['hong@example.com', 'kim.young@a.co.kr', 'support+xyz@my-site.io']▶정답 코드 펼치기 / 접기
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']휴대폰 번호 검증
목표: 010-XXXX-XXXX 형식이 맞는지 True/False.
- re.fullmatch 사용 권장
010-1234-5678: True
02-1234-5678: False
01012345678: False▶정답 코드 펼치기 / 접기
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로그에서 ERROR만 추출
목표: 여러 줄 로그 중 ERROR 라인의 시각·메시지만.
- 각 줄 형식: LEVEL YYYY-MM-DD HH:MM 메시지
- LEVEL == ERROR 인 것만
[14:32] 인증 실패
[14:35] DB 연결 끊김▶정답 코드 펼치기 / 접기
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 연결 끊김단어장 앱 (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▶정답 코드 펼치기 / 접기
# 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간이 가계부 (수입/지출 + 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원▶정답 코드 펼치기 / 접기
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원로그 분석기 (정규식 + 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 저장됨▶정답 코드 펼치기 / 접기
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 저장됨댓글 0
아직 댓글이 없습니다. 첫 댓글을 남겨 주세요.
댓글은 회원만 쓸 수 있습니다.
광고·욕설·비방·개인정보·불법 내용이 담긴 댓글은 숨기거나 지웁니다. 댓글은 닉네임과 함께 공개됩니다. 개인정보처리방침