포스트

[Python 100일 챌린지] Day 48 - 디버깅 기법

[Python 100일 챌린지] Day 48 - 디버깅 기법

import pdb; pdb.set_trace() → 프로그램 멈춤 → 변수값 확인 → 버그 발견! 😊

“왜 이 변수가 None이지?” print 100번 찍지 말고, 한 줄로 멈춰서 확인! 프로 개발자처럼 pdb 디버거로 버그를 추적합니다!

(45-55분 완독 ⭐⭐⭐)

🎯 오늘의 학습 목표

📚 사전 지식


🎯 학습 목표 1: 디버깅의 개념과 방법 이해하기

1.1 디버깅이란?

디버깅(Debugging): 프로그램의 오류(버그)를 찾아 수정하는 과정

1
2
3
4
5
6
7
8
9
10
11
# 버그가 있는 코드
def calculate_average(numbers):
    total = sum(numbers)
    return total / len(numbers)  # 빈 리스트면 ZeroDivisionError!

# 버그를 찾아 수정
def calculate_average(numbers):
    if not numbers:  # 빈 리스트 체크
        return 0
    total = sum(numbers)
    return total / len(numbers)

1.2 디버깅 방법 종류

방법 설명 적합한 상황
print 디버깅 변수 값을 출력해서 확인 간단한 버그, 빠른 확인
로깅 logging 모듈로 기록 운영 환경, 복잡한 흐름
디버거 (pdb) 코드를 멈추고 단계별 실행 복잡한 로직, 정밀 분석
프로파일링 실행 시간/성능 측정 성능 문제, 병목 구간

1.3 버그의 종류

