[에러 해결] [Python] 사용자 입력 기반 리스트 필터링 로직 오류와 해결 방법

안녕하세요, 개발자 여러분! 오늘은 Stack Overflow에서 자주 접할 수 있는 파이썬 사용자 입력 처리 및 데이터 필터링 관련 논리 오류에 대해 다루고, 명확하고 효율적인 해결 방법을 제시하고자 합니다. 특히, 주어진 대화 데이터에서 특정 감정(긍정, 중립, 부정)에 해당하는 대화를 검색하고 표시하는 로직을 구현하다 발생할 수 있는 문제점과 그 해법을 상세히 알아보겠습니다.

1. 에러 발생 상황

사용자는 다음과 같은 요구사항을 가지고 있었습니다. 미리 정의된 대화 목록에서 사용자가 입력한 감정(Positive, Neutral, Negative)에 해당하는 대화를 찾아 출력하고, 만약 일치하는 대화가 없다면 적절한 메시지를 표시하는 프로그램입니다. 사용자는 ‘quit’을 입력하여 프로그램을 종료할 수 있어야 합니다.

문제의 핵심은 사용자로부터 감정 레이블을 입력받아 해당 감정과 일치하는 모든 대화를 표시하는 부분에 있었습니다. 아래는 사용자가 작성한 코드의 일부입니다.

# Part 1 – Create the Dataset (15 points)
# Create a dictionary called conversations include id, customer, message, 
# sentiment, and score.

conversations = [
    {
    "id": "C001",
    "customer": "Alice",
    "message": "The chatbot solved my problem quickly.",
    "sentiment": "Positive",
    "score": 9
    },
    # ... (생략된 다른 대화 데이터)
    {
    "id": "C008",
    "customer": "Elsa",
    "message": "The chatbot crashed when answering.",
    "sentiment": "Negative",
    "score": 1
    },   
]

그리고 사용자 입력 및 유효성 검사 로직입니다.

#Display every conversation that matches the selected sentiment.
#If no conversations match, display an appropriate message.

prompt = "\nEnter a sentiment: postive, neutral, negative:"
prompt += "\nEnter 'quit' to end the program. "
active = True
while active:
    message = input(prompt)
    if message == 'quit':
        active = False
    else:
        message != ["positive" or "neutral" or "negative"]
        print("Please choose positive, neutral, or negative")
        message = input(prompt)

위 코드에서 사용자의 의도는 ‘quit’이 아닌 다른 입력이 들어왔을 때, 해당 입력이 유효한 감정 레이블(positive, neutral, negative)인지 확인하고, 유효하지 않으면 재입력을 요청하는 것이었습니다. 하지만 이 부분에서 논리적인 오류가 발생했습니다.

2. 명확한 발생 원인

문제는 주로 두 가지 핵심적인 논리 오류에서 비롯됩니다.

2.1. 잘못된 조건문 (`message != [“positive” or “neutral” or “negative”]`)

파이썬에서 `or` 연산자는 불리언 컨텍스트에서 첫 번째 참(True)으로 평가되는 값을 반환합니다. 따라서 `”positive” or “neutral” or “negative”`는 문자열 `”positive”`로 평가됩니다. 결과적으로 조건문은 `message != [“positive”]`와 동일해집니다.

  • 사용자가 입력한 `message`는 일반적으로 문자열(예: “positive”, “neutral”)입니다.
  • `[“positive”]`는 문자열이 아니라, “positive”라는 문자열 하나를 포함하는 리스트입니다.
  • 문자열 “positive”와 리스트 `[“positive”]`는 절대로 같을 수 없습니다.

따라서 이 조건문은 사용자가 어떤 감정 레이블을 입력하더라도 항상 `True`로 평가됩니다. 즉, ‘quit’이 아닌 모든 입력에 대해 “Please choose positive, neutral, or negative” 메시지가 출력되고 재입력을 요청하게 됩니다.

2.2. 입력 처리 로직의 부재

사용자의 코드는 유효한 감정 레이블을 입력받았을 때, 실제로 `conversations` 리스트를 필터링하여 일치하는 대화를 찾아 출력하는 핵심 로직이 완전히 빠져 있습니다. 또한, 오류 메시지 출력 후 `message = input(prompt)`를 다시 호출하는 부분은 첫 번째 유효한 입력을 무시하고 다시 입력을 받게 만들어, 프로그램 흐름이 사용자의 의도와 다르게 작동합니다.

