Python 실무 활용 (6‐7장): 파일 처리와 표준 라이브러리 - glasslego/getting-started-with-python GitHub Wiki

6. 파일 입출력

6.1 파일 읽기와 쓰기

6.1.1 기본 파일 조작

# 파일 쓰기
with open('example.txt', 'w', encoding='utf-8') as file:
    file.write("안녕하세요!\n")
    file.write("Python 파일 입출력 예시입니다.\n")

# 파일 읽기
with open('example.txt', 'r', encoding='utf-8') as file:
    content = file.read()
    print(content)

# 줄 단위로 읽기
with open('example.txt', 'r', encoding='utf-8') as file:
    lines = file.readlines()
    for i, line in enumerate(lines, 1):
        print(f"줄 {i}: {line.strip()}")

# 한 줄씩 처리 (메모리 효율적)
with open('example.txt', 'r', encoding='utf-8') as file:
    for line in file:
        print(f"읽은 줄: {line.strip()}")

# 파일 추가 모드
with open('example.txt', 'a', encoding='utf-8') as file:
    file.write("추가된 내용입니다.\n")

6.1.2 파일 모드와 옵션

# 다양한 파일 모드
modes = {
    'r': '읽기 전용 (기본값)',
    'w': '쓰기 전용 (파일 내용 삭제)',
    'a': '추가 모드 (파일 끝에 추가)',
    'x': '배타적 생성 (파일이 이미 있으면 실패)',
    'b': '바이너리 모드 (rb, wb, ab)',
    't': '텍스트 모드 (기본값)',
    '+': '읽기+쓰기 모드'
}

# 바이너리 파일 처리
with open('image.jpg', 'rb') as file:
    binary_data = file.read()
    print(f"파일 크기: {len(binary_data)} 바이트")

# 바이너리 파일 복사
with open('image.jpg', 'rb') as src:
    with open('image_copy.jpg', 'wb') as dst:
        dst.write(src.read())

# 파일 존재 여부 확인
import os

if os.path.exists('example.txt'):
    print("파일이 존재합니다.")
    print(f"파일 크기: {os.path.getsize('example.txt')} 바이트")

6.1.3 CSV 파일 처리

import csv

# CSV 파일 쓰기
students = [
    ['이름', '나이', '성적'],
    ['Alice', 20, 85],
    ['Bob', 22, 92],
    ['Charlie', 21, 78]
]

with open('students.csv', 'w', newline='', encoding='utf-8') as file:
    writer = csv.writer(file)
    writer.writerows(students)

# CSV 파일 읽기
with open('students.csv', 'r', encoding='utf-8') as file:
    reader = csv.reader(file)
    for row in reader:
        print(row)

# 딕셔너리로 CSV 처리
with open('students.csv', 'r', encoding='utf-8') as file:
    reader = csv.DictReader(file)
    for row in reader:
        print(f"이름: {row['이름']}, 나이: {row['나이']}, 성적: {row['성적']}")

# 딕셔너리로 CSV 쓰기
students_dict = [
    {'이름': 'David', '나이': 23, '성적': 88},
    {'이름': 'Eve', '나이': 20, '성적': 95}
]

with open('students_dict.csv', 'w', newline='', encoding='utf-8') as file:
    fieldnames = ['이름', '나이', '성적']
    writer = csv.DictWriter(file, fieldnames=fieldnames)
    writer.writeheader()
    writer.writerows(students_dict)

6.1.4 JSON 파일 처리

import json

# 데이터 준비
data = {
    "students": [
        {"name": "Alice", "age": 20, "grades": [85, 92, 78]},
        {"name": "Bob", "age": 22, "grades": [92, 88, 95]},
        {"name": "Charlie", "age": 21, "grades": [78, 85, 90]}
    ],
    "course": "Python Programming",
    "semester": "2024-1"
}

# JSON 파일 쓰기
with open('students.json', 'w', encoding='utf-8') as file:
    json.dump(data, file, ensure_ascii=False, indent=2)

# JSON 파일 읽기
with open('students.json', 'r', encoding='utf-8') as file:
    loaded_data = json.load(file)
    print(f"과목: {loaded_data['course']}")
    for student in loaded_data['students']:
        avg_grade = sum(student['grades']) / len(student['grades'])
        print(f"{student['name']}: 평균 {avg_grade:.1f}점")

