오픈소스기반데이터분석 2학기 2학년

방송대 오픈소스기반데이터분석 3강: 안전한 데이터 처리와 함수형 도구

오픈소스기반데이터분석 3강 - 데이터 분석을 위한 파이썬 프로그래밍 (2)


방송대 오픈소스기반데이터분석 3강: 안전한 데이터 처리와 함수형 도구

여러 값이 묶인 데이터에서 필요한 항목을 꺼내고, 잘못된 입력이나 파일 오류에도 작업을 안전하게 끝내며, 반복되는 변환·선별·집계를 간결하게 표현하려면 어떤 문법을 골라야 할까? 언패킹과 예외 처리에서 출발해 람다, map, filter, reduce를 하나의 데이터 처리 흐름으로 연결한다.

한 건의 데이터를 처리하는 네 가지 질문에서 출발한다

데이터 분석 코드는 대개 비슷한 문제를 반복해서 만난다. 한 레코드에서 필요한 값을 어떻게 꺼낼지, 쓰지 않을 값은 어떻게 표시할지, 입력이 잘못됐을 때 어디서 복구할지, 여러 레코드를 어떤 규칙으로 변환하고 모을지를 결정해야 한다. 3강의 문법 요소는 이 네 질문에 각각 답한다.

처리 질문주요 도구결과대표 위험
묶음의 각 값을 어떻게 꺼내는가?언패킹, 확장 언패킹여러 변수 또는 나머지 리스트요소 수와 변수 수 불일치
쓰지 않는 값은 어떻게 드러내는가?언더스코어 _의도적 무시를 표현실제 값을 삭제한다고 오해
실패해도 어떻게 계속하거나 정리하는가?try, except, finally예외별 대응과 정리예외를 지나치게 넓게 잡음
여러 요소를 어떻게 가공하는가?map, filter, reduce변환, 선별, 하나의 값으로 축약도구의 역할 혼동

선택 지도: 구조를 분해하면 언패킹, 실패 경로를 관리하면 예외 처리, 한 요소를 다른 값으로 바꾸면 map, 조건에 맞는 요소만 남기면 filter, 여러 요소를 하나로 모으면 reduce를 먼저 떠올린다.

언패킹은 위치에 의미를 부여하는 동시 할당이다

언패킹(unpacking)은 리스트, 튜플, 문자열 같은 반복 가능한 객체의 여러 요소를 각 변수에 한 번에 할당하는 기능이다. 오른쪽 요소의 순서가 왼쪽 변수의 순서와 대응한다.

record = ("sensor-A", 23.5, "C")
sensor_id, value, unit = record

print(sensor_id)
print(value)
print(unit)

출력은 차례로 sensor-A, 23.5, C다. 인덱스 record[0], record[1], record[2]를 반복하는 것보다 각 위치의 뜻이 변수명으로 드러난다. 문자열도 시퀀스이므로 first, second, third = "ABC"처럼 문자 단위로 나눌 수 있다.

기본 언패킹에서는 변수 수와 요소 수가 정확히 같아야 한다. 다음 코드는 받을 변수가 두 개인데 요소가 세 개이므로 ValueError가 발생한다.

x, y = [10, 20, 30]

언패킹은 “내용을 검색해 이름에 맞춰 넣는 기능”이 아니다. 변수명과 원소의 이름은 무관하며 오직 위치에 따라 배정된다. 따라서 레코드 열 순서가 바뀌면 문법 오류 없이 잘못된 의미가 들어갈 수도 있으므로 데이터 구조를 먼저 확인해야 한다.

별표 하나는 길이가 달라지는 중간 구간을 흡수한다

확장 언패킹에서는 변수 앞의 별표 *가 남는 요소들을 리스트로 모은다. 별표 변수는 한 언패킹 식에서 하나만 사용할 수 있으며, 앞·중간·뒤 어느 위치에도 놓을 수 있다.

daily_sales = [120, 135, 128, 142, 160]
first, *middle, last = daily_sales