1
2
3
4
5
6
7
8
9
10
11
# 1. 구문 오류 (Syntax Error) - 실행 전에 발견
print("Hello"  # SyntaxError: 괄호 누락

# 2. 런타임 오류 (Runtime Error) - 실행 중 발생
numbers = [1, 2, 3]
print(numbers[10])  # IndexError

# 3. 논리 오류 (Logic Error) - 가장 찾기 어려움!
def is_adult(age):
    return age > 18  # 버그: 18세도 성인인데 False 반환
    # 올바른 코드: return age >= 18

💡 논리 오류가 가장 찾기 어렵습니다. 프로그램은 정상 실행되지만 결과가 틀립니다!


🎯 학습 목표 2: print 디버깅과 로깅 활용하기

2.1 기본 print 디버깅

1
2
3
4
5
6
7
8
9
10
def calculate_total(prices):
    print(f"입력: {prices}")  # 디버깅
    total = sum(prices)
    print(f"결과: {total}")  # 디버깅
    return total

calculate_total([100, 200, 300])
# 출력:
# 입력: [100, 200, 300]
# 결과: 600

2.2 향상된 print 디버깅

1
2
3
4
5
6
7
8
9
10
11
def debug_print(var_name, value):
    """디버그 출력 - 타입까지 표시"""
    print(f"[DEBUG] {var_name} = {value} (type: {type(value).__name__})")

numbers = [1, 2, 3]
debug_print("numbers", numbers)
# [DEBUG] numbers = [1, 2, 3] (type: list)

name = "Alice"
debug_print("name", name)
# [DEBUG] name = Alice (type: str)

2.3 로깅을 활용한 디버깅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import logging

# 로깅 설정
logging.basicConfig(level=logging.DEBUG)

def process_order(order_id, items):
    logging.debug(f"주문 처리 시작: {order_id}")
    logging.debug(f"상품 목록: {items}")

    total = sum(item['price'] for item in items)
    logging.info(f"총액 계산 완료: {total}")

    return total

# 사용
items = [{'name': '사과', 'price': 1000}, {'name': '', 'price': 2000}]
process_order("ORD-001", items)

💡 print vs logging: 개발 중에는 print가 편리하지만, 운영 환경에서는 logging을 사용하세요!


🎯 학습 목표 3: pdb 디버거 사용하기

3.1 기본 사용법

1
2
3
4
5
6
7
8
9
import pdb

def buggy_function(x, y):
    result = x + y
    pdb.set_trace()  # 여기서 프로그램이 멈춤!
    final = result * 2
    return final

buggy_function(10, 20)

실행하면 터미널에 이렇게 표시됩니다:

1
2
3
> /path/to/script.py(6)buggy_function()
-> final = result * 2
(Pdb) _

여기서 (Pdb) 프롬프트가 나타나면 명령어를 입력할 수 있습니다:

  • p result 입력 → 30 출력 (result 변수 값 확인)
  • p x, y 입력 → (10, 20) 출력
  • n 입력 → 다음 줄로 이동
  • c 입력 → 프로그램 계속 실행

3.2 pdb 명령어

명령어 설명 예시
n (next) 다음 줄 실행 n
s (step) 함수 안으로 들어감 s
c (continue) 다음 중단점까지 실행 c
p 변수 변수 값 출력 p result
pp 변수 변수 예쁘게 출력 pp items
l (list) 현재 위치 코드 보기 l
w (where) 호출 스택 보기 w
q (quit) 디버거 종료 q

pdb 세션 예시 (실제 터미널 화면):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ python debug_example.py
> /home/user/debug_example.py(6)buggy_function()
-> final = result * 2
(Pdb) p result
30
(Pdb) p x, y
(10, 20)
(Pdb) n
> /home/user/debug_example.py(7)buggy_function()
-> return final
(Pdb) p final
60
(Pdb) c
$

💡 pdb 종료: q 또는 quit을 입력하면 프로그램이 즉시 종료됩니다. c (continue)는 프로그램을 끝까지 실행합니다.

3.3 breakpoint() (Python 3.7+)

1
2
3
4
5
6
7
def calculate(x, y):
    result = x + y
    breakpoint()  # pdb.set_trace()와 동일하지만 더 간편!
    return result * 2

# 환경 변수로 비활성화 가능
# PYTHONBREAKPOINT=0 python script.py

💡 breakpoint() 는 Python 3.7+에서 사용 가능하며, pdb.set_trace()보다 짧고 편리합니다!


🎯 학습 목표 4: 디버깅 베스트 프랙티스 익히기

4.1 전략 1: 이진 탐색

문제가 있는 구간을 절반씩 좁혀가는 방법입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
# 문제 있는 구간을 절반씩 좁혀가기
def process_data(data):
    step1 = clean_data(data)
    print("Step 1 OK")  # ✅ 여기까지는 정상

    step2 = transform_data(step1)
    print("Step 2 OK")  # ❌ 여기서 문제 발생!

    step3 = save_data(step2)
    print("Step 3 OK")
    return step3

# Step 1과 Step 2 사이에서 문제 발생 → transform_data() 집중 분석

4.2 전략 2: 최소 재현 예제

복잡한 코드에서 버그의 핵심만 추출합니다.

1
2
3
4
5
6
7
8
# ❌ 복잡한 코드 (100줄)에서 버그 찾기 어려움
def complex_function(a, b, c, d, e):
    # 100줄의 코드...
    pass

# ✅ 최소 예제로 축소해서 문제 파악
def simple_test():
    result = 10 / 0  # 문제 발견! ZeroDivisionError

4.3 프로파일링 - 실행 시간 측정

1
2
3
4
5
6
7
8
9
10
import time

start = time.time()
# 측정할 코드
time.sleep(1)
result = sum(range(1000000))
end = time.time()

print(f"실행 시간: {end - start:.2f}")
# 실행 시간: 1.05초

4.4 프로파일링 - cProfile

1
2
3
4
5
6
7
8
9
10
11
import cProfile

def slow_function():
    total = 0
    for i in range(1000000):
        total += i
    return total

# 프로파일링 실행
cProfile.run('slow_function()')
# 출력: 함수별 호출 횟수, 총 실행 시간, 개별 실행 시간 등

💡 프로파일링은 “어디가 느린지” 찾는 것입니다. 최적화 전에 항상 측정하세요!


🎯 학습 목표 5: assert 문과 방어적 프로그래밍

5.1 assert 문이란?

assert 문: “이 조건이 반드시 참이어야 한다”고 선언하는 문장. 조건이 거짓이면 프로그램이 즉시 중단됩니다.

1
2
3
4
5
6
7
8
9
10
def calculate_average(numbers):
    assert len(numbers) > 0, "빈 리스트는 평균을 계산할 수 없습니다"
    return sum(numbers) / len(numbers)

# 정상 케이스
print(calculate_average([1, 2, 3, 4, 5]))  # 3.0

# 오류 케이스
print(calculate_average([]))
# AssertionError: 빈 리스트는 평균을 계산할 수 없습니다

5.2 assert vs if문

구분 assert if문
목적 개발 중 버그 검출 런타임 오류 처리
운영 환경 비활성화 가능 (python -O) 항상 실행
사용 시점 “이건 절대 발생하면 안 됨” “이건 발생할 수 있음”
1
2
3
4
5
6
7
8
9
# assert 사용: 개발자 실수 검출 (내부 오류)
def process_data(data):
    assert isinstance(data, list), "data는 리스트여야 합니다"
    # ...

# if 사용: 사용자 입력 검증 (외부 오류)
def get_user_age(age_input):
    if age_input < 0:
        raise ValueError("나이는 음수일 수 없습니다")

5.3 assert 활용 패턴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 1. 함수 입력 검증
def divide(a, b):
    assert b != 0, "0으로 나눌 수 없습니다"
    return a / b

# 2. 반환값 검증
def get_positive_number():
    result = some_calculation()
    assert result > 0, f"양수가 반환되어야 하는데 {result}가 반환됨"
    return result

# 3. 상태 검증
class Order:
    def ship(self):
        assert self.status == "paid", f"결제 완료 상태여야 하는데 {self.status}"
        self.status = "shipped"

💡 assert는 운영 환경에서 비활성화될 수 있습니다! (python -O 옵션) 따라서 사용자 입력 검증에는 if문과 예외 처리를 사용하세요.


🎯 학습 목표 6: 자주 발생하는 버그 패턴

6.1 변수 참조 오류

1
2
3
4
5
6
7
# ❌ 버그: 변수명 오타
usernmae = "Alice"  # 오타!
print(username)      # NameError: name 'username' is not defined

# ✅ 해결: 변수명 주의, IDE 자동완성 활용
username = "Alice"
print(username)

6.2 리스트/딕셔너리 인덱스 오류

1
2
3
4
5
6
7
8
9
10
11
# ❌ 버그: 인덱스 범위 초과
items = ["a", "b", "c"]
print(items[3])  # IndexError: list index out of range

# ✅ 해결: 범위 확인 또는 get() 사용
if len(items) > 3:
    print(items[3])

# 딕셔너리에서는 get() 활용
user = {"name": "Alice"}
print(user.get("age", "정보 없음"))  # "정보 없음" (KeyError 방지)

6.3 None 값 처리 오류

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# ❌ 버그: None에 메서드 호출
def find_user(user_id):
    # 사용자를 찾지 못하면 None 반환
    return None

user = find_user(123)
print(user.name)  # AttributeError: 'NoneType' object has no attribute 'name'

# ✅ 해결: None 체크
user = find_user(123)
if user is not None:
    print(user.name)
else:
    print("사용자를 찾을 수 없습니다")

# 또는 간단히
if user:
    print(user.name)

6.4 변경 가능한 기본 인자 버그

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# ❌ 버그: 리스트를 기본값으로 사용
def add_item(item, items=[]):  # 버그! 모든 호출이 같은 리스트 공유
    items.append(item)
    return items

print(add_item("a"))  # ['a']
print(add_item("b"))  # ['a', 'b']  ← 이전 호출 결과가 남아있음!

# ✅ 해결: None을 기본값으로 사용
def add_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

print(add_item("a"))  # ['a']
print(add_item("b"))  # ['b'] ✅

6.5 비교 연산 오류

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# ❌ 버그: == vs = 혼동 (문법 오류로 잡힘)
# if x = 5:  # SyntaxError

# ❌ 버그: == vs is 혼동
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b)  # True (값이 같음)
print(a is b)  # False (다른 객체)