# JSON 문자열 처리
json_string = json.dumps(data, ensure_ascii=False, indent=2)
print("JSON 문자열:")
print(json_string)

parsed_data = json.loads(json_string)
print(f"파싱된 데이터 타입: {type(parsed_data)}")

6.2 디렉토리와 경로 조작

import os
import shutil
from pathlib import Path

# os 모듈 사용
current_dir = os.getcwd()
print(f"현재 디렉토리: {current_dir}")

# 디렉토리 생성
os.makedirs('test_dir/sub_dir', exist_ok=True)

# 파일/디렉토리 목록
files = os.listdir('.')
for file in files:
    if os.path.isfile(file):
        print(f"파일: {file}")
    elif os.path.isdir(file):
        print(f"디렉토리: {file}")

# 경로 조작
file_path = os.path.join('test_dir', 'sub_dir', 'test.txt')
print(f"경로: {file_path}")
print(f"디렉토리: {os.path.dirname(file_path)}")
print(f"파일명: {os.path.basename(file_path)}")
print(f"확장자: {os.path.splitext(file_path)[1]}")

# pathlib 사용 (Python 3.4+, 더 직관적)
path = Path('test_dir') / 'sub_dir' / 'test.txt'
print(f"경로: {path}")
print(f"부모 디렉토리: {path.parent}")
print(f"파일명: {path.name}")
print(f"확장자: {path.suffix}")

# 파일 생성
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("테스트 내용", encoding='utf-8')

# 파일 정보
if path.exists():
    print(f"파일 크기: {path.stat().st_size} 바이트")
    print(f"파일 내용: {path.read_text(encoding='utf-8')}")

# 디렉토리 순회
for file_path in Path('.').glob('*.py'):
    print(f"Python 파일: {file_path}")

for file_path in Path('.').rglob('*.txt'):  # 재귀적 검색
    print(f"텍스트 파일: {file_path}")

# 파일/디렉토리 이동과 복사
shutil.copy('students.csv', 'test_dir/students_backup.csv')  # 파일 복사
shutil.move('test_dir/students_backup.csv', 'test_dir/sub_dir/')  # 파일 이동

# 디렉토리 전체 복사
shutil.copytree('test_dir', 'test_dir_copy')

# 디렉토리 삭제
shutil.rmtree('test_dir_copy')

7. 내장 함수와 표준 라이브러리

7.1 주요 내장 함수

7.1.1 기본 내장 함수

# 수학 관련
print(abs(-5))           # 5 (절댓값)
print(round(3.14159, 2)) # 3.14 (반올림)
print(pow(2, 3))         # 8 (거듭제곱)
print(divmod(17, 5))     # (3, 2) (몫과 나머지)

# 형변환
print(int("123"))        # 123
print(float("3.14"))     # 3.14
print(str(42))           # "42"
print(bool(1))           # True
print(list("hello"))     # ['h', 'e', 'l', 'l', 'o']
print(tuple([1, 2, 3]))  # (1, 2, 3)
print(set([1, 2, 2, 3])) # {1, 2, 3}

# 시퀀스 관련
numbers = [1, 2, 3, 4, 5]
print(len(numbers))      # 5 (길이)
print(max(numbers))      # 5 (최댓값)
print(min(numbers))      # 1 (최솟값)
print(sum(numbers))      # 15 (합계)
print(sorted(numbers, reverse=True))  # [5, 4, 3, 2, 1]

# 기타 유용한 함수
print(any([False, True, False]))  # True (하나라도 True면)
print(all([True, True, False]))   # False (모두 True여야)
print(zip([1, 2, 3], ['a', 'b', 'c']))  # zip 객체
print(list(zip([1, 2, 3], ['a', 'b', 'c'])))  # [(1, 'a'), (2, 'b'), (3, 'c')]

# enumerate
fruits = ['apple', 'banana', 'orange']
for index, fruit in enumerate(fruits, start=1):
    print(f"{index}. {fruit}")

# range
print(list(range(5)))         # [0, 1, 2, 3, 4]
print(list(range(2, 8)))      # [2, 3, 4, 5, 6, 7]
print(list(range(0, 10, 2)))  # [0, 2, 4, 6, 8]

