PYTHON · 심층 가이드
Python 파일/IO 완전 정리
pathlib로 경로를 다루고 CSV·JSON·YAML을 안전하게 읽고 쓰며, 대용량 파일 스트리밍과 SQLite 연동, pyproject.toml 기반 패키징까지 파일 작업 전반을 정리합니다.
파일을 다루는 코드는 대개 로컬에서는 잘 돌아가다가 다른 사람의 머신에서 깨집니다. 원인은 거의 정해져 있습니다. 경로 구분자, 인코딩, 줄바꿈. pathlib은 그중 첫 번째를 없애줍니다. 문자열 결합 대신 / 연산자로 경로를 조립하면 플랫폼 차이가 사라지고, read_text()나 glob() 같은 메서드가 os.path와 open에 흩어져 있던 작업을 한 객체로 모읍니다. 새 코드에서 문자열 경로를 쓸 이유는 거의 없습니다.
파일 I/O & pathlib과 pathlib 심화로 경로 다루기를 끝낸 뒤 포맷별 처리로 넘어갑니다. CSV & JSON & 환경 변수가 큰 그림을 잡고, CSV/Excel 처리와 JSON/YAML 파싱이 세부를 채웁니다. 파일이 메모리보다 커지는 순간부터는 대용량 파일 처리의 스트리밍 기법이 필요하고, 그 데이터를 질의하고 싶어지면 SQLite 연동으로 넘어가는 흐름이 자연스럽습니다. 바깥 세계에서 데이터를 가져오는 HTTP 요청 (requests/httpx)과 웹 스크래핑 (BeautifulSoup), 그리고 결과물을 배포하는 파이썬 패키징 — pyproject.toml이 양 끝을 맡습니다.
인코딩을 생략한 open(path)는 플랫폼의 기본 인코딩을 씁니다. 한국어 Windows에서는 UTF-8이 아닐 수 있어 같은 코드가 macOS에서만 통과하는 상황이 벌어집니다. 텍스트를 열 땐 encoding="utf-8"을 항상 명시하세요. CSV도 마찬가지로 newline=""을 빼면 Windows에서 빈 줄이 하나씩 끼어듭니다. 큰 파일은 read()로 통째로 올리지 말고 파일 객체를 직접 순회하면 한 줄씩 흘려보낼 수 있습니다. 그리고 sqlite3는 자동 커밋이 아니므로 commit()을 호출하거나 커넥션을 with로 감싸야 변경이 남습니다.
01파일 I/O & pathlib
pathlib으로 경로를 객체지향적으로 처리하고 파일을 안전하게 읽고 씁니다.
Python code
from pathlib import Path
import json
# pathlib — 경로 조작
base = Path("/home/user/projects")
file = base / "data" / "config.json" # / 연산자로 결합
print(file.name) # config.json
print(file.stem) # config
print(file.suffix) # .json
print(file.parent) # /home/user/projects/data
# 파일 읽기 (with문으로 자동 닫기)
sample = Path("sample.txt")
sample.write_text("Hello\nWorld\nPython", encoding="utf-8")
# 전체 읽기
content = sample.read_text(encoding="utf-8")
# 줄 단위 읽기
with sample.open(encoding="utf-8") as f:
for line in f: # 메모리 효율적
print(line.rstrip())
# 파일 쓰기
with open("output.txt", "w", encoding="utf-8") as f:
f.write("첫 번째 줄\n")
f.writelines(["두\n", "세\n"])
# JSON 처리
data = {"name": "Alice", "scores": [95, 87, 92]}
Path("data.json").write_text(
json.dumps(data, ensure_ascii=False, indent=2),
encoding="utf-8"
)
loaded = json.loads(Path("data.json").read_text(encoding="utf-8"))
# 디렉토리 순회
for p in Path(".").glob("*.py"):
print(p)
for p in Path(".").rglob("*.json"): # 재귀
print(p)pathlib.Path는 OS에 관계없이 경로를 일관되게 처리합니다. os.path.join()보다 훨씬 직관적입니다.
open()으로 파일을 열고 close()를 빠뜨리면 리소스 누수가 발생합니다. 항상 with문을 사용하세요.
02CSV & JSON & 환경 변수
실무에서 자주 쓰는 데이터 포맷 처리와 환경 변수 관리.
Python code
import csv, json, os
from pathlib import Path
# CSV 읽기
with open("data.csv", newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
print(row["name"], row["score"])
# CSV 쓰기
fields = ["name", "score", "grade"]
rows = [
{"name": "Alice", "score": 95, "grade": "A"},
{"name": "Bob", "score": 82, "grade": "B"},
]
with open("output.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=fields)
writer.writeheader()
writer.writerows(rows)
# JSON — ensure_ascii=False 로 한글 보존
data = {"이름": "앨리스", "점수": 95}
json_str = json.dumps(data, ensure_ascii=False, indent=2)
parsed = json.loads(json_str)
# 파일로 저장/로드
Path("data.json").write_text(json_str, encoding="utf-8")
with open("data.json", encoding="utf-8") as f:
loaded = json.load(f)
# 환경 변수
from dotenv import load_dotenv # pip install python-dotenv
load_dotenv() # .env 파일 로드
db_url = os.getenv("DATABASE_URL", "sqlite:///local.db")
api_key = os.environ.get("API_KEY")
port = int(os.getenv("PORT", "8080"))json.dumps()에 ensure_ascii=False를 설정하지 않으면 한글이 \uXXXX 형태로 이스케이프됩니다.
API 키, DB 비밀번호 등 민감 정보는 절대 코드에 직접 넣지 마세요. .env 파일 + python-dotenv를 사용하고 .gitignore에 추가하세요.
03파이썬 패키징 — pyproject.toml
PEP 621 표준인 pyproject.toml은 setup.py를 대체하는 선언적 패키지 설정 파일입니다. 빌드 시스템, 의존성, 메타데이터를 하나의 파일에 정의하며, pip, poetry, hatch 등 모든 도구가 지원합니다.
Python code
# === pyproject.toml 예제 ===
"""
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "my-awesome-lib"
version = "1.0.0"
description = "교육용 Python 패키지 예제"
readme = "README.md"
license = {text = "MIT"}
requires-python = ">=3.10"
authors = [
{name = "홍길동", email = "hong@example.com"},
]
dependencies = [
"requests>=2.28",
"pydantic>=2.0",
]
[project.optional-dependencies]
dev = ["pytest>=7.0", "ruff>=0.1.0", "mypy>=1.0"]
docs = ["sphinx>=7.0", "sphinx-rtd-theme"]
[project.scripts]
my-cli = "my_awesome_lib.cli:main"
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-v --tb=short"
[tool.ruff]
target-version = "py310"
line-length = 88
[tool.mypy]
strict = true
"""
# === 패키지 구조 ===
"""
my-awesome-lib/
├── pyproject.toml
├── README.md
├── src/
│ └── my_awesome_lib/
│ ├── __init__.py
│ ├── cli.py
│ └── core.py
└── tests/
├── __init__.py
└── test_core.py
"""
# === 빌드 & 배포 명령어 ===
import subprocess, sys
commands = {
"개발 모드 설치": "pip install -e '.[dev]'",
"패키지 빌드": "python -m build",
"PyPI 업로드": "python -m twine upload dist/*",
"린트 검사": "ruff check src/",
"타입 검사": "mypy src/",
"테스트 실행": "pytest",
}
print("패키징 워크플로우:")
for name, cmd in commands.items():
print(f" {name}: {cmd}")
# 실제 사용 예: 현재 환경의 패키지 정보
from importlib.metadata import version, requires
try:
pip_ver = version("pip")
print(f"\npip 버전: {pip_ver}")
except Exception:
print("\npip 정보를 가져올 수 없습니다")src/ 레이아웃을 사용하면 개발 중에 설치되지 않은 패키지를 실수로 import하는 것을 방지합니다. pip install -e .으로 editable 설치하면 코드 변경이 즉시 반영됩니다.
setup.py와 pyproject.toml을 동시에 사용하면 설정이 충돌할 수 있습니다. 새 프로젝트는 pyproject.toml만 사용하고, 기존 프로젝트는 점진적으로 마이그레이션하세요.
04pathlib 심화
pathlib의 고급 기능으로 경로 조작, 파일 검색, 메타데이터 접근을 수행합니다.
Python code
from pathlib import Path
# 경로 조합 (/ 연산자)
base = Path("/home/user")
config = base / "projects" / "app" / "config.yaml"
print(config) # /home/user/projects/app/config.yaml
print(config.parent) # /home/user/projects/app
print(config.stem) # config
print(config.suffix) # .yaml
print(config.name) # config.yaml
# 경로 변환
print(config.with_suffix(".json")) # .../config.json
print(config.with_name("settings.yaml"))
# 현재 디렉터리 기준
here = Path.cwd()
print(f"현재: {here}")
# 파일 검색 (glob)
# python_files = list(Path(".").rglob("*.py")) # 재귀 검색
# for f in python_files[:5]:
# print(f" {f}: {f.stat().st_size} bytes")
# 파일 읽기/쓰기 (한 줄)
# Path("output.txt").write_text("Hello!", encoding="utf-8")
# content = Path("output.txt").read_text(encoding="utf-8")
# 경로 존재 확인
p = Path("/tmp/test")
print(f"존재: {p.exists()}")
print(f"파일: {p.is_file()}")
print(f"디렉터리: {p.is_dir()}")
# 부모 순회
for parent in config.parents:
print(f" 상위: {parent}")pathlib.Path의 / 연산자는 os.path.join()보다 직관적이고 크로스 플랫폼 호환됩니다.
open()에 Path 객체를 전달할 수 있지만, 일부 오래된 라이브러리는 문자열만 받습니다. str(path)로 변환하세요.
05대용량 파일 처리
메모리를 효율적으로 사용하며 대용량 파일을 읽고 처리하는 기법입니다.
Python code
from pathlib import Path
import io
# 줄 단위 읽기 (메모리 효율적)
def count_lines(filepath: str) -> int:
count = 0
with open(filepath, "r", encoding="utf-8") as f:
for _ in f: # 한 줄씩 읽음
count += 1
return count
# 청크 단위 읽기 (바이너리)
def process_large_file(filepath: str, chunk_size: int = 8192):
total_bytes = 0
with open(filepath, "rb") as f:
while chunk := f.read(chunk_size):
total_bytes += len(chunk)
return total_bytes
# 제너레이터로 CSV 처리
def read_csv_lazy(filepath: str):
with open(filepath, "r", encoding="utf-8") as f:
header = next(f).strip().split(",")
for line in f:
values = line.strip().split(",")
yield dict(zip(header, values))
# 메모리 효율적 변환
def transform_file(src: str, dst: str):
with open(src, "r") as fin, open(dst, "w") as fout:
for line in fin:
fout.write(line.upper())
# StringIO: 메모리 내 파일 객체
buffer = io.StringIO()
buffer.write("Hello\nWorld\n")
buffer.seek(0)
for line in buffer:
print(f" 메모리 파일: {line.strip()}")
print("대용량 파일 처리 패턴 준비 완료")for line in file:은 한 줄씩 읽어 메모리 사용이 일정합니다. GB 단위 파일도 처리 가능합니다.
file.read()는 파일 전체를 메모리에 올립니다. 대용량 파일에서는 줄 단위나 청크 단위로 읽으세요.
06CSV/Excel 처리
csv 모듈과 openpyxl로 CSV와 Excel 파일을 다루는 방법입니다.
Python code
import csv
import io
# CSV 쓰기
output = io.StringIO()
writer = csv.writer(output)
writer.writerow(["이름", "나이", "도시"])
writer.writerows([
["Alice", 30, "서울"],
["Bob", 25, "부산"],
["Carol", 35, "대전"],
])
csv_content = output.getvalue()
print(csv_content)
# CSV 읽기
reader = csv.DictReader(io.StringIO(csv_content))
for row in reader:
print(f" {row['이름']} ({row['나이']}) - {row['도시']}")
# CSV 방언(dialect) 설정
csv.register_dialect("custom",
delimiter="|", quoting=csv.QUOTE_MINIMAL)
output2 = io.StringIO()
writer2 = csv.writer(output2, dialect="custom")
writer2.writerow(["a", "b", "c"])
print(f"커스텀: {output2.getvalue()}")
# 대용량 CSV 처리 (제너레이터)
def process_csv(file_obj):
reader = csv.DictReader(file_obj)
for row in reader:
yield {k: v.strip() for k, v in row.items()}
# openpyxl 패턴 (설치 필요)
# from openpyxl import Workbook
# wb = Workbook()
# ws = wb.active
# ws.append(["이름", "점수"])
# ws.append(["Alice", 95])
# wb.save("output.xlsx")
print("CSV 처리 완료")DictReader는 첫 행을 헤더로 사용하여 딕셔너리로 접근할 수 있어 편리합니다.
CSV에 한글이 포함되면 encoding="utf-8-sig"를 사용하세요. Excel에서 UTF-8 CSV를 열 때 BOM이 필요합니다.
07JSON/YAML 파싱
json 모듈로 JSON 데이터를 파싱하고 직렬화합니다. 커스텀 인코더/디코더도 다룹니다.
Python code
import json
from datetime import datetime, date
from dataclasses import dataclass, asdict
# 기본 직렬화/역직렬화
data = {"name": "Alice", "scores": [90, 85, 92], "active": True}
json_str = json.dumps(data, ensure_ascii=False, indent=2)
print(json_str)
parsed = json.loads(json_str)
print(parsed["name"])
# 커스텀 인코더
class CustomEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, (datetime, date)):
return obj.isoformat()
if isinstance(obj, set):
return list(obj)
return super().default(obj)
data = {
"created": datetime.now(),
"tags": {"python", "json"},
}
print(json.dumps(data, cls=CustomEncoder, ensure_ascii=False))
# dataclass → JSON
@dataclass
class User:
name: str
age: int
email: str
user = User("Alice", 30, "alice@test.com")
print(json.dumps(asdict(user), ensure_ascii=False))
# JSON 파일 읽기/쓰기
# with open("data.json", "w", encoding="utf-8") as f:
# json.dump(data, f, ensure_ascii=False, indent=2)
# with open("data.json", "r", encoding="utf-8") as f:
# loaded = json.load(f)ensure_ascii=False를 설정하면 한글이 이스케이프되지 않고 그대로 출력됩니다.
json.dumps()는 기본적으로 datetime, set 등을 직렬화할 수 없습니다. 커스텀 인코더가 필요합니다.
08SQLite 연동
sqlite3으로 경량 데이터베이스를 사용합니다. 별도 설치 없이 Python에 내장되어 있습니다.
Python code
import sqlite3
# 메모리 DB (테스트/데모용)
conn = sqlite3.connect(":memory:")
conn.row_factory = sqlite3.Row # dict-like 접근
cursor = conn.cursor()
# 테이블 생성
cursor.execute("""
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE,
age INTEGER
)
""")
# 데이터 삽입 (파라미터 바인딩)
users = [
("Alice", "alice@test.com", 30),
("Bob", "bob@test.com", 25),
("Carol", "carol@test.com", 35),
]
cursor.executemany(
"INSERT INTO users (name, email, age) VALUES (?, ?, ?)",
users
)
conn.commit()
# 조회
for row in cursor.execute("SELECT * FROM users WHERE age >= ?", (28,)):
print(f" {row['name']} ({row['age']}세) - {row['email']}")
# 집계
cursor.execute("SELECT AVG(age) as avg_age, COUNT(*) as cnt FROM users")
stats = cursor.fetchone()
print(f"평균 나이: {stats['avg_age']:.1f}, 총 {stats['cnt']}명")
# 컨텍스트 매니저로 안전하게
with conn:
conn.execute("UPDATE users SET age = ? WHERE name = ?", (31, "Alice"))
conn.close()row_factory = sqlite3.Row를 설정하면 컬럼명으로 값에 접근할 수 있습니다.
SQL 인젝션 방지를 위해 반드시 파라미터 바인딩(?)을 사용하세요. f-string으로 SQL을 조합하지 마세요.
09HTTP 요청 (requests/httpx)
HTTP 클라이언트로 외부 API를 호출하고 응답을 처리합니다.
Python code
# requests 라이브러리 패턴 시뮬레이션
from urllib.request import urlopen, Request
from urllib.error import URLError
import json
# 기본 GET 요청
def http_get(url: str, headers: dict = None) -> dict:
req = Request(url, headers=headers or {})
try:
with urlopen(req, timeout=10) as resp:
return {
"status": resp.status,
"body": resp.read().decode("utf-8"),
"headers": dict(resp.headers),
}
except URLError as e:
return {"error": str(e)}
# requests 스타일 API 시뮬레이션
class Response:
def __init__(self, status=200, data=None):
self.status_code = status
self._data = data or {}
def json(self):
return self._data
def raise_for_status(self):
if self.status_code >= 400:
raise Exception(f"HTTP {self.status_code}")
# 실사용 패턴 (requests 설치 필요)
# import requests
# resp = requests.get("https://api.example.com/users",
# params={"page": 1},
# headers={"Authorization": "Bearer TOKEN"},
# timeout=10,
# )
# resp.raise_for_status()
# users = resp.json()
# httpx (비동기 지원)
# import httpx
# async with httpx.AsyncClient() as client:
# resp = await client.get("https://api.example.com")
resp = Response(200, {"users": [{"name": "Alice"}]})
print(f"상태: {resp.status_code}")
print(f"데이터: {resp.json()}")항상 timeout을 설정하세요. 기본값이 없어 서버가 응답하지 않으면 영원히 대기합니다.
raise_for_status()를 호출하지 않으면 4xx/5xx 응답도 성공으로 처리됩니다.
10웹 스크래핑 (BeautifulSoup)
HTML 파싱과 데이터 추출의 기본 패턴입니다. BeautifulSoup의 주요 셀렉터를 다룹니다.
Python code
# BeautifulSoup 패턴 시뮬레이션
html = """
<html>
<body>
<h1 class="title">Python 뉴스</h1>
<ul id="articles">
<li class="article">
<a href="/post/1">첫 번째 글</a>
<span class="date">2024-01-15</span>
</li>
<li class="article">
<a href="/post/2">두 번째 글</a>
<span class="date">2024-01-16</span>
</li>
</ul>
</body>
</html>
"""
# 간단한 파서 (실제는 BeautifulSoup 사용)
import re
# 태그 내용 추출
titles = re.findall(r"<a[^>]*>(.*?)</a>", html)
dates = re.findall(r'class="date">(.*?)</span>', html)
links = re.findall(r'href="([^"]*)"', html)
for title, date, link in zip(titles, dates, links):
print(f" [{date}] {title} ({link})")
# BeautifulSoup 실사용 패턴
# from bs4 import BeautifulSoup
# soup = BeautifulSoup(html, "html.parser")
# for article in soup.select("li.article"):
# title = article.select_one("a").text
# link = article.select_one("a")["href"]
# date = article.select_one(".date").text
# print(f"[{date}] {title} ({link})")
print("웹 스크래핑 패턴 시연 완료")CSS 셀렉터(soup.select())가 find_all()보다 직관적입니다.
정규식으로 HTML을 파싱하면 중첩 태그, 속성 순서 등에서 오류가 발생합니다. 반드시 전용 파서를 사용하세요.
11파일 감시 (watchdog)
파일 시스템 변경을 감지하여 자동 작업을 수행하는 패턴입니다.
Python code
import os
import time
from pathlib import Path
# 간단한 폴링 기반 파일 감시
class SimpleWatcher:
def __init__(self, path: str):
self.path = Path(path)
self._snapshot: dict[str, float] = {}
def take_snapshot(self) -> dict[str, float]:
snapshot = {}
if self.path.is_dir():
for f in self.path.iterdir():
if f.is_file():
snapshot[str(f)] = f.stat().st_mtime
return snapshot
def check_changes(self) -> dict:
new_snapshot = self.take_snapshot()
changes = {"created": [], "modified": [], "deleted": []}
for path, mtime in new_snapshot.items():
if path not in self._snapshot:
changes["created"].append(path)
elif mtime != self._snapshot[path]:
changes["modified"].append(path)
for path in self._snapshot:
if path not in new_snapshot:
changes["deleted"].append(path)
self._snapshot = new_snapshot
return changes
# watchdog 라이브러리 패턴
# from watchdog.observers import Observer
# from watchdog.events import FileSystemEventHandler
#
# class MyHandler(FileSystemEventHandler):
# def on_modified(self, event):
# print(f"변경: {event.src_path}")
# def on_created(self, event):
# print(f"생성: {event.src_path}")
#
# observer = Observer()
# observer.schedule(MyHandler(), path=".", recursive=True)
# observer.start()
watcher = SimpleWatcher("/tmp")
snapshot = watcher.take_snapshot()
print(f"감시 중: {len(snapshot)}개 파일")
print("파일 감시 시스템 준비 완료")실제 프로젝트에서는 pip install watchdog을 사용하세요. OS 레벨 이벤트를 사용하여 폴링보다 효율적입니다.
폴링 기반 감시는 CPU를 소비합니다. 간격을 너무 짧게 설정하면 성능에 영향을 줍니다.
12압축/아카이브
Python으로 ZIP, GZIP 등 압축 파일을 생성하고 해제합니다.
Python code
import zipfile
import gzip
import io
# ZIP 파일 생성 (메모리 내)
buffer = io.BytesIO()
with zipfile.ZipFile(buffer, "w", zipfile.ZIP_DEFLATED) as zf:
zf.writestr("hello.txt", "안녕하세요!")
zf.writestr("data/config.json", '{"key": "value"}')
print(f"ZIP 크기: {buffer.tell()} bytes")
# ZIP 파일 읽기
buffer.seek(0)
with zipfile.ZipFile(buffer, "r") as zf:
print(f"파일 목록: {zf.namelist()}")
for name in zf.namelist():
info = zf.getinfo(name)
content = zf.read(name).decode("utf-8")
print(f" {name} ({info.compress_size}b): {content[:30]}")
# GZIP 압축
original = "Hello, World! " * 100
compressed = gzip.compress(original.encode("utf-8"))
decompressed = gzip.decompress(compressed).decode("utf-8")
print(f"\n원본: {len(original)} bytes")
print(f"압축: {len(compressed)} bytes")
print(f"비율: {len(compressed)/len(original)*100:.1f}%")
print(f"복원 일치: {original == decompressed}")
# tarfile 패턴
# import tarfile
# with tarfile.open("archive.tar.gz", "w:gz") as tar:
# tar.add("src/", arcname="source")zipfile.ZipFile은 컨텍스트 매니저로 사용하면 자동으로 닫힙니다.
ZIP 파일에서 경로 탈출(path traversal) 공격에 주의하세요. extractall() 전에 파일 경로를 검증하세요.
13임시 파일
tempfile로 안전한 임시 파일과 디렉터리를 생성하고 자동 정리합니다.
Python code
import tempfile
import os
# 임시 파일 (자동 삭제)
with tempfile.NamedTemporaryFile(
mode="w", suffix=".txt", prefix="myapp_",
delete=True, encoding="utf-8"
) as tmp:
tmp.write("임시 데이터")
tmp.flush()
print(f"임시 파일: {tmp.name}")
print(f"존재: {os.path.exists(tmp.name)}")
# 블록 종료 후 자동 삭제
# 임시 디렉터리 (자동 삭제)
with tempfile.TemporaryDirectory(prefix="myapp_") as tmpdir:
print(f"임시 디렉터리: {tmpdir}")
# 디렉터리 안에 파일 생성
filepath = os.path.join(tmpdir, "data.txt")
with open(filepath, "w") as f:
f.write("테스트 데이터")
print(f"파일 존재: {os.path.exists(filepath)}")
# 블록 종료 후 디렉터리와 내용물 삭제
# SpooledTemporaryFile: 크기 초과 시 디스크로
with tempfile.SpooledTemporaryFile(
max_size=1024, mode="w+", encoding="utf-8"
) as tmp:
tmp.write("작은 데이터 → 메모리에 유지")
tmp.seek(0)
print(f"내용: {tmp.read()}")
# 안전한 임시 경로 생성
print(f"임시 디렉터리 경로: {tempfile.gettempdir()}")
print(f"고유 파일명: {tempfile.mktemp(suffix='.dat')}")TemporaryDirectory는 테스트에서 격리된 파일 시스템 환경을 만들 때 유용합니다.
NamedTemporaryFile(delete=True)는 Windows에서 다른 프로세스가 파일에 접근하지 못할 수 있습니다. delete=False로 생성하고 수동 삭제하세요.
정리하며
- 텍스트 파일은 항상
encoding="utf-8"을 명시해 플랫폼 기본값 의존을 끊습니다. - CSV를 열 때
newline=""을 지정해 Windows의 빈 줄 삽입을 막습니다. - 대용량 파일은
read()대신 파일 객체를 순회해 한 줄씩 처리합니다. sqlite3는commit()이나with블록 없이는 변경이 저장되지 않습니다.
더 깊이 들어가고 싶다면 Python 학습 라이브러리에서 다른 주제 가이드를 이어서 보거나, 언어 비교에서 같은 개념이 다른 언어에서 어떻게 표현되는지 확인해 보세요.