# ✅ 해결: 값 비교는 ==, None 비교는 is
if user is None:      # ✅ None과 비교할 때는 is
    pass
if score == 100:      # ✅ 값 비교할 때는 ==
    pass

6.6 루프 변수 범위 오류

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# ❌ 버그: 범위가 1 모자람
for i in range(5):    # 0, 1, 2, 3, 4 (5번 반복)
    print(i)

# 1부터 5까지 출력하고 싶었다면?
for i in range(1, 6):  # 1, 2, 3, 4, 5 ✅
    print(i)

# ❌ 버그: 리스트 수정하면서 순회
items = [1, 2, 3, 4, 5]
for item in items:
    if item % 2 == 0:
        items.remove(item)  # 버그! 순회 중 리스트 수정
print(items)  # [1, 3, 5] 가 아니라 예상치 못한 결과

# ✅ 해결: 복사본 순회 또는 리스트 컴프리헨션
items = [1, 2, 3, 4, 5]
items = [item for item in items if item % 2 != 0]
print(items)  # [1, 3, 5] ✅

🎯 학습 목표 7: IDE 디버거 활용 (VS Code / PyCharm)

7.1 IDE 디버거 vs pdb

기능 pdb (터미널) IDE 디버거
시각화 텍스트만 그래픽 UI
중단점 설정 코드에 직접 작성 클릭으로 설정
변수 확인 명령어 입력 자동 표시
호출 스택 w 명령어 항상 표시
학습 곡선 높음 낮음