3. 해결 방법 및 코드 예시

문제 해결의 핵심은 사용자 입력의 유효성을 올바르게 검사하고, 유효한 입력에 대해서는 `conversations` 리스트를 효과적으로 필터링하여 결과를 출력하는 것입니다. 다음은 수정된 코드와 각 부분에 대한 설명입니다.

3.1. 수정된 코드

# Part 1 – Create the Dataset (15 points)
# 대화 데이터셋 (원본과 동일)
conversations = [
    {
    "id": "C001",
    "customer": "Alice",
    "message": "The chatbot solved my problem quickly.",
    "sentiment": "Positive",
    "score": 9
    },
    {
    "id": "C002",
    "customer": "Brian",
    "message": "The chatbot kept repeating the same answer.",
    "sentiment": "Negative",
    "score": 3
    },
    {
    "id": "C003",
    "customer": "David",
    "message": "The chatbat solved my problem without needing to call.",
    "sentiment": "Positive",
    "score": 10
    },
    {
    "id": "C004",
    "customer": "Amanda",
    "message": "The chatbot gave an incorrect answer.",
    "sentiment": "Negative",
    "score": 3
    },
    {
    "id": "C005",
    "customer": "Chloe",
    "message": "The chatbot did not understand my question.",
    "sentiment": "Negative",
    "score": 3
    },
    {
    "id": "C006",
    "customer": "Bradeon",
    "message": "The chatbot pointed me to the correct form.",
    "sentiment": "Positive",
    "score": 8
    },
    {
    "id": "C007",
    "customer": "Dayson",
    "message": "The chatbot did not understand what I asked",
    "sentiment": "Negative",
    "score": 2
    },
    {
    "id": "C008",
    "customer": "Elsa",
    "message": "The chatbot crashed when answering.",
    "sentiment": "Negative",
    "score": 1
    },   
]

# 감정 선택지 (원본과 동일)
sentiment_options = (
    "Positive",
    "Neutral",
    "Negative"
)

# 사용자에게 보여줄 안내 메시지
prompt = "\n감정(Positive, Neutral, Negative)을 입력하세요: "
prompt += "'quit'을 입력하면 프로그램을 종료합니다. "

active = True
while active:
    # 사용자 입력을 받고, 앞뒤 공백 제거 및 소문자로 변환하여 처리
    user_input = input(prompt).strip().lower() 

    if user_input == 'quit':
        # 'quit' 입력 시 프로그램 종료
        active = False
        print("프로그램을 종료합니다.")
    elif user_input in [s.lower() for s in sentiment_options]:
        # 유효한 감정 레이블 입력 시
        # 대화 데이터의 sentiment 값과 일치시키기 위해 첫 글자만 대문자로 변환
        selected_sentiment = user_input.capitalize() 
        
        matching_conversations = []
        # 대화 목록을 순회하며 선택된 감정과 일치하는 대화 찾기
        for conv in conversations:
            if conv["sentiment"] == selected_sentiment:
                matching_conversations.append(conv)
        
        # 검색 결과 출력
        if matching_conversations:
            print(f"\n--- '{selected_sentiment}' 감정의 대화 내역 ({len(matching_conversations)}건) ---")
            for conv in matching_conversations:
                print(f"ID: {conv['id']}, 고객: {conv['customer']}, 메시지: \"{conv['message']}\", 점수: {conv['score']}")
        else:
            print(f"'{selected_sentiment}' 감정과 일치하는 대화가 없습니다.")
    else:
        # 유효하지 않은 입력 시 오류 메시지 출력
        print("유효하지 않은 입력입니다. 'Positive', 'Neutral', 'Negative' 중 하나를 입력하거나 'quit'을 입력하세요.")