print(first)   # 120
print(middle)  # [135, 128, 142]
print(last)    # 160

이 구조는 데이터 길이가 달라져도 첫 값과 마지막 값의 역할이 고정되고 중간 값의 수만 변할 때 유용하다. 단, 최소한 first와 last에 들어갈 두 요소는 필요하다. 입력이 [120]처럼 하나뿐이면 남은 요소를 어떻게 모을지와 관계없이 필수 변수 수를 채우지 못해 오류가 난다.

별표가 붙은 변수는 남는 값이 하나도 없어도 리스트를 받는다. 예를 들어 first, *middle, last = [120, 160]이면 middle은 빈 리스트 []다. “항상 중간 값이 한 개 이상 있다”는 가정은 잘못이다.

개수 검산: 일반 변수의 개수를 먼저 센다. 입력 요소가 그보다 적으면 실패하고, 같으면 별표 변수는 빈 리스트, 많으면 초과분을 모두 리스트로 받는다.

언더스코어는 값을 없애지 않고 관심 없음을 표시한다

파이썬에서 언더스코어 _는 특별한 삭제 연산자가 아니라 사용할 필요가 없는 값을 받는 관례적 변수명이다. 반복 횟수만 중요하고 반복 변수의 실제 값은 쓰지 않을 때 다음처럼 표현한다.

for _ in range(3):
    print("데이터 확인")

세 번 모두 문장이 출력된다. range(3)이 만드는 0, 1, 2가 사라진 것은 아니며 매 반복마다 _에 할당되지만 코드에서 다시 읽지 않을 뿐이다.

레코드 언패킹에서도 필요한 필드만 강조할 수 있다. 다음 학습용 데이터는 학번, 이름, 학과, 학년, 평점 순서다.

student = (20261234, "김하늘", "컴퓨터과학과", 2, 3.9)
_, name, _, _, grade = student
print(name, grade)

결과는 김하늘 3.9다. 같은 _에 여러 값이 차례로 들어가므로 마지막 값만 남지만, 애초에 읽지 않겠다는 의도를 표현한 코드다. 무시할 값이 연속된다면 student_id, name, *_, grade = student처럼 확장 언패킹을 결합할 수도 있다.

예외 처리는 실패를 숨기는 문법이 아니라 실패 경로를 설계하는 일이다

예외(exception)는 실행 중 정상 흐름을 계속할 수 없을 때 만들어지는 오류 객체다. 사용자가 숫자 대신 문자를 입력하면 ValueError, 0으로 나누면 ZeroDivisionError가 발생한다. 처리하지 않은 예외는 현재 실행 흐름을 중단시킬 수 있다.

try:
    number = int(input("0이 아닌 정수: "))
    result = 10 / number
except ValueError:
    print("정수 형식으로 입력하세요.")
except ZeroDivisionError:
    print("0으로 나눌 수 없습니다.")
else:
    print(result)

try에는 실패할 가능성이 있는 최소 범위의 코드를 둔다. 첫 변환에서 실패하면 ValueError 처리로, 나눗셈에서 실패하면 ZeroDivisionError 처리로 이동한다. 아무 예외도 없을 때만 else가 실행되므로 성공 결과 출력과 예외 가능 코드를 분리할 수 있다.

except Exception:처럼 모든 일반 예외를 한꺼번에 잡으면 프로그램은 멈추지 않을 수 있지만, 변수명 오타나 논리 결함까지 사용자의 입력 문제로 오인할 수 있다. 예상 가능한 예외를 구체적으로 잡고, 원인을 설명한 뒤 복구·재시도·기록·중단 중 하나를 선택해야 한다.

블록실행 조건주요 역할
try항상 먼저 실행예외 가능성이 있는 작업 시도
except지정한 예외가 발생오류별 복구 또는 안내
else예외가 발생하지 않음성공한 경우의 후속 처리
finally예외 발생 여부와 무관반드시 필요한 정리 작업

finally는 항상 실행되므로 정리할 대상의 존재부터 보장해야 한다

