7주차 · 문자열과 데이터 파싱
이번 주 이야기: “센서가 보내온 텍스트, 어떻게 숫자로 바꿀까?”
Section titled “이번 주 이야기: “센서가 보내온 텍스트, 어떻게 숫자로 바꿀까?””실제 장비나 파일에서 읽어온 데이터는 대부분 문자열로 들어옵니다.
" sensor=A1, value= 42 , unit=V ""id:001 | temp:24.8 | status:OK"이 텍스트를 프로그램이 쓸 수 있는 숫자나 딕셔너리로 바꾸는 작업이 파싱(parsing) 입니다.
이번 주 목표는 단순합니다.
- 문자열 메서드 4개(
strip,split,replace,join)를 익히고 - 정제 → 분리 → 검증 → 변환 순서를 몸에 익히며
- 리스트 컴프리헨션으로 반복 처리를 한 줄로 줄이기
- 문자열 인덱싱/슬라이싱을 실수 없이 읽는다.
- 문자열이 immutable(불변)이라는 의미를 코드로 이해한다.
strip,split,replace,join,find를 목적에 맞게 조합한다.- 텍스트를 정제 → 분리 → 검증 → 변환 순서로 처리한다.
- 리스트 컴프리헨션 기본 형태를 읽고 작성한다.
- C 문자열과 Python 문자열의 차이를 설명한다.
1) 문자열 기초: 인덱싱과 슬라이싱
Section titled “1) 문자열 기초: 인덱싱과 슬라이싱”문자열은 인덱스가 있는 문자 시퀀스입니다.
text = "signal"print(text[0]) # s (첫 글자)print(text[-1]) # l (마지막 글자)print(text[1:4]) # ign (1번부터 3번까지)print(len(text)) # 6슬라이싱 규칙
Section titled “슬라이싱 규칙”text = "sampling"print(text[0:4]) # samp — 0,1,2,3번 (4번 미포함)print(text[4:]) # ling — 4번부터 끝까지print(text[:3]) # sam — 맨앞부터 2번까지print(text[::2]) # smln — 두 칸 간격2) 문자열은 불변(immutable)이다
Section titled “2) 문자열은 불변(immutable)이다”한 글자를 직접 바꿀 수 없습니다.
# 잘못된 방법name = "radar"name[0] = "R" # TypeError 발생!
# 올바른 방법name = "R" + name[1:]print(name) # Radar문자열 수정은 “원본 수정”이 아니라 새 문자열 생성입니다.
3) 파싱에 자주 쓰는 5개 메서드
Section titled “3) 파싱에 자주 쓰는 5개 메서드”strip() — 앞뒤 공백·개행 제거
Section titled “strip() — 앞뒤 공백·개행 제거”raw = " temp=24.8\n"print(raw.strip()) # temp=24.8split() — 구분자로 조각내기
Section titled “split() — 구분자로 조각내기”line = "time=12:31,temp=24.8,status=OK"print(line.split(","))# ['time=12:31', 'temp=24.8', 'status=OK']두 번째 인자로 최대 분리 횟수를 지정할 수 있습니다.
line = "key:val:extra"print(line.split(":", 1)) # ['key', 'val:extra'] — 첫 번째만 분리replace() — 패턴 치환
Section titled “replace() — 패턴 치환”msg = "temp : 24.8"print(msg.replace(" ", "")) # temp:24.8join() — 리스트를 하나의 문자열로
Section titled “join() — 리스트를 하나의 문자열로”tokens = ["A", "B", "C"]print("-".join(tokens)) # A-B-Cfind() — 부분 문자열 위치 찾기
Section titled “find() — 부분 문자열 위치 찾기”line = "id:001|temp:24.8"print(line.find("|")) # 6 (없으면 -1)print(line.find("xyz")) # -1C와 비교: 문자열 처리 방식이 이렇게 다릅니다
Section titled “C와 비교: 문자열 처리 방식이 이렇게 다릅니다”| 항목 | C | Python | 기억할 점 |
|---|---|---|---|
| 문자열 표현 | char text[] = "signal"; | text = "signal" | Python은 선언이 훨씬 짧다 |
| 길이 구하기 | strlen(text) | len(text) | 함수 이름이 다르다 |
| 부분 추출 | 루프/포인터 연산 | 슬라이싱 text[1:4] | Python은 문법으로 바로 자른다 |
| 공백 제거 | 직접 구현/라이브러리 | strip() | Python은 내장 메서드 사용 |
| 분리 | strtok 등 | split() | Python은 원본 보존이 더 직관적 |
| 위치 탐색 | strstr, strchr | find() | 없으면 -1 반환 |
char text[] = "signal";printf("%c\n", text[0]); // 첫 글자printf("%zu\n", strlen(text)); // 길이text = "signal"print(text[0]) # 첫 글자print(len(text)) # 길이핵심은 Python 문자열은 슬라이싱과 메서드가 강력해서 파싱 코드가 짧아진다는 점입니다.
4) 파싱 파이프라인 사고법
Section titled “4) 파싱 파이프라인 사고법”flowchart TB
A[Raw Text] --> B["1) Clean<br/>strip(), replace()"]
B --> C["2) Split<br/>split(',') / split(':')"]
C --> D["3) Validate<br/>필드 수 / 키 존재 / 숫자 형태"]
D --> E["4) Convert<br/>int(), float()"]
E --> F["Structured Data<br/>dict / list"] 5) 리스트 컴프리헨션: 리스트를 한 줄로 만들기
Section titled “5) 리스트 컴프리헨션: 리스트를 한 줄로 만들기”예제에서 [f.strip() for f in fields]라는 문법이 등장합니다. 이것이 리스트 컴프리헨션입니다.
[표현식 for 변수 in 반복가능한것]위 한 줄은 아래 for 문과 정확히 같은 결과를 만듭니다.
# for 문 버전result = []for f in fields: result.append(f.strip())
# 리스트 컴프리헨션 버전 (같은 동작)result = [f.strip() for f in fields]단계별 추적 예시
Section titled “단계별 추적 예시”fields = [" A ", " B ", "C "]라고 할 때:
| 반복 순서 | f 값 | f.strip() 결과 |
|---|---|---|
| 1번째 | " A " | "A" |
| 2번째 | " B " | "B" |
| 3번째 | "C " | "C" |
최종 결과: ["A", "B", "C"]
조건을 붙일 수도 있습니다.
# 길이 4 이상인 단어만 고르기words = ["radar", "AI", "signal", "ML"]long_words = [w for w in words if len(w) >= 4]print(long_words) # ['radar', 'signal']6) 자주 하는 오해와 반례
Section titled “6) 자주 하는 오해와 반례”오해 1: “split()만 하면 파싱 끝”
Section titled “오해 1: “split()만 하면 파싱 끝””line = "id:001 | temp:24.8"parts = line.split("|")print(parts[1]) # ' temp:24.8' (앞 공백 포함!)공백 때문에 키 비교가 실패합니다. strip()을 함께 써야 안전합니다.
오해 2: “숫자로 바로 바꾸는 게 빠르다”
Section titled “오해 2: “숫자로 바로 바꾸는 게 빠르다””line = "score:NA"score = int(line.split(":")[1]) # ValueError — 검증 없이 변환하면 바로 중단오해 3: “인덱스 접근은 항상 가능”
Section titled “오해 3: “인덱스 접근은 항상 가능””line = "id:001"parts = line.split("|")print(parts[1]) # IndexError — 필드가 1개뿐!필드 수를 먼저 검사해야 합니다.
예제 1 줄별 해설
Section titled “예제 1 줄별 해설”raw는 일부러 공백이 섞인 실제 입력 상황을 흉내 냅니다.strip()으로 외곽 잡음을 먼저 제거합니다.split(”,“)로 필드 경계를 나눕니다.- 리스트 컴프리헨션으로 필드별 공백을 정리해 비교 실패를 예방합니다.
split(”=“)으로 키와 값을 분리하고 값을 추출합니다.isdigit()검사로 변환 가능성을 먼저 확인합니다.- 검증 통과 시에만
int()를 호출합니다.
예제 2 줄별 해설
Section titled “예제 2 줄별 해설”replace(” ”, "")로 공백을 제거한 뒤split(”|“)로 필드를 나눕니다.continue는 “이번 반복 나머지를 건너뛰어라”는 명령입니다. 필드 수가 맞지 않으면 즉시 다음 줄로 넘어갑니다.split(”:”, 1)의 두 번째 인자1은 maxsplit입니다. 첫 번째 콜론만 기준으로 나눠 값 안에 콜론이 있어도 안전합니다.- 구조 검증이 끝난 뒤
isdigit()패턴으로 숫자 여부를 확인한 뒤float()로 변환합니다. - 성공/실패를 동시에 출력해 데이터 품질을 확인합니다.
7) 파싱 안티패턴 요약표
Section titled “7) 파싱 안티패턴 요약표”| 안티패턴 | 실제 증상 | 개선 습관 |
|---|---|---|
한 줄 체인 코딩 int(line.split(':')[1]) | 어디서 깨졌는지 모름 | 중간 변수로 단계 기록 |
매직 인덱스 남용 parts[2] | IndexError | len(parts) 먼저 확인 |
| 검증 없는 변환 | ValueError | validate → convert 고정 |
| 공백 무시 | 키 미스매치 | strip/replace 습관화 |
| 실패 데이터 폐기 | 디버깅 정보 손실 | (원문, 원인) 함께 저장 |
실습 문제 · 직접 코딩
Section titled “실습 문제 · 직접 코딩”문제: 아래 코드를 실행하지 말고, 각 print의 출력값을 먼저 적어 보세요. 그 다음 콘솔에서 직접 확인하세요.
문제: 아래 코드는 의도대로 동작하지 않습니다. 버그가 있는 줄을 찾고, 왜 틀렸는지 주석으로 설명한 뒤 고치세요.
문제: 문자열 “name=kim,score=95”를 딕셔너리로 바꾸세요.
문제: 리스트 컴프리헨션으로 문자열 목록을 변환하세요.
문제: 아래 로그를 파싱해 평균 온도와 오류 줄 수를 출력하세요.