7.1.2 고급 내장 함수

# map, filter, reduce 예시
numbers = [1, 2, 3, 4, 5]

# map: 각 요소에 함수 적용
squares = list(map(lambda x: x**2, numbers))
print(squares)  # [1, 4, 9, 16, 25]

# filter: 조건을 만족하는 요소만 필터링
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens)  # [2, 4]

# reduce: 누적 연산
from functools import reduce
total = reduce(lambda x, y: x + y, numbers)
print(total)  # 15

# isinstance, hasattr, getattr
class Person:
    def __init__(self, name):
        self.name = name
    
    def greet(self):
        return f"Hello, I'm {self.name}"

person = Person("Alice")

print(isinstance(person, Person))        # True
print(hasattr(person, 'name'))          # True
print(hasattr(person, 'age'))           # False
print(getattr(person, 'name'))          # "Alice"
print(getattr(person, 'age', 'Unknown')) # "Unknown" (기본값)

# vars, dir
print(vars(person))  # {'name': 'Alice'} (객체의 __dict__)
print(dir(person))   # 객체의 모든 속성과 메서드 리스트

# id, type
print(id(person))    # 객체의 메모리 주소
print(type(person))  # <class '__main__.Person'>

# globals, locals
def test_function():
    local_var = "지역 변수"
    print("지역 변수들:", locals())

global_var = "전역 변수"
print("전역 변수들 (일부):", {k: v for k, v in globals().items() if not k.startswith('_')})
test_function()

7.2 중요한 표준 라이브러리

7.2.1 collections 모듈

from collections import Counter, defaultdict, deque, namedtuple, OrderedDict

# Counter: 개수 세기
text = "hello world"
counter = Counter(text)
print(counter)  # Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})
print(counter.most_common(3))  # [('l', 3), ('o', 2), ('h', 1)]

words = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']
word_count = Counter(words)
print(word_count['apple'])  # 3

# defaultdict: 기본값이 있는 딕셔너리
dd = defaultdict(list)
dd['fruits'].append('apple')
dd['fruits'].append('banana')
print(dd)  # defaultdict(<class 'list'>, {'fruits': ['apple', 'banana']})

# 그룹화 예시
students = [('Alice', 'Math'), ('Bob', 'Science'), ('Charlie', 'Math'), ('David', 'Science')]
groups = defaultdict(list)
for name, subject in students:
    groups[subject].append(name)
print(dict(groups))  # {'Math': ['Alice', 'Charlie'], 'Science': ['Bob', 'David']}

# deque: 양방향 큐
dq = deque([1, 2, 3])
dq.appendleft(0)    # 왼쪽에 추가
dq.append(4)        # 오른쪽에 추가
print(dq)           # deque([0, 1, 2, 3, 4])

print(dq.popleft()) # 0 (왼쪽에서 제거)
print(dq.pop())     # 4 (오른쪽에서 제거)
print(dq)           # deque([1, 2, 3])

# namedtuple: 이름이 있는 튜플
Point = namedtuple('Point', ['x', 'y'])
p = Point(3, 4)
print(f"x: {p.x}, y: {p.y}")  # x: 3, y: 4
print(p._asdict())             # {'x': 3, 'y': 4}

Person = namedtuple('Person', 'name age city')
person = Person('Alice', 25, 'Seoul')
print(person.name)             # Alice

7.2.2 itertools 모듈

import itertools

# count: 무한 카운터
counter = itertools.count(10, 2)  # 10부터 2씩 증가
for i, num in enumerate(counter):
    if i >= 5:
        break
    print(num)  # 10, 12, 14, 16, 18

# cycle: 무한 반복
colors = itertools.cycle(['red', 'green', 'blue'])
for i, color in enumerate(colors):
    if i >= 7:
        break
    print(color)  # red, green, blue, red, green, blue, red

# repeat: 값 반복
repeated = list(itertools.repeat('hello', 3))
print(repeated)  # ['hello', 'hello', 'hello']

# chain: 여러 이터러블 연결
list1 = [1, 2, 3]
list2 = [4, 5, 6]
list3 = [7, 8, 9]
chained = list(itertools.chain(list1, list2, list3))
print(chained)  # [1, 2, 3, 4, 5, 6, 7, 8, 9]

