# Basic Python

> `새해를 맞이하여 기본에 충실하기 위한 한달 back to basic til project`

# 준비

- setting

    - Python [3.xx](https://3.xx)

    - Jupyter Notebook(or JupyterLab)

    - 라이브러리: numpy pandas requests beautifulsoup4

- 폴더구조

    - notebooks/

    - src/

    - data/raw, data/processed

    - reports

---

# road map

1. 기초 문법 & 타입 & 문자열 & 주피터

2. 데이터 구조(list/tuple/dict/set

3. 조건/루프/함수/예외/OOP

4. 파일 입출력 + Numpy/Pandas

5. REST API + 웹 스크래핑 + 종합

---

# practice

### 1 week

20260120 - 파이썬 기초 + 데이터 구조

- 목표 : laptop에서 코드 실행이 자연스럽고 타입/변수/연산에 익숙해지기!

- [x] 정수/실수/문자열/bool 타입 변환

code

```javascript
def show(title: str) -> None:
    print("\n" + "=" * 10, title, "=" * 10)

def main() -> None:
    show("type check")
    a = 10
    b = 3.14
    c = "123"
    d = True
    print(a, type(a))
    print(b, type(b))
    print(c, type(c))
    print(d, type(d))

    show("type casting")
    print(int("123"), type(int("123")))
    print(float("3.14"), type(float("3.14")))
    print(str(10), type(str(10)))

    show("bool rules")
    print(bool(""), bool("0"), bool("hello"))
    print(bool(0), bool(1), bool(-1))
    print(bool([]), bool([1]))
    print(bool({}), bool({"a": 1}))

    show("mission 1: '  003  ' -> 3")
    s = "  003  "
    x = int(s.strip())
    print(x, type(x))

    show("mission 2: '3.0' -> 3")
    y = int(float("3.0"))
    print(y, type(y))

if __name__ == "__main__":
    main()

```

- [x] 연산자 우선순위 공부

code

```javascript
def show(title: str) -> None:
    print("\n" + "=" * 10, title, "=" * 10)

def main() -> None:
    show("precedence basics")
    print("10 + 2 * 3 =", 10 + 2 * 3)
    print("(10 + 2) * 3 =", (10 + 2) * 3)
    print("2 ** 3 ** 2 =", 2 ** 3 ** 2)  # 오른쪽 결합

    show("division")
    print("7 / 2 =", 7 / 2)
    print("7 // 2 =", 7 // 2)
    print("7 % 2 =", 7 % 2)

    show("comparisons + logic")
    a, b = 12, 3
    print("a > 10 and b < 5 =", a > 10 and b < 5)
    print("not (a > 10 and b < 5) =", not (a > 10 and b < 5))
    print("a == 12 or b == 5 =", a == 12 or b == 5)

    show("mission: classify number")
    n = 0
    if n > 0:
        print("positive")
    elif n == 0:
        print("zero")
    else:
        print("negative")

if __name__ == "__main__":
    main()
```

- [x] print formatting

code

```javascript

def main() -> None:
    name = "thsqh"
    price = 12900
    qty = 3
    total = price * qty

    print("\n1) f-string basics")
    print(f"name={name}")
    print(f"price={price}, qty={qty}, total={total}")

    print("\n2) float formatting")
    latency_ms = 123
    print(f"latency={latency_ms}ms, latency_sec={latency_ms/1000:.3f}s")

    print("\n3) alignment")
    for x in [3, 30, 300]:
        print(f"x right aligned: |{x:>6}|")
        print(f"x left  aligned: |{x:<6}|")

    print("\n4) repr (!r) - 공백/이스케이프 확인용")
    s = " hello\tworld\n"
    print(f"raw: {s!r}")

if __name__ == "__main__":
    main()

```

20260121 - 문자열 집중

- 목표 : 인덱싱/슬라이싱/escape/formatting 체득

- [x] 슬라이싱으로 이메일에서 도메인 추출

code

```javascript
def show(title: str) -> None:
    print("\n" + "=" * 10, title, "=" * 10)

def extract_user_domain(email: str) -> tuple[str, str]:
    email = email.strip()
    at = email.find("@")
    if at == -1:
        raise ValueError(f"Invalid email: {email!r}")
    user = email[:at]
    domain = email[at + 1 :]
    return user, domain

def clean_csv_like(s: str) -> list[str]:
    # "  a, b ,c  " -> ["a", "b", "c"]
    s = s.strip()
    parts = [p.strip() for p in s.split(",")]
    return parts

def parse_log(line: str) -> dict:
    # 예: '2026-01-21 09:12:33 INFO user_id=42 status=200 path="/api/v1/auth"'
    line = line.strip()
    date, time, level, rest = line.split(" ", maxsplit=3)
    out = {"date": date, "time": time, "level": level}

    # path="...": 따옴표 값 먼저 추출
    key = 'path="'
    if key in rest:
        start = rest.find(key) + len(key)
        end = rest.find('"', start)
        out["path"] = rest[start:end]
        rest = (rest[: rest.find(key)] + rest[end + 1 :]).strip()

    for kv in rest.split():
        if "=" not in kv:
            continue
        k, v = kv.split("=", 1)
        v = v.strip()
        out[k] = int(v) if v.isdigit() else v
    return out

def main() -> None:
    show("indexing / slicing")
    e = " admin@company.co.kr "
    user, domain = extract_user_domain(e)
    print(f"email={e!r} -> user={user!r}, domain={domain!r}")
    print("domain first char:", domain[0])
    print("domain last char:", domain[-1])

    show("split / join / strip / replace")
    s = "  a, b ,c  "
    parts = clean_csv_like(s)
    print("parts:", parts)
    joined = "|".join(parts)
    print("joined:", joined)
    print("replaced:", joined.replace("b", "B"))

    show("escape + formatting")
    msg = "He said: \"hello\"\nNext\tTabbed"
    print(msg)
    print(f"repr: {msg!r}")

    show("log parsing")
    log = '2026-01-21 09:12:33 INFO user_id=42 action=login latency_ms=123 status=200 path="/api/v1/auth"'
    print(parse_log(log))

if __name__ == "__main__":
    main()

```

- [x] split/join/strip/replace

code

```javascript
def parse_log(line: str) -> dict:
    line = line.strip()
    date, time, level, rest = line.split(" ", maxsplit=3)
    out = {"date": date, "time": time, "level": level}

    i = 0
    n = len(rest)

    while i < n:
        # 공백 스킵
        while i < n and rest[i] == " ":
            i += 1
        if i >= n:
            break

        # key 읽기
        eq = rest.find("=", i)
        if eq == -1:
            break
        key = rest[i:eq]
        i = eq + 1

        # value 읽기 (따옴표면 "..." 끝까지, 아니면 공백 전까지)
        if i < n and rest[i] == '"':
            i += 1
            endq = rest.find('"', i)
            if endq == -1:
                raise ValueError("Unclosed quote in log value")
            val = rest[i:endq]
            i = endq + 1
        else:
            # 공백 전까지
            j = i
            while j < n and rest[j] != " ":
                j += 1
            val = rest[i:j]
            i = j

        out[key] = int(val) if val.isdigit() else val

    return out

```

- [x] 간단한 로그 한 줄 파싱

code

```javascript
# src/day2_strings.py

import json

from pathlib import Path
from asyncio import log

def show(title: str) -> None:
    print("\n" + "=" * 10, title, "=" * 10)

def extract_user_domain(email: str) -> tuple[str, str]:
    email = email.strip()
    at = email.find("@")
    if at == -1:
        raise ValueError(f"Invalid email: {email!r}")
    user = email[:at]
    domain = email[at + 1:]
    return user, domain

def clean_csv_like(s: str) -> list[str]:
    # "  a, b ,c  " -> ["a", "b", "c"]
    s = s.strip()
    parts = [p.strip() for p in s.split(",")]
    return parts

def parse_log(line: str) -> dict:
    line = line.strip()
    date, time, level, rest = line.split(" ", maxsplit=3)
    out = {"date": date, "time": time, "level": level}

    i = 0
    n = len(rest)

    while i < n:
        # 공백 스킵
        while i < n and rest[i] == " ":
            i += 1
        if i >= n:
            break

        # key 읽기
        eq = rest.find("=", i)
        if eq == -1:
            break
        key = rest[i:eq]
        i = eq + 1

        # value 읽기 (따옴표면 "..." 끝까지, 아니면 공백 전까지)
        if i < n and rest[i] == '"':
            i += 1
            endq = rest.find('"', i)
            if endq == -1:
                raise ValueError("Unclosed quote in log value")
            val = rest[i:endq]
            i = endq + 1
        else:
            # 공백 전까지
            j = i
            while j < n and rest[j] != " ":
                j += 1
            val = rest[i:j]
            i = j

        out[key] = int(val) if val.isdigit() else val

    return out

def validate(d: dict) -> list[str]:
    errors = []
    status = d.get("status")
    user_id = d.get("user_id")
    path = d.get("path")

    if not (isinstance(status, int) and 100 <= status <= 599):
        errors.append(f"bad status={status}")
    if not (isinstance(user_id, int) and user_id > 0):
        errors.append(f"bad user_id={user_id}")
    if not (isinstance(path, str) and path.startswith("/")):
        errors.append(f"bad path={path!r}")
    return errors

def main() -> None:
    raw_path = Path("data/raw/sample.log")
    ok_rows = []
    bad_rows = []

    for idx, line in enumerate(raw_path.read_text(encoding="utf-8").splitlines(), start=1):
        if not line.strip():
            continue
        d = parse_log(line)
        errs = validate(d)
        if errs:
            bad_rows.append({"line_no": idx, "errors": errs, "row": d})
        else:
            ok_rows.append(d)
            Path("data/processed/parsed_logs.json").write_text(
                json.dumps(ok_rows, ensure_ascii=False, indent=2),
                encoding="utf-8"
            )
    Path("data/processed/parsed_logs_errors.json").write_text(
        json.dumps(bad_rows, ensure_ascii=False, indent=2),
        encoding="utf-8"
    )

    print(f"OK={len(ok_rows)}, BAD={len(bad_rows)}")

    show("indexing / slicing")
    e = " admin@company.co.kr "
    user, domain = extract_user_domain(e)
    print(f"email={e!r} -> user={user!r}, domain={domain!r}")
    print("domain first char:", domain[0])
    print("domain last char:", domain[-1])

    show("split / join / strip / replace")
    s = "  a, b ,c  "
    parts = clean_csv_like(s)
    print("parts:", parts)
    joined = "|".join(parts)
    print("joined:", joined)
    print("replaced:", joined.replace("b", "B"))

    show("escape + formatting")
    msg = "He said: \"hello\"\nNext\tTabbed"
    print(msg)
    print(f"repr: {msg!r}")

    show("log parsing")
    log = '2026-01-21 09:12:33 INFO user_id=42 status=200 path="/api v1/auth" msg="login failed: bad password"'
    print(parse_log(log))

    d = parse_log(log)
    assert 100 <= d.get("status", 0) < 599, f"bad status: {d.get('status')}"
    assert d.get("user_id", 0) > 0, f"bad user_id: {d.get('user_id')}"
    assert isinstance(d.get("path"), str) and d["path"].startswith(
        "/"), f"bad path: {d.get('path')}"

    print("quality checks: OK")
    print(d)

    out_path = Path("data/processed/parsed_log.json")
    out_path.write_text(json.dumps(
        d, ensure_ascii=False, indent=2), encoding="utf-8")
    print(f"saved -> {out_path}")

if __name__ == "__main__":
    main()

```

20260122 - list/tuple

- 목표 : 데이터를 담고 가공하는 손맛 익히기

- [ ] append/extend/insert/pop/remove

- [ ] 슬라이싱, 정령 key사용

- [ ] list comprehension 10문제 자문자답

20260123 - dict/set

- 목표 : key value 기반 처리 + 중복제거/집합연산

- [ ] 카운팅 만들기 : dict

- [ ] get, items, defaultdict

- [ ] set으로 중복제거 + 교집합/차집합

## ✅1 week mini project - 텍스트 분석기

- 목표 :

    - 입력 : 텍스트 파일

    - 출력 : 단어 빈도 Top 20, 문장 수, 평균 단어 길이

    - 저장 : 결과물 result.json으로 저장

---

### 2 weeks

20260126 - 조건/분기 (if)

- 목표 : 비교/논리 연산으로 프로그램 흐름 제어 익히기

□ 비교 연산자(==, !=, >, >=, <, <=) 연습

□ 논리 연산자(and/or/not) + 우선순위 확인

□ if/elif/else로 구간별 조건 처리(요금/등급 계산기)

□ 조건식 단축 표현(삼항 연산자) 3문제

20260127 - 루프 (for/while)

- 목표 : 반복 패턴(누적/탐색/필터)을 손에 익히기

□ for 기본(리스트/문자열/dict items 순회)

□ while 기본 + break/continue 사용

□ 누적합/최댓값/최솟값 직접 구현(내장함수 없이 1번)

□ 중첩 루프 1문제(2중까지만)

20260128 - 함수 (functions)

- 목표 : 입력/출력 설계 + 코드 재사용 습관 만들기

□ 함수 정의/호출, return 이해

□ default/keyword arguments 예제 5개

□ "유효성 검사 함수" 2개 만들기(예: 이메일/숫자범위)

□ 작은 로직 3개를 함수로 쪼개서 리팩토링

20260129 - 예외 처리 (try/except)

- 목표 : "안 죽는 코드" 만들기(실패 케이스 다루기)

□ try/except/else/finally 흐름 정리

□ FileNotFoundError / ValueError 처리 실습

□ 사용자 입력 재시도 로직(최대 3회)

□ raise로 예외 발생시키는 케이스 2개 만들기

20260130 - OOP 기초 (class)

- 목표 : 객체/클래스로 현실 문제 모델링 감 잡기

□ class / init / 메서드 / 속성 개념 정리

□ Dataset 클래스 만들기(load/save/summary)

□ 객체 2개 생성해서 상태(state) 변화 확인

□ (선택) repr 구현해서 보기 좋게 출력

## ✅2 weeks mini project - CSV정리 도구

- 목표 : 파일 I/O + 예외처리 + 함수(또는 클래스)로 "작은 도구" 완성

□ input CSV 읽기(read_csv 대신 csv 모듈로 1번 해보기 선택)

□ 결측치 처리 규칙 정하기(삭제/대체)

□ 숫자/날짜 형 변환 + 실패 케이스 예외 처리

□ 결과를 cleaned.csv로 저장 + 요약 리포트(report.txt) 생성

□ README에 "사용법/입력/출력/규칙" 간단히 작성

---

### 3 weeks

20260202 - NumPy 핵심

- 목표 : 배열 사고방식 + 벡터화 맛보기

□ ndarray 생성/shape/dtype 다뤄보기

□ 인덱싱/슬라이싱/boolean mask 5문제

□ 기본 통계(mean/std/min/max) + 조건 필터링

□ (선택) 브로드캐스팅 예제 2개

20260203 - Pandas I (입출력/조회/정제)

- 목표 : 표 데이터 기본 조작 루틴 만들기

□ read_csv/read_json + to_csv 저장

□ loc/iloc 차이 예제로 정리

□ 필터링(조건 여러개) 5문제

□ 결측치 isna/fillna/dropna 비교 정리

20260204 - Pandas II (groupby/merge)

- 목표 : 분석의 핵심(집계/조인) 패턴 익히기

□ groupby 집계 5문제(count/sum/mean)

□ pivot_table 1개 만들어보기(선택)

□ merge로 조인 2번(기본키/복합키 느낌)

□ "전처리 → 집계 → 결과 저장" 흐름 1번 완주

20260205 - REST API (requests)

- 목표 : API에서 JSON 받아서 저장/분석까지

□ requests.get + status_code/timeout 처리

□ params(querystring) 넣어서 호출

□ JSON 파싱 → DataFrame 변환 → csv 저장

□ 실패 케이스(429/500 등) 대비해서 예외 처리

20260206 - 웹 스크래핑 (BeautifulSoup)

- 목표 : HTML에서 원하는 데이터 뽑아오기

□ soup 만들기 + find/select 차이 정리

□ 리스트/표(table)에서 데이터 추출

□ 추출 결과를 DataFrame으로 변환 후 저장

□ robots.txt/요청 간격(매너) 체크 메모 남기기

## ✅3 weeks capstone : 웹 데이터 수집 → 정제 → 분석

- 목표 : "API + 스크래핑 + Pandas" 파이프라인 1개 완성

□ API 데이터 수집(JSON) → raw 저장

□ 스크래핑 데이터 수집(HTML) → raw 저장

□ 정제 후 merge + groupby로 요약 테이블 생성

□ processed.csv 저장 + 노트북 리포트(TIL) 작성

□ 최소 조건: merge 1회, groupby 1회, 예외처리 1회 포함

---

### 4 weeks

20260209 - 리팩토링(함수/모듈화) + 구조 잡기

- 목표 : "노트북만 있는 프로젝트" → "재사용 가능한 코드"로 정리

□ notebooks/ src/ data/ reports/ 구조로 정리

□ 핵심 로직을 src로 이동(수집/정제/분석 함수 분리)

□ config(상수/경로/URL) 분리

□ 재실행해도 같은 결과가 나오도록(idempotent) 정리

20260210 - 로깅/에러처리/CLI 맛보기

- 목표 : 실무 느낌(실행/로그/에러)을 최소로라도 갖추기

□ print 대신 logging 적용(기본 INFO 레벨)

□ 실패 케이스 메시지 정리(어디서 왜 실패했는지)

□ argparse로 간단 CLI 만들기(예: --start-date, --out)

□ (선택) 재시도(backoff) 1회 적용

20260211 - 테스트/문서화/README 포트폴리오 마감

- 목표 : 깃허브 올렸을 때 "바로 이해되는" 상태로 마감

□ 핵심 함수 2~3개 pytest로 테스트

□ requirements.txt 작성

□ README에 설치/실행/예시 결과/데이터 출처 작성

□ TIL에 "배운 점/트러블슈팅/개선 포인트" 정리

□ (선택) 결과 요약 그래프 1개(matplotlib) 추가

---

For the site tree, see the [root Markdown](https://slashpage.com/sonbo.md).