finally는 파일 닫기나 연결 해제처럼 성공·실패와 관계없이 수행해야 할 정리에 알맞다. 그러나 “항상 실행된다”는 성질 때문에 오히려 새 예외를 만들 수도 있다. 다음 패턴에서 open()이 실패하면 file은 할당되지 않는다. 그런데 finally의 file.close()는 무조건 실행되므로 정의되지 않은 변수를 참조하는 문제가 생긴다.

try:
    file = open("data.csv", "r", encoding="utf-8")
    content = file.read()
except FileNotFoundError:
    print("파일을 찾을 수 없습니다.")
finally:
    file.close()  # open()이 실패했다면 안전하지 않다.

파일처럼 컨텍스트 관리자를 지원하는 자원은 with 문이 더 안전하고 간결하다. 파일이 정상적으로 열렸을 때만 블록에 들어가며, 블록을 벗어날 때 자동으로 닫는다.

try:
    with open("data.csv", "r", encoding="utf-8") as file:
        content = file.read()
except FileNotFoundError:
    print("파일을 찾을 수 없습니다.")
else:
    print(content)

finally가 불필요하다는 뜻은 아니다. 데이터베이스 연결, 잠금 해제, 임시 상태 복구처럼 성공 여부와 관계없이 수행할 정리 로직에 계속 필요하다. 핵심은 정리 대상이 실제로 만들어졌는지 보장하는 것이다.

오류 교정 원칙: 원래 예외를 처리하는 과정에서 또 다른 예외가 발생하면 최초 원인이 가려진다. 자원 획득과 정리의 짝을 확인하고, 가능하면 with 같은 컨텍스트 관리자를 우선 사용한다.

함수형 프로그래밍은 데이터 변경과 외부 영향을 줄이는 관점이다

파이썬은 절차적·객체지향 방식과 함께 함수형 프로그래밍의 여러 도구를 지원한다. 함수형 관점에서는 계산을 함수의 결합으로 표현하고, 기존 데이터를 직접 바꾸기보다 새로운 값을 만들어 내며, 외부 상태에 미치는 영향을 줄이려 한다.

순수 함수(pure function)는 같은 입력에 항상 같은 출력을 내고, 함수 밖의 상태를 변경하지 않는 함수다. 다음 함수는 입력값만으로 결과를 계산하므로 순수 함수로 이해할 수 있다.

def celsius_to_fahrenheit(celsius):
    return celsius * 9 / 5 + 32

반면 함수 안에서 전역 목록을 바꾸거나 파일에 기록하고 화면에 출력하는 행위는 계산 결과 외에 외부 세계를 변화시키는 부작용(side effect)이다. 부작용이 언제나 나쁜 것은 아니다. 결과 저장과 사용자 안내에는 필요하지만, 계산과 부작용을 분리하면 테스트와 디버깅이 쉬워진다.

순수성 점검: 같은 인수를 두 번 넣었을 때 결과가 같은가? 함수 밖의 변수를 읽거나 바꾸는가? 파일·화면·네트워크에 영향을 주는가? 첫 질문은 ‘예’, 뒤 두 질문은 ‘아니오’일수록 순수 함수에 가깝다.

람다는 짧은 함수를 값처럼 전달할 때 빛난다

람다 함수(lambda function)는 이름 없이 한 표현식으로 만드는 함수다. 표현식의 결과가 자동으로 반환되므로 return을 쓰지 않는다.

add_two = lambda x: x + 2
print(add_two(3))  # 5

이 예제는 일반 함수 def add_two(x): return x + 2와 같은 계산을 한다. 하지만 람다의 장점은 이름을 붙여 재사용하는 데 있기보다 map, filter, 정렬 함수처럼 “함수를 인수로 받는 함수”에 짧은 규칙을 바로 전달할 때 드러난다.

employees = {"이지혜": 300, "구민준": 500, "방서연": 410}
adjusted = {
    name: (lambda salary: salary * 1.1)(salary)
    for name, salary in employees.items()
}