# combinations: 조합
items = ['A', 'B', 'C', 'D']
for combo in itertools.combinations(items, 2):
    print(combo)  # ('A', 'B'), ('A', 'C'), ('A', 'D'), ('B', 'C'), ('B', 'D'), ('C', 'D')

# permutations: 순열
for perm in itertools.permutations(['A', 'B', 'C'], 2):
    print(perm)  # ('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('C', 'A'), ('C', 'B')

# combinations_with_replacement: 중복 허용 조합
for combo in itertools.combinations_with_replacement(['A', 'B'], 2):
    print(combo)  # ('A', 'A'), ('A', 'B'), ('B', 'B')

# product: 데카르트 곱
for prod in itertools.product(['A', 'B'], [1, 2]):
    print(prod)  # ('A', 1), ('A', 2), ('B', 1), ('B', 2)

# groupby: 그룹화
data = [('A', 1), ('A', 2), ('B', 3), ('B', 4), ('C', 5)]
for key, group in itertools.groupby(data, key=lambda x: x[0]):
    print(f"{key}: {list(group)}")
# A: [('A', 1), ('A', 2)]
# B: [('B', 3), ('B', 4)]
# C: [('C', 5)]

# takewhile, dropwhile: 조건부 필터링
numbers = [1, 3, 5, 8, 10, 12, 7, 9]
taken = list(itertools.takewhile(lambda x: x < 8, numbers))
print(taken)  # [1, 3, 5] (조건을 만족하는 동안만)

dropped = list(itertools.dropwhile(lambda x: x < 8, numbers))
print(dropped)  # [8, 10, 12, 7, 9] (조건을 만족하지 않는 첫 번째부터)

7.2.3 datetime 모듈

from datetime import datetime, date, time, timedelta, timezone
import calendar

# 현재 날짜와 시간
now = datetime.now()
today = date.today()
current_time = datetime.now().time()

print(f"현재 날짜시간: {now}")
print(f"오늘 날짜: {today}")
print(f"현재 시간: {current_time}")

# 특정 날짜/시간 생성
specific_date = date(2024, 12, 25)
specific_datetime = datetime(2024, 12, 25, 14, 30, 0)
specific_time = time(14, 30, 0)

print(f"크리스마스: {specific_date}")
print(f"특정 시간: {specific_datetime}")

# 날짜 계산
tomorrow = today + timedelta(days=1)
week_later = today + timedelta(weeks=1)
hour_later = now + timedelta(hours=1)

print(f"내일: {tomorrow}")
print(f"일주일 후: {week_later}")
print(f"한 시간 후: {hour_later}")

# 날짜 차이 계산
birthday = date(2000, 5, 15)
age_days = today - birthday
print(f"태어난 지 {age_days.days}일 지났습니다.")

# 문자열 변환
date_string = now.strftime("%Y-%m-%d %H:%M:%S")
print(f"포맷된 날짜: {date_string}")

korean_format = now.strftime("%Y년 %m월 %d일 %H시 %M분")
print(f"한국식 포맷: {korean_format}")

# 문자열에서 날짜 파싱
date_str = "2024-03-15 14:30:00"
parsed_date = datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S")
print(f"파싱된 날짜: {parsed_date}")

# 요일 정보
weekday = today.weekday()  # 0=월요일, 6=일요일
weekday_name = calendar.day_name[weekday]
print(f"오늘은 {weekday_name}입니다.")

# 월 정보
month_name = calendar.month_name[today.month]
print(f"이번 달은 {month_name}입니다.")

# 윤년 확인
print(f"2024년은 윤년인가? {calendar.isleap(2024)}")

# 타임존 처리
utc_now = datetime.now(timezone.utc)
print(f"UTC 시간: {utc_now}")

# 월 달력 출력
print(calendar.month(2024, 3))

7.2.4 re 모듈 (정규표현식)

import re

# 기본 패턴 매칭
text = "전화번호: 010-1234-5678, 이메일: [email protected]"

# search: 첫 번째 매치 찾기
phone_pattern = r'\d{3}-\d{4}-\d{4}'
phone_match = re.search(phone_pattern, text)
if phone_match:
    print(f"전화번호: {phone_match.group()}")  # 010-1234-5678

