지난번 글에서 파이썬(Python) 심화적인 내용을 다루기 시작했다. 함수의 개념부터 패키지, 라이브러리, 모듈을 비롯해 객체 지향의 개념까지 다루면서 학습해 보았는데 이번 글에서는 그에 이어서 파이썬의 심화적인 내용을 더 정리하며 학습해 볼 예정이다. 파일 입출력 기능부터 함수형 프로그래밍, 동시성과 병렬 처리 등에 대한 내용들에 대해 정리하며 학습해 보자.
✅ 파일 입출력 (File I/O)
1️⃣ 파일 열기와 닫기
file = open("example.txt", "r") # 파일 열기
data = file.read() # 내용 읽기
file.close() # 파일 닫기
- "r": 읽기 / "w": 쓰기(덮어쓰기) / "a": 추가
- "b": 바이너리 / "t": 텍스트 모드 (기본)
- "x": 존재하지 않을 때만 새로 생성
2️⃣ with 구문 (컨텍스트 매니저)
with open("example.txt", "r") as file:
data = file.read()
→ 파일을 자동으로 닫아주는 안전한 방법으로 만약 예외가 발생해도 close()가 호출된다.
3️⃣ 읽기 메서드
메서드 | 설명 |
read() | 파일 전체 읽기 |
readline() | 한 줄씩 읽기 |
readlines() | 모든 줄을 리스트로 읽기 |
4️⃣ 쓰기 메서드
메서드 | 설명 |
write("내용") | 문자열 1개 쓰기 |
writelines([리스트]) | 줄 리스트 쓰기 (줄바꿈 직접 넣어야 함) |
5️⃣ 바이너리 파일
바이너리 파일은 이미지, 오디오, 영상, 압축 파일 등을 의미한다. → 텍스트가 아니라 0과 1의 이진 데이터
with open("image.jpg", "rb") as file:
binary = file.read()
- 'rb': 바이너리 읽기
- 'wb': 바이너리 쓰기
- 인코딩 지정 ❌ (바이너리는 인코딩을 사용하지 않는다.)
👉 바이너리 암호화 예시 (XOR)
def xor_encrypt(data: bytes, key: int) -> bytes:
return bytes([b ^ key for b in data])
with open("secret.jpg", "rb") as f:
content = f.read()
encrypted = xor_encrypt(content, key=123)
with open("encrypted.jpg", "wb") as f:
f.write(encrypted)
- 비트 단위 연산으로 암호화
- ^는 XOR 연산자 (같으면 0, 다르면 1)
6️⃣ CSV 파일 다루기
CSV: 엑셀처럼 줄과 칸이 있는 쉼표로 구분된 텍스트 파일
import csv
with open("data.csv", "r", newline='') as f:
reader = csv.reader(f)
for row in reader:
print(row)
- newline='': 윈도우에서 줄 바꿈 문제 방지
- csv.DictReader를 쓰면 행을 딕셔너리로 읽을 수 있음
👉 CSV 파일 쓰기
with open("output.csv", "w", newline='') as f:
writer = csv.writer(f)
writer.writerow(["이름", "나이"])
writer.writerow(["~~", 24])
7️⃣ 파일 시스템 다루기 (os, pathlib, shutil)
- os.remove("파일명"): 파일 삭제
- os.rename("old.txt", "new.txt"): 이름 바꾸기
- shutil.copy(), move() 등으로 파일 복사/이동
- pathlib.Path("폴더").exists()로 존재 여부 확인
⚙️ 요약 정리
기능 | 주요 함수 |
파일 열기/닫기 | open(), close(), with |
읽기 | read(), readline(), readlines() |
쓰기 | write(), writelines() |
바이너리 처리 | rb, wb, XOR, base64 |
CSV 다루기 | csv.reader, csv.DictReader, writer |
시스템 파일 조작 | os, pathlib, shutil |
✅ 예외 처리란?
프로그램 실행 중에 발생하는 예상치 못한 상황(예외)을비정상 종료 없이 우아하게 처리하는 방법
1️⃣ 에러 vs 예외
개념 | 설명 | 예시 |
Error | 문법적 문제, 실행 불가 | SyntaxError |
Exception | 런타임 중 발생, 처리 가능 | ZeroDivisionError, FileNotFoundError |
2️⃣ 기본 문법 (try ~ except)
try:
# 예외가 발생할 수 있는 코드
result = 10 / 0
except ZeroDivisionError:
print("0으로 나눌 수 없습니다.")
결과는 아래 예외 부분의 결과가 출력되는 것을 확인할 수 있다.
3️⃣ 전체 구조
try:
# 시도할 코드
except 예외1:
# 예외1 처리
except 예외2 as e:
print(f"에러 내용: {e}")
else:
# 예외가 발생하지 않았을 때 실행
finally:
# 예외 여부와 관계없이 항상 실행
4️⃣ 대표적인 예외 종류
예외 클래스 | 설명 |
ZeroDivisionError | 0으로 나눌 때 |
ValueError | 부적절한 값 |
TypeError | 타입이 안 맞을 때 |
IndexError | 리스트 범위 벗어남 |
KeyError | 딕셔너리에 없는 키 접근 |
FileNotFoundError | 파일 없음 |
PermissionError | 접근 권한 없음 |
5️⃣ 사용자 정의 예외 만들기
class TooYoungError(Exception):
pass
def vote(age):
if age < 18:
raise TooYoungError("너무 어려서 투표할 수 없습니다!")
try:
vote(15)
except TooYoungError as e:
print("예외 발생:", e)
6️⃣ 예외 체이닝 (원인까지 추적)
try:
raise ValueError("잘못된 값!")
except ValueError as e:
raise RuntimeError("실행 중 오류 발생") from e
→ 예외의 원인(e)을 그대로 연결해서 던짐
7️⃣ assert 문 (디버깅용 체크)
assert 2 + 2 == 4 # OK
assert 1 + 1 == 3, "❌" # AssertionError 발생
8️⃣ 로깅 활용 (logging 모듈)
import logging
logging.basicConfig(level=logging.ERROR)
try:
1 / 0
except ZeroDivisionError:
logging.exception("0으로 나누기 발생!")
→ print 대신 logging으로 예외를 로그에 남길 수 있다. 로그를 기록할 수 있기에 유용하게 사용할 수 있다.
⚙️ 요약정리
키워드 | 설명 |
try ~ except | 예외 잡기 |
else | 예외 없을 때 실행 |
finally | 항상 실행 |
raise | 예외 발생시키기 |
assert | 디버깅용 조건 검사 |
logging | 로그로 예외 기록 |
✅ 이터레이터 & 제너레이터
1️⃣ 이터러블(Iterable)이란?
for 문에서 사용할 수 있는 모든 객체를 말한다. 예를 들어, list, tuple, str, dict, set, file, range 등
특징:
- for x in ~ 구문에서 반복 가능
- __iter__() 메서드를 갖고 있음
2️⃣ 이터레이터(Iterator)란?
다음 값을 하나씩 꺼낼 수 있는 객체이다. 말로만 들으면 이해가 어렵기에 예시 코드를 보면서 이해하는 것이 좋을 것 같다.
특징:
- __iter__()와 __next__()를 모두 구현
- next() 함수로 다음 값을 가져옴
- 한 번 쓰고 나면 다시 사용하지 못함
예시:
nums = iter([1, 2, 3])
print(next(nums)) # 1
print(next(nums)) # 2
print(next(nums)) # 3
3️⃣ 제너레이터(Generator)란?
yield를 사용해서 값을 하나씩 생성하는 역할을 하며, 이터레이터를 더 쉽게 만드는 방법으로 사용된다.
yield vs return
키워드 | 차이점 |
return | 함수 종료 & 값 반환 |
yield | 함수 상태 저장 & 값 "하나만" 반환, 중단 후 재개 가능 |
예시:
def countdown(n):
while n > 0:
yield n
n -= 1
for i in countdown(3):
print(i)
출력:
3
2
1
4️⃣ 제너레이터 표현식
- 리스트 컴프리헨션과 비슷하지만 [] 대신 () 사용
- 메모리 효율이 좋다는 장점이 있음 (지연 평가, lazy evaluation)
gen = (x*x for x in range(1000000))
print(next(gen)) # 0
print(next(gen)) # 1
5️⃣ 지연 계산 (Lazy Evaluation)
- 필요할 때마다 계산
- 전체 리스트를 메모리에 담지 않음
- 무한 시퀀스도 처리 가능
예시:
def infinite_counter():
i = 0
while True:
yield i
i += 1
6️⃣ yield from (제너레이터 위임)
def gen1():
yield from [1, 2, 3]
for i in gen1():
print(i)
→ 다른 이터러블을 한 줄로 yield 해준다.
⚙️ 요약 정리
개념 | 설명 |
이터러블 | for로 순회 가능한 객체 |
이터레이터 | __next__()로 하나씩 꺼냄 |
제너레이터 | yield로 이터레이터를 쉽게 구현 |
제너레이터 표현식 | (x for x in ...) 형태 |
지연 평가 | 필요한 순간에 계산 (메모리 효율적) |
✅ 동시성과 병렬 처리 정리
❓ 동시성과 병렬 처리란?
📌 개념 구분
구분 | 설명 |
동시성 (Concurrency) | 한 코어에서 여러 작업을 번갈아 실행 |
병렬성 (Parallelism) | 여러 코어에서 작업을 동시에 실행 |
→ 동시성은 논리적 동시에 실행, 병렬성은 물리적으로 동시에 실행
1️⃣ 왜 필요한가?
- 성능 향상: 전체 실행 시간 단축
- 응답성 향상: 사용자 인터페이스가 멈추지 않음
- 자원 활용도 증가: 멀티코어 CPU 효율적 활용
2️⃣ 주요 방식 비교
방식 | 설명 | 활용 예시 |
threading | 프로세스 내에서 여러 스레드 실행 | 다운로드, 파일 입출력 |
multiprocessing | 여러 프로세스를 병렬 실행 | 대규모 연산, 머신러닝 학습 |
asyncio | 코루틴 기반 이벤트 루프 처리 | 웹 크롤링, API 호출 병렬 |
3️⃣ GIL (Global Interpreter Lock)
파이썬 인터프리터가 한 번에 하나의 스레드만 실행하도록 강제하는 잠금(lock)이다. 즉, 쉽게 말해서 동시에 여러 스레드가 python 코드를 실행할 수 없다는 의미이다.
- 파이썬 인터프리터가 한 번에 하나의 스레드만 실행
- 결과적으로 threading은 CPU-bound 작업에 비효율적
- multiprocessing은 GIL을 피할 수 있음
import threading
def count():
for _ in range(10**7):
pass
t1 = threading.Thread(target=count)
t2 = threading.Thread(target=count)
t1.start()
t2.start()
t1.join()
t2.join()
→ 예시 코드를 보면 두 스레드가 동시에 실행된 것 같이 보이지만, GIL의 영향으로 Python 코드는 한 번에 한 번만 실행된다. 로그를 찍어서 확인해 보면 하나의 스레드가 종료되고 다음 스레드가 실행되는 것을 확인할 수 있을 것이다.
4️⃣ Threading
기본 구조
import threading
def worker():
print("작업 실행")
t = threading.Thread(target=worker)
t.start()
t.join()
주요 속성/메서드
속성/메서드 | 설명 |
start() | 스레드 시작 |
join() | 스레드 종료까지 대기 |
is_alive() | 실행 중인지 확인 |
daemon=True | 데몬 스레드로 설정 |
✔ 스레드 간 통신: Event
event = threading.Event()
event.set() # 신호 보내기
event.wait() # 신호 대기
event.clear() # 신호 해제
✔ 조건 동기화: Condition
- wait(), notify(), notify_all() 사용
- 내부적으로 Lock 사용
✔ Lock으로 Race Condition 방지
lock = threading.Lock()
lock.acquire()
# 공유 데이터 처리
lock.release()
✔ Queue 활용 (스레드 안전)
from queue import Queue
q = Queue()
q.put("작업")
q.get()
- 내부적으로 Lock 사용 → 동기화 안전함
- task_done() + join() 조합 가능
5️⃣ ThreadPoolExecutor
concurrent.futures 모듈을 제공하며, 스레드 풀을 통해 작업을 스케줄링한다.
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=3) as executor:
futures = executor.map(func, iterable)
6️⃣ multiprocessing
다중 프로세스를 이용한 병렬처리
기본 구조
from multiprocessing import Process
p = Process(target=func)
p.start()
p.join()
프로세스 간 공유 자원
- Queue, Pipe, Manager 등 이용
Pool 사용
from multiprocessing import Pool
with Pool(processes=4) as pool:
result = pool.map(func, iterable)
- apply(): 동기 실행
- apply_async(): 비동기 실행
- map_async(): 비동기 여러 작업
7️⃣ Asyncio
파이썬의 비동기 처리를 위한 내장 프레임워크로 싱글 스레드 환경에서도 수천 개의 작업을 효율적으로 처리할 수 있다.
핵심 요소 3가지
개념 | 설명 |
async def | 코루틴 함수를 정의하는 키워드 |
await | 비동기 함수가 끝날 때까지 다른 작업을 하며 대기 |
이벤트 루프 | 비동기 작업을 스케줄링하고 실행하는 컨트롤 타워 |
코루틴(Coroutine) 이란?
async def로 정의돈 함수로, 일반 함수처럼 한 번 실행되고 끝나는 것이 아니라 중간에 멈췄다가(await) 다시 재개할 수 있는 함수를 말한다.
import asyncio
async def say_hello():
print("안녕")
await asyncio.sleep(1)
print("다시 안녕")
asyncio.run(say_hello())
await란?
await는 '이 작업이 끝날 동안 기다릴 거야. 그러니까 그동안 다른 일 좀 하고 있을게'라는 뜻으로 생각하면 된다.
await asyncio.sleep(1)
→ 해당 코드는 1초를 기다리지만, 그 1초 동안 CPU는 노는 것이 아니라 다른 코루틴을 실행할 수 있게 된다.
여러 작업을 병렬처럼 처리하려면?
import asyncio
async def task(name):
print(f"{name} 시작")
await asyncio.sleep(1)
print(f"{name} 끝")
async def main():
await asyncio.gather(task("A"), task("B"), task("C"))
asyncio.run(main())
실행 결과:
A 시작
B 시작
C 시작
A 끝
B 끝
C 끝
→ 순차적으로 3초가 아닌, 동시에 시작되어 1초면 끝나는 것을 볼 수 있다.
이벤트 루프(Event Loop)
이벤트 루프(Event Loop)는 모든 비동기 작업을 스케줄링하고 실행하는 중추 신경계라고 말할 수 있다.
- asyncio.run()이 이벤트 루프를 실행
- 각 코루틴을 실행하고, await가 있는 부분마다 다른 코루틴으로 전환
asyncio가 좋은 경우
상황 | 설명 |
웹 크롤링 | 수천 개의 페이지에 동시에 요청 가능 |
API 호출 | 병렬 API 요청, 응답 대기 중 다른 작업 |
채팅/게임 서버 | 클라이언트 수천 명을 한 스레드로 처리 |
파일 다운로드 | 여러 개 파일 동시에 저장 |
threading/multiprocessing과 비교
항목 | asyncio | threading | multiprocessing |
동시성 | ✅ | ✅ | ❌ |
병렬성 | ❌ (싱글 스레드) | ❌ (GIL 영향) | ✅ (멀티코어) |
자원 소비 | 적음 | 중간 | 큼 |
적합한 작업 | I/O-bound | I/O-bound | CPU-bound |
⚙️ 요약정리
키워드 | 설명 |
async def | 코루틴 정의 |
await | 작업 대기 + 다른 코루틴 실행 |
asyncio.run() | 이벤트 루프 실행 |
gather() | 여러 코루틴 병렬 실행 |
적합한 작업 | API, 웹 크롤링, 소켓 통신 등 I/O-bound |
해당 글에서 파일 입출력부터 예외 처리, 함수형 프로그래밍, 이터레이터와 제너레이터, 동시성과 병렬처리의 개념들을 정리하며 학습했다. 이어지는 예제 코드 글에서 코드를 분석하면서 자세히 다룰 예정이지만 기본적인 개념을 간단하게라도 이해하고 넘어가는 것이 중요하다고 생각되어 간단하게 정리해 보았다.
파이썬 내용을 학습하다 보니 잠깐 헷갈렸던 개념이 있어 정리하기 위해 이해하며 노트에 정리해 보았다.
데코레이터에 대한 내용이 갑자기 헷갈려서 어떤 기능이고, 어떻게 사용하는 것인지를 다시 되짚어 보았다.
데코레이터는 기존 함수를 변경하지 않고, 기능을 확장하거나 감싸서 동작을 추가하는 함수라고 생각하면 된다.
코드에 데코레이터를 사용해 본 일이 많지는 않지만 함수를 사용할 때 하나의 방법으로 고민하며 사용해 보면서 익숙해져야겠다고 생각된다.
본 후기는 [카카오엔터프라이즈x스나이퍼팩토리] 카카오클라우드로 배우는 AIaaS 마스터 클래스 (B-log) 리뷰로 작성 되었습니다.