다만 이렇게 람다를 즉시 호출하면 읽기 어려울 수 있다. 복잡한 조건, 여러 단계의 계산, 예외 처리, 반복 사용, 설명용 이름이 필요하다면 def로 일반 함수를 만드는 편이 낫다. “짧다”보다 “한눈에 의미가 보인다”가 선택 기준이다.

map과 filter는 출력의 의미가 서로 다르다

map(function, iterable)은 모든 요소에 함수를 적용해 각각의 결과를 만든다. 입력이 5개라면 보통 출력도 5개다. filter(predicate, iterable)는 조건 함수가 참을 반환한 원래 요소만 남기므로 출력 개수는 같거나 줄어든다.

temperatures = [18.0, 21.5, 27.0, 30.0]

fahrenheit = list(map(lambda c: c * 9 / 5 + 32, temperatures))
hot_celsius = list(filter(lambda c: c >= 25, temperatures))

print(fahrenheit)
print(hot_celsius)

첫 결과는 [64.4, 70.7, 80.6, 86.0], 둘째 결과는 [27.0, 30.0]이다. map은 섭씨 값을 화씨 값으로 바꿨고, filter는 섭씨라는 단위와 값 자체를 유지한 채 기준에 맞는 항목만 골랐다.

파이썬 3에서 두 함수는 결과 목록을 즉시 만드는 대신 필요할 때 값을 내는 이터레이터를 반환한다. 결과를 여러 번 확인하거나 목록 연산에 쓰려면 예제처럼 list()로 구체화한다. 간단한 변환·선별은 리스트 컴프리헨션이 더 읽기 쉬울 수도 있다.

질문mapfilter
핵심 동작각 요소를 새 값으로 변환조건에 맞는 원래 요소를 선별
전달 함수의 반환값새 요소로 사용할 값포함 여부를 정하는 참/거짓
출력 개수일반적으로 입력과 같음입력과 같거나 적음
예단위 변환, 문자열 정리합격자, 임계값 이상 관측치

reduce는 중간 결과를 다음 요소와 결합해 하나로 축약한다

reduce는 functools 모듈에서 가져온다. 이항 함수와 반복 가능한 객체를 받아 앞에서부터 누적한다. [1, 2, 3, 4, 5]의 합을 구하는 과정은 다음과 같다.

  1. 첫 두 요소를 결합한다: 1 + 2 = 3
  2. 중간 결과와 다음 요소를 결합한다: 3 + 3 = 6
  3. 다시 결합한다: 6 + 4 = 10
  4. 마지막 요소를 결합한다: 10 + 5 = 15
from functools import reduce

numbers = [1, 2, 3, 4, 5]
total = reduce(lambda x, y: x + y, numbers)
print(total)  # 15

합계에는 내장 함수 sum(numbers)가 더 명확하다. reduce는 전용 내장 함수가 없는 누적 결합 규칙을 표현할 때 의미가 있다. 빈 반복 객체를 초기값 없이 축약하면 첫 중간값을 만들 수 없어 오류가 발생하므로, 데이터가 비어 있을 수 있다면 reduce(function, iterable, initializer)의 초기값을 제공한다.

empty = []
safe_total = reduce(lambda x, y: x + y, empty, 0)
print(safe_total)  # 0

변환·선별·축약을 연결하면 데이터 파이프라인이 된다

학습용 사례로 문자열 형태의 측정값을 정수로 바꾸고, 70점 이상만 남겨 합계를 구해 보자. 각 단계의 책임을 나누면 오류 위치와 데이터 변화가 선명해진다.

from functools import reduce

raw_scores = ["85", "62", "91", "70"]
scores = list(map(int, raw_scores))
passed = list(filter(lambda score: score >= 70, scores))
total = reduce(lambda x, y: x + y, passed, 0)

print(scores)  # [85, 62, 91, 70]
print(passed)  # [85, 91, 70]
print(total)   # 246