💡 입문자에게는 IDE 디버거 추천! 시각적으로 변수 값을 확인하고, 클릭으로 중단점을 설정할 수 있습니다.

7.2 VS Code 디버깅

설정 방법:

  1. Python 확장 프로그램 설치
  2. 중단점 설정: 줄 번호 왼쪽 클릭 (빨간 점 표시)
  3. F5 키로 디버깅 시작
  4. 디버그 패널에서 변수 값 확인
1
2
3
4
5
6
7
# example.py
def calculate(x, y):
    result = x + y      # ← 여기에 중단점 설정 (줄 번호 클릭)
    final = result * 2
    return final

print(calculate(10, 20))

디버그 조작:

  • F5: 디버깅 시작/계속
  • F10: 다음 줄로 (Step Over)
  • F11: 함수 안으로 (Step Into)
  • Shift+F11: 함수 밖으로 (Step Out)
  • Shift+F5: 디버깅 중지

7.3 PyCharm 디버깅

설정 방법:

  1. 줄 번호 클릭으로 중단점 설정
  2. 우클릭 → “Debug” 선택
  3. Debug 패널에서 변수, 호출 스택 확인

디버그 조작:

  • F8: 다음 줄로 (Step Over)
  • F7: 함수 안으로 (Step Into)
  • Shift+F8: 함수 밖으로 (Step Out)
  • F9: 계속 실행 (Resume)

💡 조건부 중단점: 특정 조건에서만 멈추게 할 수 있습니다! (예: i == 100일 때만)

  • VS Code: 중단점 우클릭 → “Edit Breakpoint” → 조건 입력
  • PyCharm: 중단점 우클릭 → Condition 입력

🎯 학습 목표 8: 실전 디버깅 시나리오

8.1 시나리오: 쇼핑몰 할인 계산 버그

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 버그가 있는 코드
def calculate_final_price(price, discount_percent, coupon_discount):
    """
    최종 가격 계산
    - price: 원래 가격
    - discount_percent: 할인율 (예: 20 = 20%)
    - coupon_discount: 쿠폰 할인 금액
    """
    discounted = price * discount_percent  # 버그 1
    after_coupon = discounted - coupon_discount  # 버그 2
    return after_coupon

# 테스트: 10000원, 20% 할인, 1000원 쿠폰
result = calculate_final_price(10000, 20, 1000)
print(f"최종 가격: {result}")
# 예상: 7000원 (10000 * 0.8 - 1000)
# 실제: 199000원 ← 뭔가 잘못됨!

