본문 바로가기

클라우드(Cloud)

[스나이퍼팩토리] 카카오클라우드 AIaaS 마스터 클래스 9주차 - 파이썬 심화 2 (Python Advanced 2)

 

 

 지난번 글에서 파이썬(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 비동기 함수가 끝날 때까지 다른 작업을 하며 대기
이벤트 루프 비동기 작업을 스케줄링하고 실행하는 컨트롤 타워
 
 
 Asyncio를 공부하다 보면 코루틴 함수를 기반으로 한다는 내용이 있다. 이때, 코루틴이 무엇인가가 궁금할텐데 아래 내용을 살펴보면 된다. 
 

코루틴(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) 리뷰로 작성 되었습니다.