map은 자료형을 변환하고, filter는 통과 기준을 적용하며, reduce는 남은 값들을 하나로 모은다. 만약 raw_scores에 "결석"이 들어 있으면 map(int, ...) 단계에서 ValueError가 발생한다. 이때는 전체 파이프라인을 넓은 예외 처리로 감추기보다 입력 정제 규칙을 정하거나 개별 값의 변환 실패를 기록하는 편이 분석 품질에 도움이 된다.

파이프라인 검산: 각 단계 뒤에 ① 자료형이 무엇인지, ② 원소 수가 어떻게 변했는지, ③ 원래 순서가 유지되는지, ④ 빈 데이터가 들어와도 결과가 정의되는지를 확인한다.

핵심 개념 정리

  • 언패킹은 위치에 따라 요소를 변수에 배정하며, 기본형은 변수 수와 요소 수가 같아야 한다.
  • 확장 언패킹의 별표 변수는 나머지를 리스트로 받고, 언더스코어는 사용하지 않을 값을 나타내는 관례다.
  • 예외 처리는 예상 가능한 실패를 구체적으로 분류하고 복구·안내·정리 경로를 설계하는 일이다.
  • finally는 항상 실행되므로 정리 대상의 존재를 보장해야 하며, 파일은 with 문이 더 안전할 수 있다.
  • 함수형 관점은 데이터 변경과 부작용을 줄이고 작은 계산 함수를 조합한다.
  • map은 변환, filter는 선별, reduce는 축약에 사용한다.

전체 사고 순서: 먼저 입력 구조를 언패킹해 의미 있는 이름을 붙이고, 실패 가능한 경계를 좁혀 예외별 대응을 설계한다. 그다음 각 요소의 변환은 map, 조건 선별은 filter, 누적 결합은 reduce 또는 더 명확한 내장 함수로 표현하고, 단계마다 자료형·개수·빈 입력을 검산한다.

예상문제 10선

1. a, *b, c = [10, 20, 30, 40] 실행 후 변수의 값은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ③

  • ① 오답: 별표가 없는 일반 변수는 단일 요소를 받으므로 a와 c는 리스트가 아니다.
  • ② 오답: 확장 언패킹에서 별표 변수는 튜플이 아니라 리스트를 받는다.
  • ③ 정답: 양 끝 요소는 일반 변수에, 남은 가운데 두 요소는 리스트로 b에 배정된다.
  • ④ 오답: 별표는 b에 붙어 있으므로 남는 요소를 c가 받을 수 없다.

2. 반복문에서 for _ in range(3)의 언더스코어를 가장 정확히 설명한 것은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ①

  • ① 정답: 값은 매번 할당되지만 다시 읽지 않겠다는 의도를 이름으로 표현한다.
  • ② 오답: 언더스코어는 일반 식별자로 사용할 수 있으며 값 생성은 계속된다.
  • ③ 오답: 반복 횟수는 range(3)이 정하며 변수 이름은 횟수에 영향을 주지 않는다.
  • ④ 오답: 언더스코어에도 값이 할당되며 삭제 기능은 없다.

3. 숫자 입력을 받아 10 / number를 계산할 때 예외와 원인의 연결이 옳은 것은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ④

  • ① 오답: 파일 경로가 아니라 정수 변환에서 생긴 오류이므로 파일 예외와 무관하다.
  • ② 오답: 정수 0으로 변환은 성공하고 나눗셈에서 ZeroDivisionError가 발생한다.
  • ③ 오답: 0이 아닌 정상 정수라면 두 단계 모두 성공한다.
  • ④ 정답: 숫자가 아닌 문자열을 int()로 바꾸지 못할 때 ValueError가 발생한다.

4. try-except-else-finally에서 예외가 발생하지 않았을 때의 실행 흐름은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ②

  • ① 오답: try가 먼저이며 예외가 없으면 except는 실행되지 않는다.
  • ② 정답: 시도가 성공하면 else가 실행되고 마지막에 finally가 실행된다.
  • ③ 오답: except는 해당 예외가 발생했을 때만 실행된다.
  • ④ 오답: finally는 정리 단계이므로 일반적으로 마지막에 실행된다.