# findall: 모든 매치 찾기
email_pattern = r'\w+@\w+\.\w+'
emails = re.findall(email_pattern, text)
print(f"이메일들: {emails}")  # ['[email protected]']

# 그룹 사용
phone_pattern_groups = r'(\d{3})-(\d{4})-(\d{4})'
match = re.search(phone_pattern_groups, text)
if match:
    print(f"전체: {match.group(0)}")    # 010-1234-5678
    print(f"첫 번째: {match.group(1)}") # 010
    print(f"두 번째: {match.group(2)}") # 1234
    print(f"세 번째: {match.group(3)}") # 5678

# 이름 있는 그룹
pattern_named = r'(?P<area>\d{3})-(?P<exchange>\d{4})-(?P<number>\d{4})'
match = re.search(pattern_named, text)
if match:
    print(f"지역번호: {match.group('area')}")

# 문자열 치환
text = "안녕하세요. 제 전화번호는 010-1234-5678입니다."
masked = re.sub(r'\d{3}-\d{4}-\d{4}', 'XXX-XXXX-XXXX', text)
print(masked)  # "안녕하세요. 제 전화번호는 XXX-XXXX-XXXX입니다."

# split: 패턴으로 문자열 분리
text = "apple,banana;orange:grape"
fruits = re.split(r'[,;:]', text)
print(fruits)  # ['apple', 'banana', 'orange', 'grape']

