# Part 2: 파이썬 핵심 문법 마스터하기

**⬅️ 이전 시간: [Part 1: AI 개발 환경 완벽 구축 가이드](../01_ai_development_environment/part_1_ai_development_environment.md)**
**➡️ 다음 시간: [Part 3: 파이썬 컬렉션, 더 깊게 이해하기](../03_python_collections/part_3_python_collections.md)**

---

## 1. 학습 목표 (Learning Objectives)

이번 파트가 끝나면, 여러분은 다음을 할 수 있게 됩니다.

- 변수를 선언하고 숫자, 문자열, 불리언 등 기본 자료형을 활용할 수 있습니다.
- [리스트](../../glossary.md#리스트-list), [튜플](../../glossary.md#튜플-tuple), [딕셔너리](../../glossary.md#딕셔너리-dictionary), 셋의 차이점을 이해하고 상황에 맞게 선택하여 사용할 수 있습니다.
- `if`, `for`, `while` 등 제어문을 사용하여 코드의 실행 흐름을 제어할 수 있습니다.
- 코드의 재사용성을 높이는 [함수](../../glossary.md#함수-function)를 직접 정의하고, `lambda`를 이용한 익명 함수를 작성할 수 있습니다.
- [리스트 컴프리헨션](../../glossary.md#리스트-컴프리헨션-list-comprehension)과 같은 파이썬다운(Pythonic) 코드를 작성할 수 있습니다.

## 2. 핵심 요약 (Key Summary)
이 파트에서는 파이썬 프로그래밍의 가장 기본적인 구성 요소들을 배웁니다. 데이터를 저장하는 변수, 다양한 종류의 데이터를 나타내는 자료형(숫자, 문자열, 불리언)을 시작으로, 여러 데이터를 묶어서 관리하는 리스트, 튜플, 딕셔너리, 셋의 사용법과 차이점을 익힙니다. 또한, `if`, `for`, `while`과 같은 제어문을 사용하여 코드의 흐름을 제어하고, 함수를 정의하여 코드의 재사용성을 높이는 방법을 학습합니다.

- **핵심 키워드**: `변수(Variable)`, `자료형(Data Type)`, `리스트(List)`, `튜플(Tuple)`, `딕셔너리(Dictionary)`, `셋(Set)`, `제어문(Control Flow)`, `들여쓰기(Indentation)`, `함수(Function)`, `람다(Lambda)`

## 3. 왜 파이썬 문법이 중요할까요? (Introduction)

AI 개발의 세계에 오신 것을 환영합니다! 파이썬은 AI와 데이터 과학 분야에서 가장 사랑받는 언어입니다. 문법이 간결하고 사람의 생각과 비슷해서, 프로그래밍을 처음 시작하는 분들도 쉽게 배울 수 있습니다. 이번 주차에는 앞으로 우리가 만들 모든 AI 서비스의 뼈대가 될 파이썬의 가장 핵심적인 문법들을 하나씩 정복해나갑니다.

> [!TIP]
> 이 파트의 모든 코드 예제는 아래 링크의 파이썬 파일에서 직접 실행하고 수정해볼 수 있습니다.
> **실행 가능한 소스 코드: [`part_2_3_python_syntax_collections.py`](../../source_code/part_2_3_python_syntax_collections.py)**

---

## 4. 모든 것의 시작, 변수와 자료형

> **🎯 1일차 목표:** 변수(Variable)를 사용해 데이터를 저장하고, 파이썬의 기본 데이터 종류(자료형)를 이해합니다.

### 4.1. 변수(Variable): 데이터에 이름표 붙이기
<a href="../../glossary.md#변수-variable">변수(Variable)</a>는 숫자, 글자 같은 데이터를 저장하는 '상자'입니다. `age = 30` 이라는 코드는, `30`이라는 데이터를 `age`라는 이름의 상자에 넣는다는 뜻입니다. 이제부터 우리는 `age`라는 이름만 부르면 컴퓨터가 알아서 `30`이라는 값을 찾아줍니다.

```python
my_name = "파이"      # 문자열 (String)
my_age = 20         # 정수 (Integer)
pi = 3.14           # 실수 (Float)
is_student = True   # 불리언 (Boolean)

print(f"안녕하세요, 제 이름은 {my_name}이고 나이는 {my_age}살입니다.")
```

### 4.2. 기본 자료형(Data Types)
파이썬은 데이터의 종류를 구분하며, 이를 <a href="../../glossary.md#자료구조-data-structure">자료형</a>이라고 합니다.
- **정수 (Integer)**: `1`, `100`, `-5` 같이 소수점이 없는 숫자.
- **실수 (Float)**: `3.14`, `-0.5` 같이 소수점이 있는 숫자.
- **문자열 (String)****: `"안녕하세요"`, `'Python'` 같이 따옴표로 감싼 글자들.
- **<a href="../../glossary.md#boolean">불리언 (Boolean)</a>**: `True` 또는 `False`. 조건의 참/거짓을 나타냅니다.

---

## 5. 여러 데이터를 담는 그릇 (1): 리스트와 튜플

> **🎯 2일차 목표:** 순서가 있는 데이터 묶음인 [리스트(List)](../../glossary.md#리스트-list)와 변경이 불가능한 [튜플(Tuple)](../../glossary.md#튜플-tuple)을 다루고, [리스트 컴프리헨션](../../glossary.md#리스트-컴프리헨션-list-comprehension)으로 파이썬다운 코드를 작성합니다.

### 5.1. 리스트(List): 순서가 있고 변경 가능한 만능 주머니
- 여러 값을 순서대로 저장하는 **변경 가능한** <a href="../../glossary.md#자료구조-data-structure">자료구조</a>입니다. (`[]` 사용)

```python
fruits = ["apple", "banana", "cherry"]
fruits[1] = "blueberry"      # 값 변경
fruits.append("strawberry")  # 요소 추가
print(f"최종 리스트: {fruits}")
```

### 5.2. 파이썬다운 코드: 리스트 컴프리헨션 (List Comprehension)
`for` 반복문을 한 줄로 간결하게 표현하여 새로운 리스트를 만드는 방법입니다.

```python
# 0부터 9까지의 숫자 중 짝수만 제곱하여 리스트 만들기
squares = [i**2 for i in range(10) if i % 2 == 0]
print(f"짝수의 제곱 리스트: {squares}") # [0, 4, 16, 36, 64]
```

### 5.3. 튜플(Tuple): 순서가 있지만 변경 불가능한 금고
- 여러 값을 순서대로 저장하는 **변경 불가능한** <a href="../../glossary.md#자료구조-data-structure">자료구조</a>입니다. (`()` 사용)
- 한 번 만들어지면 내용을 바꿀 수 없어 안정성이 중요할 때 (e.g., 함수의 반환 값, 좌표) 사용됩니다.

```python
point = (10, 20)
# point[0] = 15 #! TypeError 발생
```

---

## 6. 여러 데이터를 담는 그릇 (2): 딕셔너리와 셋

> **🎯 3일차 목표:** `Key:Value` 쌍으로 데이터를 저장하는 [딕셔너리(Dictionary)](../../glossary.md#딕셔너리-dictionary)와 중복을 허용하지 않는 셋(Set)을 다룹니다.

### 6.1. 딕셔너리(Dictionary): 의미를 부여한 데이터 관리법
- `Key:Value` 쌍으로 데이터를 저장하며, 순서보다 '의미(Key)'를 통해 값을 찾는 데 특화된 <a href="../../glossary.md#자료구조-data-structure">자료구조</a>입니다. (`{}` 사용)

```python
user = {"name": "앨리스", "age": 30}
user['email'] = "alice@example.com" # 데이터 추가/변경
print(f"이름: {user['name']}, 이메일: {user.get('email')}")
```

### 6.2. 셋(Set): 중복을 제거하고 관계를 파악하는 도구
- **중복을 허용하지 않는** 순서 없는 데이터의 모음입니다. (`{}` 사용)
- 데이터의 중복 제거, 그룹 간의 합집합, 교집합 등을 구할 때 유용한 <a href="../../glossary.md#set">자료구조</a>입니다.

```python
unique_numbers = {1, 2, 3, 2, 1, 4}
print(f"중복이 제거된 집합: {unique_numbers}") # {1, 2, 3, 4}

set_a = {1, 2, 3}
set_b = {3, 4, 5}
print(f"교집합: {set_a & set_b}") # {3}
```

### 6.3. 더 파이썬답게: 딕셔너리와 셋 컴프리헨션
[리스트 컴프리헨션](../../glossary.md#리스트-컴프리헨션-list-comprehension)처럼, 딕셔너리와 셋도 `for` 반복문을 한 줄로 간결하게 표현하여 만들 수 있습니다.

```python
# 딕셔너리 컴프리헨션: 숫자를 키로, 제곱을 값으로
square_dict = {x: x**2 for x in range(5)}
print(f"제곱 딕셔너리: {square_dict}") # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

# 셋 컴프리헨션: 중복된 리스트에서 유일한 짝수만 추출
numbers_list = [1, 2, 2, 3, 4, 4, 5]
even_set = {x for x in numbers_list if x % 2 == 0}
print(f"고유한 짝수 셋: {even_set}") # {2, 4}
```

---

## 7. 코드의 흐름 제어하기

> **🎯 4일차 목표:** `if`, `for`, `while` 제어문을 사용하여 조건과 반복에 따라 코드의 흐름을 제어합니다.

### 7.1. 파이썬의 상징: 들여쓰기(Indentation)
파이썬은 **[들여쓰기](../../glossary.md#들여쓰기-indentation)(스페이스 4칸)**로 코드의 소속을 구분하는 '문법'입니다. `if`, `for` 등에 속한 코드들은 반드시 들여쓰기를 해야 합니다.

### 7.2. `if-elif-else`: 조건에 따라 움직이기
```python
score = 85
grade = "" # 학점을 저장할 변수 초기화

if score >= 90:
    grade = "A"
elif score >= 80:
    grade = "B"
else:
    grade = "F"

print(f"학점: {grade}")
```

> **[TIP] 한 줄로 쓰는 조건부 표현식 (Conditional Expression)**
> 간단한 `if-else` 구문은 `참일때_값 if 조건 else 거짓일때_값` 형태로 더 간결하게 표현할 수 있습니다.
> ```python
> # 80점 이상이면 "Pass", 아니면 "Fail"을 부여하는 경우
> status = "Pass" if score >= 80 else "Fail"
> print(f"결과: {status}") # 출력: Pass
> ```

### 7.3. `for`: 순서대로 반복하기
리스트, 튜플, 문자열 등 순회 가능한 객체의 요소를 하나씩 순회합니다. `enumerate`를 사용하면 인덱스와 값을 함께 얻을 수 있습니다.

```python
fruits = ["apple", "banana", "cherry"]
for idx, fruit in enumerate(fruits):
    print(f"{idx+1}번째 과일: {fruit}")
```

### 7.4. `while`: 조건이 만족하는 동안 반복하기
주어진 조건이 `True`인 동안 코드를 계속해서 반복합니다. 조건이 언젠가 `False`가 되도록 만들지 않으면 '무한 루프'에 빠지므로 주의해야 합니다.

```python
count = 3
while count > 0:
    print(f"카운트 다운: {count}")
    count -= 1 # 이 줄이 없으면 무한 루프!
print("발사!")
```

### 7.5. `lambda`의 단짝 친구: `map`과 `filter`
`lambda`는 리스트 같은 순회 가능한 데이터의 모든 요소에 함수를 일괄 적용하는 `map`이나, 특정 조건에 맞는 요소만 걸러내는 `filter`와 함께 사용될 때 매우 강력합니다.

- **`map(함수, 이터러블)`**: 각 요소에 `함수`를 적용한 결과를 반환합니다.
- **`filter(함수, 이터러블)`**: `함수`의 결과가 `True`인 요소만 걸러서 반환합니다.

```python
numbers = [1, 2, 3, 4, 5]

# map과 lambda: 모든 숫자를 2배로 만들기
doubled_numbers = list(map(lambda x: x * 2, numbers))
print(f"2배가 된 숫자들: {doubled_numbers}") # [2, 4, 6, 8, 10]

# filter와 lambda: 홀수만 필터링하기
odd_numbers = list(filter(lambda x: x % 2 != 0, numbers))
print(f"홀수만 필터링: {odd_numbers}") # [1, 3, 5]
```
> [!NOTE]
> `map`과 `filter`의 결과는 그 자체로 리스트가 아닌 '이터레이터'이므로, 결과를 눈으로 확인하려면 `list()`로 변환해야 합니다. 하지만 최근에는 이보다 가독성이 좋은 **리스트 컴프리헨션**이 더 선호되는 추세입니다.
>
> `doubled_numbers = [x * 2 for x in numbers]`
> `odd_numbers = [x for x in numbers if x % 2 != 0]`

---

## 8. 코드를 재사용하는 마법, 함수

> **🎯 5일차 목표:** 반복되는 코드를 `[함수(Function)](../../glossary.md#함수-function)`로 묶어 재사용하고, 함수의 입력([매개변수](../../glossary.md#매개변수-parameter와-인자-argument))과 출력([반환 값](../../glossary.md#반환-값-return-value))을 이해합니다.

### 8.1. 함수(Function): 나만의 미니 프로그램
[함수]../../glossary.md#함수-function)는 특정 작업을 수행하는 코드 묶음에 이름을 붙인 **'재사용 가능한 레시피'**입니다. `def` 키워드로 정의하고, 이름으로 호출하여 사용합니다.

### 8.2. 입력(매개변수)과 출력(반환 값)
- **[매개변수(Parameter)](../../glossary.md#매개변수-parameter와-인자-argument):** 함수에 전달하는 재료(입력 값).
- **[`return`](../../glossary.md#반환-값-return-value):** 함수가 작업을 마친 후 돌려주는 결과물(출력 값).

```python
def add(a, b):
    result = a + b
    return result

# 함수 호출 및 결과 저장
sum_result = add(5, 3)
print(f"5 + 3 = {sum_result}")
```
함수를 잘 사용하면 복잡한 문제를 여러 개의 간단한 함수로 나누어 해결할 수 있어, 훨씬 체계적인 프로그래밍이 가능해집니다.

### 8.3. 한 줄로 끝내는 익명 함수: 람다(Lambda)
`lambda`는 이름 없이 사용하는 간단한 한 줄짜리 함수입니다. '익명 함수'라고도 불리며, 복잡한 `def` 함수 정의가 필요 없는 간단한 작업을 다른 함수의 인자로 전달할 때 매우 유용합니다.

- **기본 문법**: `lambda 인자: 표현식`

예를 들어, 두 수를 더하는 간단한 함수는 `lambda`를 사용해 다음과 같이 표현할 수 있습니다.

```python
# def add(a, b):
#     return a + b
# 위 함수를 람다로 표현
add_lambda = lambda a, b: a + b

print(f"람다 함수 결과: {add_lambda(3, 5)}") # 출력: 람다 함수 결과: 8

# sorted() 함수의 key 매개변수에 활용
points = [(1, 5), (3, 2), (2, 8)]
# 각 튜플의 두 번째 요소를 기준으로 정렬
sorted_points = sorted(points, key=lambda point: point[1])
print(f"두 번째 원소로 정렬: {sorted_points}") # 출력: [(3, 2), (1, 5), (2, 8)]
```
`lambda`는 코드를 더 간결하고 직관적으로 만들어주지만, 복잡한 로직에는 일반적인 `def` 함수를 사용하는 것이 가독성에 더 좋습니다.

---

## 9. 심화 학습: Pythonic 코드 탐구 (Decorators & Generators)

숙련된 개발자를 위해, 코드의 효율성과 표현력을 극대화하는 파이썬의 강력한 기능인 데코레이터와 제너레이터를 소개합니다.

### 9.1. 데코레이터(Decorators): 함수를 포장하는 기술

**데코레이터**는 기존 함수의 코드를 수정하지 않고도, 그 함수에 새로운 기능을 덧붙이거나 감싸는(wrapping) '포장지' 같은 역할을 합니다. 로깅, 실행 시간 측정, 접근 제어 등 다양한 [횡단 관심사(Cross-cutting concerns)](../../glossary.md#횡단-관심사-cross-cutting-concern)를 우아하게 처리할 수 있습니다.

```python
import time

def timing_decorator(func):
    """함수의 실행 시간을 측정하는 데코레이터"""
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs) # 원래 함수 실행
        end_time = time.time()
        print(f"'{func.__name__}' 함수 실행 시간: {end_time - start_time:.4f}초")
        return result
    return wrapper

@timing_decorator
def slow_function(delay_time):
    """시간이 오래 걸리는 가상 함수"""
    print("느린 함수 시작...")
    time.sleep(delay_time)
    print("느린 함수 종료.")

# 데코레이터가 적용된 함수 호출
slow_function(2)
```
위 예제에서 `@timing_decorator`는 `slow_function`에 실행 시간 측정 기능을 '장식'합니다.

### 9.2. 제너레이터(Generators): 메모리 효율적인 이터레이터

**제너레이터**는 모든 값을 메모리에 올려두고 시작하는 리스트와 달리, 필요할 때마다 값을 '생성(yield)'하여 전달하는 특별한 종류의 [이터레이터](../../glossary.md#이터레이터-iterator)입니다. 이 방식은 대용량 데이터 스트림을 처리할 때 메모리 사용량을 획기적으로 줄여줍니다.

- **`yield` 키워드**: 함수가 제너레이터임을 나타냅니다. 값을 반환하고 함수의 상태를 일시 정지시킵니다. 다음에 호출될 때 정지된 지점부터 다시 시작합니다.
- **제너레이터 표현식(Generator Expression)**: 리스트 컴프리헨션과 유사하지만, `[]` 대신 `()`를 사용합니다. 즉시 리스트를 생성하는 대신 제너레이터 객체를 반환합니다.

```python
# 제너레이터 함수
def count_up_to(max_num):
    count = 1
    while count <= max_num:
        yield count # 값을 생성하고 멈춤
        count += 1

# 제너레이터 사용
counter = count_up_to(5)
for num in counter:
    print(num) # 1, 2, 3, 4, 5 출력

# 제너레이터 표현식
large_squares = (x**2 for x in range(1_000_000))
# sum(large_squares) # 메모리 문제 없이 거대한 시퀀스의 합계를 계산 가능
```

이러한 기능들은 AI/ML 파이프라인에서 대규모 데이터셋을 효율적으로 다루거나, 모델의 행동을 로깅하는 등 실제 개발 현장에서 매우 유용하게 사용됩니다.

---

## 10. 연습 문제 (Exercises)

이번 주에 배운 내용을 바탕으로 다음 함수들을 직접 완성해보세요.

**문제 1: 학점 계산기 함수**
- 학생의 점수(0~100)를 매개변수로 받아 학점을 반환하는 `get_grade` 함수를 `if-elif-else`를 사용하여 만들어보세요.
  - 90점 이상: "A", 80점 이상: "B", 70점 이상: "C", 70점 미만: "F"

**문제 2: 리스트에서 특정 단어 개수 세기**
- 과일 이름 리스트와 특정 과일 이름을 매개변수로 받아, 리스트 안에 그 과일이 몇 번 나타나는지 `for`문을 사용해 세고 개수를 반환하는 `count_word` 함수를 만들어보세요.

**문제 3: 구구단 출력 함수**
- 숫자를 하나 매개변수로 받아 해당 숫자의 구구단을 `while`문을 사용하여 출력하는 `multiplication_table` 함수를 만들어보세요. (이 함수는 값을 반환할 필요가 없습니다.)

모범 답안은 [`part_2_python_core_syntax.py`](../../source_code/02_python_core_syntax/part_2_python_core_syntax.py) 파일에서 확인할 수 있습니다.

---

## 11. 트러블슈팅 (Troubleshooting)

코드를 작성하다 보면 예상치 못한 문제들을 마주하게 됩니다. 괜찮습니다! 모든 개발자가 겪는 과정이며, 문제 해결 능력은 뛰어난 개발자의 핵심 역량입니다.

- **`IndentationError`가 발생했나요?**
  - 파이썬은 들여쓰기로 코드 블록을 구분합니다. `if`, `for`, `def` 아래의 코드가 정확히 스페이스 4칸으로 들여쓰기 되었는지 확인하세요.
- **`SyntaxError`는 무엇인가요?**
  - 문법이 틀렸다는 의미입니다. 괄호 `()`, 따옴표 `""`, 콜론 `:` 등을 빠뜨리지 않았는지 확인해보세요.
- **코드가 예상대로 동작하지 않나요?**
  - `print()` 함수를 사용해 중간중간 변수의 값이 어떻게 변하는지 출력해보면 문제의 원인을 찾는 데 큰 도움이 됩니다.

더 자세한 문제 해결 가이드나 다른 동료들이 겪은 문제에 대한 해결책이 궁금하다면, 아래 문서를 참고해주세요.

- **➡️ [Geumdo-Docs: TROUBLESHOOTING.md](../../../TROUBLESHOOTING.md)**

---

## 되짚어보기 (Recap)

- **변수와 자료형**: 모든 데이터는 변수에 담기고, 각 데이터는 고유한 자료형(정수, 문자열 등)을 가집니다.
- **자료구조**: 리스트(변경 가능, 순서 O), 튜플(변경 불가, 순서 O), 딕셔너리(Key-Value, 순서 X), 셋(중복 불가, 순서 X)의 특징을 이해하고 상황에 맞게 사용하는 것이 중요합니다.
- **제어문**: `if`문으로 조건을, `for`/`while`문으로 반복을 제어하며 코드의 흐름을 만듭니다.
- **함수**: `def`를 사용하여 반복되는 코드를 재사용 가능한 '레시피'로 만들어 코드의 구조를 개선합니다.

## 더 깊이 알아보기 (Further Reading)

- [Official Python 3.10.12 documentation](https://docs.python.org/3/)
- [A Byte of Python (한국어)](https://python.bytebank.org/)
- [점프 투 파이썬](https://wikidocs.net/book/1)

---

## 🚀 캡스톤 미니 프로젝트: 간단한 로그 분석기 만들기

지금까지 배운 Python 핵심 문법을 모두 활용하여, 앞으로 진행할 최종 캡스톤 프로젝트의 기반을 다지는 작은 실습을 진행해봅시다. 이번 미니 프로젝트의 목표는 여러 줄의 로그(log) 텍스트 파일을 읽고, 분석하여, 의미 있는 요약 정보를 출력하는 간단한 프로그램을 만드는 것입니다.

이 과정을 통해 여러분은 파일을 읽고, 문자열을 다루고, 배운 자료구조(리스트, 딕셔너리)와 제어문, 함수를 실제 문제에 적용하는 경험을 하게 됩니다.

### 프로젝트 목표

주어진 형식의 로그 파일을 분석하여 다음과 같은 요약 리포트를 출력하는 Python 스크립트(`log_analyzer.py`)를 완성합니다.

**샘플 로그 파일 (`sample.log`):**
```
2023-10-27 10:00:00 INFO: User 'alice' logged in.
2023-10-27 10:01:30 WARNING: Disk space is running low.
2023-10-27 10:02:15 INFO: Data processing started.
2023-10-27 10:05:00 ERROR: Failed to connect to database.
2023-10-27 10:05:05 INFO: User 'bob' logged in.
2023-10-27 10:06:00 ERROR: File not found: 'data.csv'.
```

**예상 출력 결과:**
```
Log Analysis Report
--------------------
Total Logs: 6
Log Levels Count:
  - INFO: 3
  - WARNING: 1
  - ERROR: 2
```

### 단계별 구현 가이드

아래 단계에 따라 함수를 하나씩 완성해보세요.

**1. 로그 파일 읽기 함수**
- `read_logs(file_path)` 라는 이름의 함수를 만드세요.
- 이 함수는 파일 경로를 입력받아, 파일의 각 줄을 리스트의 요소로 담아 반환해야 합니다.
- (힌트) 파일 처리는 다음과 같은 `with open(...)` 구문을 사용하면 편리합니다.
  ```