5. 파일을 여는 try 뒤의 finally: file.close()가 새 오류를 만들 수 있는 이유는?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ③

  • ① 오답: finally는 예외 발생 여부와 관계없이 실행된다.
  • ② 오답: 읽기 모드로 연 파일도 작업 뒤에 닫을 수 있고 닫아야 한다.
  • ③ 정답: 자원 획득 전에 실패하면 정리할 객체 자체가 없으므로 정의되지 않은 변수를 참조할 수 있다.
  • ④ 오답: FileNotFoundError는 구체적인 except 블록으로 처리할 수 있다.

6. 순수 함수에 가장 가까운 것은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ④

  • ① 오답: 같은 입력에도 호출 시각에 따라 결과가 달라진다.
  • ② 오답: 함수 밖의 전역 목록을 변경하는 부작용이 있다.
  • ③ 오답: 반환값이 같아도 파일 기록이라는 외부 부작용이 있다.
  • ④ 정답: 출력이 입력으로만 결정되고 외부 상태를 변경하지 않는다.

7. 람다 함수보다 def로 정의한 일반 함수가 더 적합한 상황은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ①

  • ① 정답: 문장 단위 본문, 이름, 문서화와 재사용이 필요한 로직은 일반 함수가 읽고 관리하기 쉽다.
  • ② 오답: 한 표현식의 짧은 정렬 키는 람다가 자연스러운 사용처다.
  • ③ 오답: 단순 조건 함수는 람다로 간결하게 전달할 수 있다.
  • ④ 오답: 일회성 단일 표현식은 람다의 용도와 맞는다.

8. list(filter(lambda x: x % 2 == 0, [1, 2, 3, 4]))의 결과는?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ②

  • ① 오답: 참·거짓 목록을 만드는 것은 조건을 map으로 적용했을 때의 형태다.
  • ② 정답: 나머지가 0인 원래 요소 2와 4만 조건을 통과한다.
  • ③ 오답: filter는 나머지 계산값이 아니라 원래 요소를 반환한다.
  • ④ 오답: 1과 3은 나머지가 1이므로 조건이 거짓이다.

9. reduce(lambda x, y: x + y, [2, 3, 4])의 누적 과정과 결과는?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ④

  • ① 오답: 중간 결과가 다음 요소와 계속 결합되므로 첫 연산에서 끝나지 않는다.
  • ② 오답: 모든 요소를 각각 바꾸는 설명은 map에 가깝다.
  • ③ 오답: 전달된 함수는 최댓값 비교가 아니라 덧셈을 수행한다.
  • ④ 정답: 첫 결합 결과 5가 다음 호출의 첫 인수가 되어 마지막 값 9를 만든다.

10. 문자열 점수 목록에서 정수 변환, 70점 이상 선별, 합계 계산을 순서대로 수행할 도구 연결은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ②

  • ① 오답: 문자열 상태에서 수치 기준을 적용하기 전에 정수 변환이 먼저 필요하다.
  • ② 정답: 각 값을 변환하고 조건에 맞는 값을 남긴 뒤 하나의 합계로 축약하는 순서다.
  • ③ 오답: 여러 문자열을 먼저 축약하면 개별 점수의 변환과 선별이 불가능해진다.
  • ④ 오답: 합계 하나로 줄인 뒤에는 원래 점수별로 70점 기준을 적용할 수 없다.

참고 자료와 작성 기준

이 글은 해당 차시 강의자료를 바탕으로 학습 목적에 맞게 재구성한 비공식 학습자료입니다. 개념 관계, 예제와 문제 해설은 학습자의 이해를 돕도록 구성하고 검토했습니다.

  • 작성·편집: 올에이클래스 학습연구팀
  • 주요 근거: 한국방송통신대학교 컴퓨터과학과 「오픈소스 기반 데이터분석」 제3강 강의자료(2025)
  • 보충 자료: 별도의 외부 자료를 사용하지 않음
  • 편집 원칙: 올에이클래스 편집 정책
  • 최종 내용 검토: 2026-08-30

댓글