3.2. 코드 설명

  1. 사용자 입력 처리 (`user_input = input(prompt).strip().lower()`):
    • `strip()`: 사용자가 입력한 문자열의 앞뒤 공백을 제거하여 의도치 않은 오류를 방지합니다.
    • `lower()`: 사용자 입력을 모두 소문자로 변환하여 대소문자 구분 없이 처리할 수 있도록 합니다. (예: “positive”, “Positive”, “POSITIVE” 모두 동일하게 처리)
  2. 종료 조건 (`if user_input == ‘quit’:`):
    • ‘quit’이 입력되면 `active` 플래그를 `False`로 설정하여 `while` 루프를 종료합니다.
  3. 유효한 감정 레이블 검사 (`elif user_input in [s.lower() for s in sentiment_options]:`):
    • `[s.lower() for s in sentiment_options]`를 사용하여 `sentiment_options` 튜플의 모든 요소를 소문자로 변환한 리스트를 동적으로 생성합니다. (예: `[‘positive’, ‘neutral’, ‘negative’]`)
    • `in` 연산자를 사용하여 `user_input`이 이 소문자 리스트 안에 포함되어 있는지 효율적으로 검사합니다. 이것이 올바른 유효성 검사 방법입니다.
    • `selected_sentiment = user_input.capitalize()`: 대화 데이터의 `sentiment` 필드가 첫 글자만 대문자인 형태이므로, 비교를 위해 사용자 입력을 `capitalize()` 메서드로 변환합니다. (예: “positive” -> “Positive”)
  4. 대화 필터링 및 출력:
    • `matching_conversations`라는 빈 리스트를 생성합니다.
    • `conversations` 리스트의 각 대화(`conv`)를 순회하면서, 대화의 `sentiment` 값과 `selected_sentiment`가 일치하는지 확인합니다.
    • 일치하는 대화는 `matching_conversations` 리스트에 추가합니다.
    • 필터링이 끝난 후, `matching_conversations` 리스트가 비어있지 않으면(즉, 일치하는 대화가 있으면) 해당 대화들의 상세 정보를 출력합니다.
    • 리스트가 비어있으면 “일치하는 대화가 없습니다”라는 메시지를 출력합니다.
  5. 유효하지 않은 입력 처리 (`else:`):
    • ‘quit’도 아니고, 유효한 감정 레이블도 아닌 경우, “유효하지 않은 입력” 메시지를 출력하고 다시 `while` 루프의 시작으로 돌아가 사용자 입력을 기다립니다.

4. 향후 예방을 위한 팁

이와 같은 논리 오류를 방지하고 더 견고한 코드를 작성하기 위한 몇 가지 팁입니다.

  • 조건문 로직 재확인: 복잡한 `and`, `or` 연산자를 포함하는 조건문은 예상과 다르게 작동할 수 있으므로, 항상 그 결과를 명확히 이해하고 필요하다면 작은 테스트 코드로 검증하는 습관을 들이세요.
  • `in` 연산자 활용: 특정 값이 컬렉션(리스트, 튜플, 세트 등)에 포함되어 있는지 확인할 때는 `in` 연산자를 사용하는 것이 가장 파이썬ic하고 가독성이 좋습니다. `x == a or x == b or x == c` 대신 `x in (a, b, c)`를 사용하세요.
  • 일관된 데이터 형식 유지: 사용자 입력과 비교 대상이 되는 데이터의 형식을 일관되게 유지하는 것이 중요합니다. 대소문자, 공백 등은 `lower()`, `upper()`, `capitalize()`, `strip()` 등의 문자열 메서드를 사용하여 표준화할 수 있습니다.
  • 단계별 구현 및 테스트: 코드를 한 번에 완성하려 하기보다는, 사용자 입력 처리, 유효성 검사, 데이터 필터링, 결과 출력 등 각 단계를 나누어 구현하고 테스트하면 오류를 더 쉽게 발견하고 수정할 수 있습니다.
  • 명확한 사용자 피드백: 사용자 입력이 잘못되었을 때 어떤 입력이 유효한지 명확하게 알려주는 메시지를 제공하여 사용자 경험을 개선하세요.

이 문서를 통해 파이썬에서 사용자 입력을 처리하고 데이터를 필터링하는 과정에서 발생할 수 있는 일반적인 오류를 이해하고, 효율적으로 해결하는 방법을 배우셨기를 바랍니다. 행복한 코딩 되세요!

댓글 남기기