디버깅 과정:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Step 1: print로 중간값 확인
def calculate_final_price(price, discount_percent, coupon_discount):
    print(f"[DEBUG] price={price}, discount_percent={discount_percent}")

    discounted = price * discount_percent
    print(f"[DEBUG] discounted={discounted}")  # 200000 ← 이상함!

    after_coupon = discounted - coupon_discount
    print(f"[DEBUG] after_coupon={after_coupon}")

    return after_coupon

# Step 2: 버그 발견
# discounted = price * discount_percent = 10000 * 20 = 200000
# 버그 1: 퍼센트를 비율로 변환하지 않음 (20 → 0.2)
# 버그 2: 할인된 가격에서 쿠폰을 빼야 하는데, 할인 금액에서 빼고 있음

# Step 3: 수정
def calculate_final_price(price, discount_percent, coupon_discount):
    discount_rate = discount_percent / 100  # 20 → 0.2
    discounted = price * (1 - discount_rate)  # 10000 * 0.8 = 8000
    after_coupon = discounted - coupon_discount  # 8000 - 1000 = 7000
    return after_coupon

result = calculate_final_price(10000, 20, 1000)
print(f"최종 가격: {result}")  # 7000원 ✅

8.2 시나리오: 사용자 목록 필터링 버그

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 버그가 있는 코드
def filter_adults(users):
    """18세 이상 사용자만 필터링"""
    adults = []
    for user in users:
        if user['age'] > 18:  # 버그: 18세도 성인인데 제외됨
            adults.append(user)
    return adults

users = [
    {"name": "Alice", "age": 18},
    {"name": "Bob", "age": 20},
    {"name": "Charlie", "age": 15}
]

adults = filter_adults(users)
print(adults)
# 예상: Alice, Bob
# 실제: Bob만 포함 ← 18세 Alice가 빠짐!

디버깅 과정:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# pdb로 디버깅
import pdb

def filter_adults(users):
    adults = []
    for user in users:
        pdb.set_trace()  # 각 반복에서 멈춤
        if user['age'] > 18:
            adults.append(user)
    return adults

# 터미널에서:
# (Pdb) p user
# {'name': 'Alice', 'age': 18}
# (Pdb) p user['age'] > 18
# False  ← 18은 18보다 크지 않음!
# (Pdb) q

# 수정: > 를 >= 로 변경
def filter_adults(users):
    adults = []
    for user in users:
        if user['age'] >= 18:  # 수정됨!
            adults.append(user)
    return adults

8.3 시나리오: API 응답 처리 버그

1
2
3
4
5
6
7
8
9
10
11
12
13
# 버그가 있는 코드
def process_api_response(response):
    """API 응답에서 사용자 이름 추출"""
    user = response['data']['user']
    return user['name']

# 테스트
response1 = {"data": {"user": {"name": "Alice"}}}
print(process_api_response(response1))  # Alice ✅

response2 = {"data": {"user": None}}  # 사용자가 없는 경우
print(process_api_response(response2))
# TypeError: 'NoneType' object is not subscriptable ← 버그!

디버깅 및 수정:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def process_api_response(response):
    """API 응답에서 사용자 이름 추출 (안전 버전)"""
    # 방어적 프로그래밍
    data = response.get('data')
    if data is None:
        return None

    user = data.get('user')
    if user is None:
        return None

    return user.get('name')

# 테스트
print(process_api_response({"data": {"user": {"name": "Alice"}}}))  # Alice
print(process_api_response({"data": {"user": None}}))               # None
print(process_api_response({"data": None}))                         # None
print(process_api_response({}))                                     # None

💡 실전 팁 & 주의사항

✅ 효율적인 디버깅 습관

  1. 문제 재현 먼저
    • 문제를 일관되게 재현할 수 있어야 합니다
    • 최소 재현 예제 만들기
  2. 가설 세우고 검증
    • “이 변수가 None일 것이다” → 확인
    • 추측 대신 증거 기반 디버깅
  3. 변경 사항 추적
    • 한 번에 하나씩 수정
    • 각 변경의 효과 확인

⚠️ 주의사항

  • pdb.set_trace()는 배포 전에 제거하세요
  • 성능 측정은 여러 번 반복 후 평균값 사용
  • print 디버깅 후 코드 정리 필수