# 컴파일된 패턴 (성능 향상)
email_regex = re.compile(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b')
text = "연락처: [email protected], [email protected]"
emails = email_regex.findall(text)
print(emails)  # ['[email protected]', '[email protected]']

# 유용한 패턴 예시
patterns = {
    'email': r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b',
    'phone': r'\d{2,3}-\d{3,4}-\d{4}',
    'url': r'https?://(?:[-\w.])+(?:[:\d]+)?(?:/(?:[\w/_.])*(?:\?(?:[\w&=%.])*)?(?:#(?:\w*))?)?',
    'ip': r'\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b',
    'korean': r'[가-힣]+',
    'number': r'-?\d+(?:\.\d+)?'
}

test_text = """
웹사이트: https://www.example.com
이메일: [email protected]
전화: 02-1234-5678
IP: 192.168.1.1
이름: 홍길동
가격: -1234.56
"""

for name, pattern in patterns.items():
    matches = re.findall(pattern, test_text)
    print(f"{name}: {matches}")

7.2.5 random 모듈

import random

# 기본 랜덤 함수들
print(random.random())           # 0.0 ~ 1.0 사이 실수
print(random.randint(1, 10))     # 1 ~ 10 사이 정수
print(random.randrange(0, 10, 2)) # 0, 2, 4, 6, 8 중 하나
print(random.uniform(1.0, 10.0)) # 1.0 ~ 10.0 사이 실수

# 시퀀스에서 선택
fruits = ['apple', 'banana', 'orange', 'grape']
print(random.choice(fruits))      # 랜덤하게 하나 선택
print(random.choices(fruits, k=3)) # 중복 허용하여 3개 선택
print(random.sample(fruits, 2))   # 중복 없이 2개 선택

# 가중치가 있는 선택
weights = [10, 5, 1, 1]  # apple이 가장 높은 확률
print(random.choices(fruits, weights=weights, k=5))

# 리스트 섞기
numbers = list(range(10))
print(f"원본: {numbers}")
random.shuffle(numbers)
print(f"섞인 후: {numbers}")

# 시드 설정 (재현 가능한 랜덤)
random.seed(42)
print(random.randint(1, 100))  # 항상 같은 값

random.seed(42)  # 같은 시드
print(random.randint(1, 100))  # 위와 같은 값

# 정규분포 랜덤
print(random.gauss(100, 15))     # 평균 100, 표준편차 15인 정규분포
print(random.normalvariate(100, 15))  # 위와 동일

# 랜덤 문자열 생성
import string

def generate_password(length=8):
    characters = string.ascii_letters + string.digits + "!@#$%^&*"
    return ''.join(random.choices(characters, k=length))

print(f"랜덤 비밀번호: {generate_password(12)}")

# 랜덤 데이터 생성
def generate_sample_data(n=10):
    names = ['Alice', 'Bob', 'Charlie', 'David', 'Eve']
    departments = ['Engineering', 'Marketing', 'Sales', 'HR']
    
    data = []
    for _ in range(n):
        person = {
            'name': random.choice(names),
            'age': random.randint(22, 65),
            'department': random.choice(departments),
            'salary': random.randint(30000, 120000)
        }
        data.append(person)
    return data

sample_data = generate_sample_data(5)
for person in sample_data:
    print(person)

7.2.6 pathlib 모듈 (현대적 경로 처리)

from pathlib import Path
import shutil

# Path 객체 생성
current_dir = Path.cwd()  # 현재 디렉토리
home_dir = Path.home()    # 홈 디렉토리
file_path = Path('data') / 'files' / 'example.txt'

print(f"현재 디렉토리: {current_dir}")
print(f"홈 디렉토리: {home_dir}")
print(f"파일 경로: {file_path}")

# 경로 정보
print(f"부모 디렉토리: {file_path.parent}")
print(f"파일명: {file_path.name}")
print(f"확장자: {file_path.suffix}")
print(f"확장자 제외 이름: {file_path.stem}")
print(f"모든 확장자: {file_path.suffixes}")

# 절대 경로와 상대 경로
abs_path = file_path.absolute()
print(f"절대 경로: {abs_path}")

# 경로 존재 여부 확인
if file_path.exists():
    print("파일이 존재합니다.")
else:
    print("파일이 존재하지 않습니다.")

# 디렉토리와 파일 구분
path = Path('.')
if path.is_dir():
    print("디렉토리입니다.")
elif path.is_file():
    print("파일입니다.")

# 디렉토리 생성
new_dir = Path('test_directory')
new_dir.mkdir(exist_ok=True)  # 이미 있어도 에러 없음

nested_dir = Path('test') / 'nested' / 'directory'
nested_dir.mkdir(parents=True, exist_ok=True)  # 부모 디렉토리도 생성

# 파일 읽기/쓰기
text_file = Path('example.txt')
text_file.write_text("안녕하세요!\nPython pathlib 예시입니다.", encoding='utf-8')
content = text_file.read_text(encoding='utf-8')
print(f"파일 내용:\n{content}")

# 바이너리 파일
binary_file = Path('example.bin')
binary_file.write_bytes(b'\x00\x01\x02\x03')
binary_content = binary_file.read_bytes()
print(f"바이너리 내용: {binary_content}")

# 파일 목록 순회
for file in Path('.').iterdir():
    if file.is_file():
        print(f"파일: {file.name}")
    elif file.is_dir():
        print(f"디렉토리: {file.name}")

# 패턴 매칭으로 파일 찾기
python_files = list(Path('.').glob('*.py'))
print(f"Python 파일들: {python_files}")

# 재귀적 검색
all_text_files = list(Path('.').rglob('*.txt'))
print(f"모든 텍스트 파일들: {all_text_files}")

# 파일 정보
if text_file.exists():
    stat = text_file.stat()
    print(f"파일 크기: {stat.st_size} 바이트")
    print(f"수정 시간: {stat.st_mtime}")
    
    # 파일 권한 (Unix/Linux)
    print(f"권한: {oct(stat.st_mode)}")

# 파일 이동과 이름 변경
old_name = Path('example.txt')
new_name = Path('renamed_example.txt')
if old_name.exists():
    old_name.rename(new_name)

# 파일 복사 (shutil 필요)
if new_name.exists():
    shutil.copy(new_name, 'copied_example.txt')

# 임시 파일/디렉토리
import tempfile

with tempfile.TemporaryDirectory() as temp_dir:
    temp_path = Path(temp_dir)
    temp_file = temp_path / 'temp.txt'
    temp_file.write_text('임시 파일입니다.')
    print(f"임시 파일 위치: {temp_file}")
    print(f"임시 파일 내용: {temp_file.read_text()}")
# with 블록을 벗어나면 임시 디렉토리 자동 삭제

# 정리
for cleanup_file in ['renamed_example.txt', 'copied_example.txt', 'example.bin']:
    Path(cleanup_file).unlink(missing_ok=True)  # 파일 삭제
    
if new_dir.exists():
    shutil.rmtree(new_dir)  # 디렉토리 삭제
⚠️ **GitHub.com Fallback** ⚠️