🧪 연습 문제

문제 1: 디버깅 도우미 함수

목표: 변수의 이름, 값, 타입을 한 줄로 출력하는 간단한 디버깅 함수를 작성하세요.

요구사항:

  • 변수명과 값을 받아서 출력
  • 타입도 함께 표시
  • [DEBUG] 접두어 사용
해답 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def debug(name, value):
    """간단한 디버그 출력"""
    print(f"[DEBUG] {name} = {value} (type: {type(value).__name__})")

# 테스트
x = [1, 2, 3]
debug("x", x)
# [DEBUG] x = [1, 2, 3] (type: list)

y = "hello"
debug("y", y)
# [DEBUG] y = hello (type: str)

z = 3.14
debug("z", z)
# [DEBUG] z = 3.14 (type: float)

# 실제 디버깅 예시
def calculate_total(items):
    debug("items", items)  # 입력 확인
    total = sum(item['price'] for item in items)
    debug("total", total)  # 중간 결과 확인
    return total

문제 2: 버그 찾기

목표: 아래 코드에서 버그를 찾고 수정하세요.

1
2
3
4
5
6
7
8
9
10
11
def calculate_discount(price, discount_percent):
    """할인 가격 계산"""
    discount = price * discount_percent
    final_price = price - discount
    return final_price

# 테스트: 10000원에서 20% 할인
result = calculate_discount(10000, 20)
print(f"할인 후 가격: {result}")
# 예상: 8000원
# 실제: -190000원 (버그!)
해답 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 버그 원인: discount_percent가 20이면 price * 20 = 200000이 됨
# 퍼센트를 비율로 변환하지 않음!

# 수정된 코드
def calculate_discount(price, discount_percent):
    """할인 가격 계산"""
    discount = price * (discount_percent / 100)  # 퍼센트를 비율로 변환
    final_price = price - discount
    return final_price

# 테스트
result = calculate_discount(10000, 20)
print(f"할인 후 가격: {result}")
# 출력: 할인 후 가격: 8000.0원 ✅

디버깅 과정:

  1. print로 중간값 확인: print(f"discount = {discount}")
  2. discount가 200000인 것을 발견
  3. price * discount_percent에서 퍼센트 변환 누락 확인
  4. /100 추가로 해결

📝 오늘 배운 내용 정리

개념 설명 예시
print 디버깅 변수 값 출력으로 확인 print(f"x = {x}")
pdb Python 내장 디버거 import pdb; pdb.set_trace()
breakpoint() Python 3.7+ 디버깅 breakpoint()
assert 조건 검증 문 assert x > 0, "양수여야 함"
IDE 디버거 시각적 디버깅 도구 VS Code, PyCharm
cProfile 성능 프로파일링 cProfile.run('func()')
자주 발생하는 버그 None, 인덱스, 비교 오류 dict.get(), is None
디버깅 전략 이진 탐색, 최소 재현 -

핵심 코드 패턴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# pdb 디버깅
import pdb

def buggy_function(x, y):
    result = x + y
    pdb.set_trace()  # 여기서 멈춤
    return result * 2

# breakpoint (Python 3.7+)
def my_function():
    breakpoint()  # 디버거 시작
    # 코드...

# 시간 측정
import time
start = time.time()
# 작업
elapsed = time.time() - start

🔗 관련 자료


📚 이전 학습

Day 47: 로깅 시스템 ⭐⭐⭐

어제는 logging 모듈로 프로그램의 실행 기록을 파일로 남기는 방법을 배웠습니다!

📚 다음 학습

Day 49: 파일 시스템 고급 ⭐⭐⭐

내일은 pathlib와 os 모듈로 파일과 디렉토리를 생성, 삭제, 이동하는 방법을 배웁니다!


“늦었다고 생각할 때가 가장 빠른 시기입니다!” 🚀

Day 48/100 Phase 5: 파일 처리와 예외 처리 #100DaysOfPython

이제와서 시작하는 Python 마스터하기 - Day 48 완료! 🎉

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.