← 파이썬 강의 목록으로
🚀
고급 (Advanced)
requests · BeautifulSoup · session · robots.txt · ndarray · DataFrame · groupby · CSV · venv · pyproject.toml · console_scripts · TestPyPI · 타입 + 비동기 + 외부 라이브러리 + 테스트 + 배포

7~10주차 — 웹 스크래핑 · NumPy / Pandas 입문 · 패키징과 배포 · 고급 종합 실습

requests 로 HTTP 요청, BeautifulSoup 로 HTML 파싱, 세션·쿠키 관리, 그리고 robots.txt 와 매너까지 — 윤리적 스크래핑의 기본을 익힙니다. NumPy 배열 연산과 인덱싱, Pandas DataFrame 의 필터·정렬·groupby, 그리고 CSV 읽고 분석하기까지 — 데이터 처리의 두 거인을 만납니다. 가상환경, pyproject.toml 의 구조, console_scripts 로 CLI 도구 제공, 그리고 TestPyPI 업로드까지 — 자체 패키지를 만들어 배포하는 전체 흐름. 비동기·외부 라이브러리·테스트·배포를 통합한 실전 프로젝트. 패키지 구조 + CI 적용까지 포함합니다.

requestsBeautifulSoupscrapingethicsnumpypandasDataFramegroupbyvenvpyproject.tomlbuildtwinecapstoneasyncpytestpackaging
소요 시간
⏱ 2시간 + 2시간 + 2시간 + 프로젝트형
난이도
📊 고급
선수 조건
🎯 고급 6주차
결과물
정중하고 견고한 스크래퍼 작성 / DataFrame 으로 CSV 데이터 분석 / 자체 패키지 빌드와 배포 / 통합 패키지 프로젝트 완성

이 강의에서 배우는 것

  • 1requests 로 HTTP GET/POST 요청을 한다
  • 2BeautifulSoup 로 HTML 을 파싱한다
  • 3헤더·세션·쿠키를 다룬다
  • 4robots.txt 와 매너를 지킨다
  • 5NumPy 배열 생성·연산·인덱싱을 한다
  • 6Pandas DataFrame 의 기본 조작을 익힌다
  • 7CSV 를 읽고 필터·집계한다
  • 8groupby 와 피벗 테이블을 사용한다
  • 9pyproject.toml 의 구조를 이해한다
  • 10자체 패키지를 빌드해서 설치한다
  • 11console_scripts 로 CLI 도구를 제공한다
  • 12TestPyPI 에 업로드한다
  • 13비동기·외부 라이브러리·테스트·배포를 통합한 실전 프로젝트를 완성한다
  • 14패키지 구조 + CI 를 적용한다

7주차 — 웹 스크래핑

requests 로 HTTP 요청, BeautifulSoup 로 HTML 파싱, 세션·쿠키 관리, 그리고 robots.txt 와 매너까지 — 윤리적 스크래핑의 기본을 익힙니다.

1. requests

python
import requests

r = requests.get("https://example.com")
print(r.status_code)        # 200
print(r.text[:200])
print(r.headers["Content-Type"])

# JSON API
r = requests.get("https://api.github.com/users/python")
data = r.json()
print(data["public_repos"])

2. POST 요청

python
r = requests.post("https://httpbin.org/post",
    data={"key": "value"},
    headers={"User-Agent": "MyBot 1.0"},
)

3. BeautifulSoup

bash
pip install beautifulsoup4 lxml
python
from bs4 import BeautifulSoup

html = """<html><body>
    <h1>제목</h1>
    <ul>
        <li class="item">A</li>
        <li class="item">B</li>
    </ul>
</body></html>"""

soup = BeautifulSoup(html, "html.parser")
print(soup.h1.text)
print([li.text for li in soup.find_all("li")])
print([li.text for li in soup.select("li.item")])   # CSS 선택자

4. 세션과 쿠키

python
session = requests.Session()
session.headers.update({"User-Agent": "Mozilla/5.0"})

# 로그인
session.post("https://example.com/login", data={"user": "x", "pass": "y"})

# 같은 세션 → 쿠키 유지
r = session.get("https://example.com/me")

5. 매너와 윤리

  • robots.txt 확인: https://example.com/robots.txt
  • 요청 간격: time.sleep(1) 정도 권장
  • 사용자 에이전트: 정직하게 표시
  • 저작권·약관 준수
⚠️

스크래핑은 합법성과 윤리가 중요한 영역입니다. 사이트의 이용 약관과 robots.txt 를 반드시 확인하세요.

6. 실전 패턴

python
import requests
from bs4 import BeautifulSoup

URL = "https://news.ycombinator.com"
HEADERS = {"User-Agent": "Mozilla/5.0 (study)"}

def fetch_titles():
    r = requests.get(URL, headers=HEADERS, timeout=10)
    r.raise_for_status()
    soup = BeautifulSoup(r.text, "html.parser")
    titles = [a.get_text() for a in soup.select(".titleline > a")]
    return titles[:10]

for i, title in enumerate(fetch_titles(), 1):
    print(f"{i}. {title}")

자주 하는 실수

  1. timeout 누락 — 무한 대기. 항상 timeout=10
  2. raise_for_status() 안 함 — 404 못 알아챔
  3. HTML 파서 미지정 — 명시적으로 'html.parser'
  4. 선택자 깨지기 쉬움 — id/class 변동에 취약
  5. 차단당함 — 너무 빠른 요청. time.sleep 으로 간격

FAQ

Q1. JS 렌더링 페이지? — Selenium, Playwright 사용.

Q2. 동적 사이트? — 보통 내부 API 직접 호출(개발자 도구 → 네트워크 탭).

Q3. 로그인 후 페이지? — Session 사용. CSRF 토큰까지.

8주차 — NumPy / Pandas 입문

NumPy 배열 연산과 인덱싱, Pandas DataFrame 의 필터·정렬·groupby, 그리고 CSV 읽고 분석하기까지 — 데이터 처리의 두 거인을 만납니다.

0. 설치

bash
pip install numpy pandas matplotlib

1. NumPy 기본

python
import numpy as np

a = np.array([1, 2, 3, 4, 5])
print(a.mean(), a.std(), a.sum())

# 벡터 연산 (Python list보다 훨씬 빠름)
print(a * 2)        # [2 4 6 8 10]
print(a + a)
print(a > 2)        # 불리언 배열

# 2D
m = np.arange(12).reshape(3, 4)
print(m)
print(m.shape)
print(m.sum(axis=0))   # 컬럼 합
print(m.sum(axis=1))   # 행 합

2. NumPy 인덱싱

python
a = np.arange(10)
print(a[3])
print(a[2:6])
print(a[a > 5])      # 불리언 인덱싱

m = np.arange(12).reshape(3, 4)
print(m[1, 2])       # 행 1, 열 2
print(m[:, 1])       # 모든 행, 열 1
print(m[1, :])       # 행 1

3. Pandas DataFrame

python
import pandas as pd

df = pd.DataFrame({
    "name": ["Alice", "Bob", "Charlie", "Dave"],
    "score": [92, 78, 85, 67],
    "dept": ["A", "B", "A", "B"],
})

print(df)
print(df.describe())
print(df.shape, df.columns.tolist())

4. 필터·정렬·집계

python
# 필터
print(df[df.score >= 80])

# 정렬
print(df.sort_values("score", ascending=False))

# 그룹 집계
print(df.groupby("dept")["score"].mean())
print(df.groupby("dept").agg({"score": ["mean", "max", "count"]}))

5. CSV 읽기

python
df = pd.read_csv("sales.csv")
df["date"] = pd.to_datetime(df["date"])
monthly = df.groupby(df["date"].dt.to_period("M"))["amount"].sum()
print(monthly)

6. 시각화 (matplotlib)

python
import matplotlib.pyplot as plt

monthly.plot(kind="bar")
plt.title("월별 매출")
plt.tight_layout()
plt.savefig("monthly.png")

자주 하는 실수

  1. for 루프로 NumPy 배열 처리 — 벡터 연산 사용해야 빠름
  2. SettingWithCopyWarning — df[df.x > 0]['y'] = 5 대신 .loc[]
  3. 인덱스 vs 컬럼 혼동 — df['a'] 는 컬럼, df.loc[a] 는 인덱스
  4. NaN 무시 — dropna(), fillna() 로 명시적 처리

FAQ

Q1. NumPy vs list? — NumPy는 동일 타입, 메모리 연속, 벡터 연산. 수치 계산에서 100배 이상 빠름.

Q2. Pandas vs SQL? — Pandas는 메모리 내. SQL은 영구 저장 + 큰 데이터.

Q3. polars 가 더 빠르다는데? — 맞음. 큰 데이터에서 좋음. 다만 생태계는 pandas 압도적.

9주차 — 패키징과 배포

가상환경, pyproject.toml 의 구조, console_scripts 로 CLI 도구 제공, 그리고 TestPyPI 업로드까지 — 자체 패키지를 만들어 배포하는 전체 흐름.

1. 가상환경

프로젝트마다 라이브러리 버전을 분리합니다.

bash
python -m venv .venv

# 활성화
source .venv/bin/activate         # macOS/Linux
.venv\Scripts\activate            # Windows

# 종료
deactivate

2. pyproject.toml

현대 파이썬 패키지의 표준 설정 파일.

toml
[project]
name = "mytool"
version = "0.1.0"
description = "내 멋진 CLI 도구"
authors = [{name = "홍길동", email = "hong@example.com"}]
requires-python = ">=3.10"
dependencies = [
    "requests>=2.28",
    "rich>=13.0",
]

[project.scripts]
mytool = "mytool.cli:main"

[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"

3. 패키지 구조

text
mytool_project/
├── pyproject.toml
├── README.md
├── LICENSE
├── src/
│   └── mytool/
│       ├── __init__.py
│       └── cli.py
└── tests/
    └── test_cli.py
python
# src/mytool/cli.py
def main():
    print("Hello from mytool!")

설치:

bash
pip install -e .          # 개발 모드 (수정이 즉시 반영)
mytool                    # CLI 실행

4. 빌드

bash
pip install build
python -m build           # dist/ 에 .whl, .tar.gz 생성

5. TestPyPI 업로드

bash
pip install twine
twine upload --repository testpypi dist/*

다른 환경에서 설치:

bash
pip install --index-url https://test.pypi.org/simple/ mytool

6. requirements.txt vs pyproject.toml

requirements.txtpyproject.toml
용도환경 고정 (deploy)라이브러리 메타데이터
버전정확한 핀범위
빌드안 됨됨
bash
pip freeze > requirements.txt   # 현재 환경 내보내기
pip install -r requirements.txt # 재현

자주 하는 실수

  1. 가상환경 활성화 안 함 — 시스템 파이썬에 설치됨
  2. __init__.py 빠뜨림 — 패키지로 인식 안 됨
  3. src/ 레이아웃 안 씀 — import 충돌 가능
  4. 버전 안 올리고 재배포 — PyPI는 같은 버전 거부

FAQ

Q1. setup.py 는? — 신규는 pyproject.toml. 레거시 호환만 setup.py.

Q2. poetry vs pip+venv? — poetry는 통합 도구. 신규는 poetry나 uv 권장.

Q3. PyPI vs TestPyPI? — PyPI는 진짜 공개. TestPyPI는 연습용.

10주차 — 고급 종합 실습

비동기·외부 라이브러리·테스트·배포를 통합한 실전 프로젝트. 패키지 구조 + CI 적용까지 포함합니다.

1. 프로젝트 구조 (권장)

text
my_project/
├── pyproject.toml
├── README.md
├── src/my_project/
│   ├── __init__.py
│   ├── models.py        (dataclass)
│   ├── client.py        (httpx 비동기)
│   ├── analyzer.py      (Pandas)
│   └── cli.py           (argparse 진입점)
├── tests/
│   ├── test_models.py
│   ├── test_client.py
│   └── test_analyzer.py
└── .github/workflows/ci.yml   (선택)

2. 골격 — 비동기 크롤러 + 분석

models.py: dataclass 로 도메인 정의 → client.py: httpx 비동기 fetch → analyzer.py: Pandas 집계 → cli.py: argparse 로 진입점.

3. 체크리스트

  • 타입 힌트 + mypy --strict
  • pytest 테스트 (커버리지 70%+)
  • CLI (argparse 또는 click)
  • 비동기 또는 병렬 처리
  • 외부 라이브러리 (requests/httpx, pandas 등)
  • pyproject.toml 패키지화
  • README (설치법, 사용법)

자주 하는 실수

  1. 모놀리식 한 파일 — 테스트도 어렵고 재사용도 어려움
  2. 타입 힌트 누락 — IDE 지원 안 받음
  3. 테스트 없이 시작 — 나중에 더 어려움
  4. README 없음 — 본인도 며칠 후 잊어버림

FAQ

Q1. CI 가 뭔가요? — Continuous Integration. push마다 자동 테스트(GitHub Actions).

Q2. 어떤 도메인? — 본인이 진짜 쓸 만한 것. 뉴스 요약, 공부 시간 추적, 시세 알림 등.

최종 과제 — 4개 중 1개 선택

  1. 비동기 크롤러 + 분석
  2. CLI 도구 패키지 (PyPI 배포)
  3. 데이터 파이프라인 (CSV → 정제 → 시각화)
  4. 자동화 봇 (Slack/Discord 웹훅)

다음 단계

고급 과정 완주 🎉. 이후 도메인별 학습:

  • 백엔드: FastAPI / Django REST framework
  • 데이터·ML: PyTorch / scikit-learn / Hugging Face
  • 자동화·DevOps: Ansible / Airflow / Docker
  • 웹 스크래핑: Scrapy / Playwright

💻 예제 (examples)

실제로 실행해 결과를 확인할 수 있는 예제 코드입니다.

01_requests_basic.py— GET, JSON
CODE
import requests

r = requests.get("https://httpbin.org/get", params={"q": "python"}, timeout=10)
print(r.status_code)
print(r.json()["args"])
▶ 실행 결과
200
{'q': 'python'}
02_bs4_parse.py— HTML 파싱
CODE
from bs4 import BeautifulSoup

html = """<html><body>
<h1>제목</h1>
<ul>
  <li class="item">A</li>
  <li class="item">B</li>
  <li class="item">C</li>
</ul>
</body></html>"""

soup = BeautifulSoup(html, "html.parser")
print(soup.h1.text)
print([li.text for li in soup.select("li.item")])
▶ 실행 결과
제목
['A', 'B', 'C']
03_session.py— 세션 + 쿠키
CODE
import requests

s = requests.Session()
s.headers.update({"User-Agent": "study/1.0"})

# httpbin이 보낸 쿠키를 받아서 그대로 사용
s.get("https://httpbin.org/cookies/set/sessionid/abc123")
r = s.get("https://httpbin.org/cookies")
print(r.json())
▶ 실행 결과
{'cookies': {'sessionid': 'abc123'}}
04_scrape_demo.py— 실제 스크래핑 (httpbin)
CODE
import requests
from bs4 import BeautifulSoup

r = requests.get("https://httpbin.org/html", timeout=10)
r.raise_for_status()
soup = BeautifulSoup(r.text, "html.parser")
print("h1:", soup.h1.get_text(strip=True))
print("p 개수:", len(soup.find_all("p")))
▶ 실행 결과
h1: Herman Melville - Moby-Dick
p 개수: 1
01_numpy_basic.py— 배열, 연산
CODE
import numpy as np

a = np.array([1, 2, 3, 4, 5])
print("평균:", a.mean())
print("표준편차:", round(a.std(), 4))
print("합계:", a.sum())
print("배수:", a * 2)
print("불리언:", a > 2)
▶ 실행 결과
평균: 3.0
표준편차: 1.4142
합계: 15
배수: [ 2  4  6  8 10]
불리언: [False False False  True  True]
02_numpy_indexing.py— 인덱싱·슬라이싱
CODE
import numpy as np

m = np.arange(12).reshape(3, 4)
print(m)
print("m[1, 2] =", m[1, 2])
print("m[:, 1] =", m[:, 1])
print("m[1, :] =", m[1, :])
print("axis=0 합:", m.sum(axis=0))
print("axis=1 합:", m.sum(axis=1))
▶ 실행 결과
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
m[1, 2] = 6
m[:, 1] = [1 5 9]
m[1, :] = [4 5 6 7]
axis=0 합: [12 15 18 21]
axis=1 합: [ 6 22 38]
03_pandas_basic.py— DataFrame 생성·조회
CODE
import pandas as pd

df = pd.DataFrame({
    "name": ["Alice", "Bob", "Charlie"],
    "score": [92, 78, 85],
})
print(df)
print("---")
print("평균:", df["score"].mean())
print("최대:", df["score"].max())
▶ 실행 결과
      name  score
0    Alice     92
1      Bob     78
2  Charlie     85
---
평균: 85.0
최대: 92
04_pandas_groupby.py— 필터·groupby
CODE
import pandas as pd

df = pd.DataFrame({
    "name": ["Alice", "Bob", "Charlie", "Dave"],
    "score": [92, 78, 85, 67],
    "dept": ["A", "B", "A", "B"],
})

print(df[df.score >= 80])
print("---")
print(df.groupby("dept")["score"].mean())
▶ 실행 결과
      name  score dept
0    Alice     92    A
2  Charlie     85    A
---
dept
A    88.5
B    72.5
Name: score, dtype: float64
pyproject.toml— 패키지 설정 (전체)
CODE
[project]
name = "mytool"
version = "0.1.0"
description = "내 멋진 CLI 도구"
authors = [{name = "홍길동", email = "hong@example.com"}]
requires-python = ">=3.10"
dependencies = ["requests>=2.28"]

[project.scripts]
mytool = "mytool.cli:main"

[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"
▶ 실행 결과
(설정 파일 — 직접 출력은 없음)
src/mytool/__init__.py— 패키지 진입
CODE
__version__ = "0.1.0"
▶ 실행 결과
(import 시 활성화)
src/mytool/cli.py— CLI 진입점
CODE
import argparse

def main():
    parser = argparse.ArgumentParser(prog="mytool")
    parser.add_argument("name", help="이름")
    parser.add_argument("--upper", action="store_true")
    args = parser.parse_args()
    msg = f"Hello, {args.name}!"
    print(msg.upper() if args.upper else msg)

if __name__ == "__main__":
    main()
▶ 실행 결과
$ mytool 홍길동
Hello, 홍길동!
$ mytool 홍길동 --upper
HELLO, 홍길동!
build_demo.sh— 빌드와 로컬 설치
CODE
# 가상환경
python -m venv .venv
source .venv/bin/activate

# 개발 설치
pip install -e .

# 실행
mytool 세상

# 빌드
pip install build
python -m build
▶ 실행 결과
Hello, 세상!
Successfully built mytool-0.1.0.tar.gz mytool-0.1.0-py3-none-any.whl
models.py— dataclass 도메인
CODE
from dataclasses import dataclass, field

@dataclass
class Article:
    title: str
    url: str
    score: int = 0
    tags: list[str] = field(default_factory=list)

a = Article("파이썬 비동기", "https://x", score=120, tags=["python", "async"])
print(a)
▶ 실행 결과
Article(title='파이썬 비동기', url='https://x', score=120, tags=['python', 'async'])
client.py— httpx 비동기 fetch
CODE
import asyncio
import httpx

async def fetch_one(client, url):
    r = await client.get(url, timeout=10)
    return r.status_code, len(r.text)

async def fetch_all(urls):
    async with httpx.AsyncClient() as client:
        return await asyncio.gather(*(fetch_one(client, u) for u in urls))

if __name__ == "__main__":
    urls = ["https://example.com"] * 3
    print(asyncio.run(fetch_all(urls)))
▶ 실행 결과
[(200, 1256), (200, 1256), (200, 1256)]
analyzer.py— Pandas 집계
CODE
import pandas as pd

def by_dept(df: pd.DataFrame) -> pd.Series:
    return df.groupby("dept")["score"].mean()

if __name__ == "__main__":
    df = pd.DataFrame({"name": ["A", "B", "C", "D"], "score": [90, 70, 80, 60], "dept": ["X", "Y", "X", "Y"]})
    print(by_dept(df))
▶ 실행 결과
dept
X    85.0
Y    65.0
Name: score, dtype: float64
cli.py— argparse 진입점
CODE
import argparse

def main():
    p = argparse.ArgumentParser(prog="myapp")
    sub = p.add_subparsers(dest="cmd", required=True)
    sub.add_parser("crawl")
    a = sub.add_parser("analyze")
    a.add_argument("path")
    args = p.parse_args()

    if args.cmd == "crawl":
        print("크롤 시작")
    elif args.cmd == "analyze":
        print(f"분석: {args.path}")

if __name__ == "__main__":
    main()
▶ 실행 결과
$ myapp crawl
크롤 시작
$ myapp analyze data.csv
분석: data.csv

📝 과제 (exercises)

직접 풀어보고, 막힐 때 정답을 펼쳐 비교해보세요.

과제 1

JSON API 호출

목표: GitHub API 로 사용자 저장소 정보를 가져온다.

요구사항
  • username 입력 받기
  • public_repos 개수 출력
  • 예외 처리 (timeout, raise_for_status)
💡 힌트

https://api.github.com/users/{username}

입출력 예시
사용자: torvalds
공개 저장소: 7
채점
  • · timeout 사용
  • · raise_for_status 사용
▶정답 코드 펼치기 / 접기
SOLUTION
import requests

def info(username):
    url = f"https://api.github.com/users/{username}"
    r = requests.get(url, timeout=10)
    r.raise_for_status()
    data = r.json()
    return data.get("public_repos", 0)

name = "torvalds"
print(f"사용자: {name}")
print(f"공개 저장소: {info(name)}")
▶ 실행 결과
사용자: torvalds
공개 저장소: 7
과제 2

HTML 헤딩 추출

목표: 주어진 URL 의 모든 h1, h2 텍스트를 추출한다.

요구사항
  • BeautifulSoup 사용
  • h1, h2 모두
💡 힌트

soup.find_all('h1')

입출력 예시
H1: ['Herman Melville - Moby-Dick']
H2: []
채점
  • · 파서 명시
  • · 결과 list
▶정답 코드 펼치기 / 접기
SOLUTION
import requests
from bs4 import BeautifulSoup

r = requests.get("https://httpbin.org/html", timeout=10)
soup = BeautifulSoup(r.text, "html.parser")
h1s = [t.get_text(strip=True) for t in soup.find_all("h1")]
h2s = [t.get_text(strip=True) for t in soup.find_all("h2")]
print(f"H1: {h1s}")
print(f"H2: {h2s}")
▶ 실행 결과
H1: ['Herman Melville - Moby-Dick']
H2: []
과제 3

robots.txt 확인

목표: 주어진 도메인의 robots.txt 를 가져와 출력한다.

요구사항
  • https://{domain}/robots.txt
  • 200 이 아니면 안내 메시지
💡 힌트

status_code 확인

입출력 예시
User-agent: *
Disallow: ...
채점
  • · timeout 사용
  • · 비정상 응답 처리
▶정답 코드 펼치기 / 접기
SOLUTION
import requests

def show_robots(domain):
    url = f"https://{domain}/robots.txt"
    r = requests.get(url, timeout=10)
    if r.status_code != 200:
        print(f"robots.txt 없음 (status {r.status_code})")
        return
    print(r.text[:200])

show_robots("github.com")
▶ 실행 결과
# If you would like to crawl GitHub contact us via https://support.github.com/contact?tags=dotcom-robots
# We also provide an extensive API: https://docs.github.com
User-agent: *
Disallow: /*/pulls
Disallow: /*/pull/
과제 4

NumPy 배열 통계

목표: 1~100 배열의 평균/최대/짝수 합을 구한다.

요구사항
  • np.arange 사용
  • 평균, 최대, 짝수 합 출력
💡 힌트

a[a % 2 == 0].sum()

입출력 예시
평균: 50.5
최대: 100
짝수 합: 2550
채점
  • · 불리언 인덱싱 사용
▶정답 코드 펼치기 / 접기
SOLUTION
import numpy as np

a = np.arange(1, 101)
print(f"평균: {a.mean()}")
print(f"최대: {a.max()}")
print(f"짝수 합: {a[a % 2 == 0].sum()}")
▶ 실행 결과
평균: 50.5
최대: 100
짝수 합: 2550
과제 5

DataFrame 학과별 평균

목표: 학생 점수 DataFrame 에서 학과별 평균을 구한다.

요구사항
  • pandas.DataFrame 직접 작성
  • groupby + mean
💡 힌트

df.groupby('dept')['score'].mean()

입출력 예시
dept
A    88.5
B    72.5
Name: score, dtype: float64
채점
  • · DataFrame 사용
  • · groupby 결과 일치
▶정답 코드 펼치기 / 접기
SOLUTION
import pandas as pd

df = pd.DataFrame({
    "name": ["Alice", "Bob", "Charlie", "Dave"],
    "score": [92, 78, 85, 67],
    "dept": ["A", "B", "A", "B"],
})
print(df.groupby("dept")["score"].mean())
▶ 실행 결과
dept
A    88.5
B    72.5
Name: score, dtype: float64
과제 6

BMI 시뮬레이션

목표: 키/몸무게 분포에서 BMI 통계를 구한다.

요구사항
  • np.random.normal 로 키 1.7m, 몸무게 65kg 분포 100개
  • BMI = 몸무게 / 키^2
  • 평균 BMI 출력
💡 힌트

seed 고정으로 재현 가능

입출력 예시
평균 BMI: 22.x
채점
  • · 벡터 연산
  • · 통계 출력
▶정답 코드 펼치기 / 접기
SOLUTION
import numpy as np

rng = np.random.default_rng(42)
heights = rng.normal(1.7, 0.05, 100)
weights = rng.normal(65, 5, 100)
bmi = weights / heights ** 2
print(f"평균 BMI: {bmi.mean():.2f}")
print(f"최저 BMI: {bmi.min():.2f}")
print(f"최고 BMI: {bmi.max():.2f}")
▶ 실행 결과
평균 BMI: 22.66
최저 BMI: 17.95
최고 BMI: 28.14
과제 7

최소 패키지

목표: Hello World 를 출력하는 CLI 패키지를 만든다.

요구사항
  • src/hello/__init__.py + cli.py
  • pyproject.toml + console_scripts
  • pip install -e . 후 hello 명령 실행 가능
💡 힌트

[project.scripts] hello = 'hello.cli:main'

입출력 예시
Hello, 패키지!
채점
  • · 패키지 구조
  • · CLI 등록
▶정답 코드 펼치기 / 접기
SOLUTION
# pyproject.toml
[project]
name = "hello"
version = "0.1.0"
[project.scripts]
hello = "hello.cli:main"
[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"

# src/hello/__init__.py
__version__ = "0.1.0"

# src/hello/cli.py
def main():
    print("Hello, 패키지!")

# 설치 + 실행
# pip install -e .
# hello
▶ 실행 결과
Hello, 패키지!
과제 8

argparse 가산기

목표: 두 수를 더하는 CLI 패키지를 만든다.

요구사항
  • addr 1 2 → 3 출력
  • argparse 사용
💡 힌트

type=int 지정

입출력 예시
3
채점
  • · argparse 사용
  • · 정상 출력
▶정답 코드 펼치기 / 접기
SOLUTION
# src/addr/cli.py
import argparse

def main():
    p = argparse.ArgumentParser(prog="addr")
    p.add_argument("a", type=int)
    p.add_argument("b", type=int)
    args = p.parse_args()
    print(args.a + args.b)

# pyproject.toml: [project.scripts] addr = "addr.cli:main"
# 실행: addr 1 2
▶ 실행 결과
3
과제 9

버전 출력 옵션

목표: --version 옵션으로 패키지 버전을 출력한다.

요구사항
  • argparse --version action
  • __init__.py 의 __version__ 사용
💡 힌트

action='version', version=f'%(prog)s {__version__}'

입출력 예시
mytool 0.1.0
채점
  • · --version 동작
  • · 버전 일치
▶정답 코드 펼치기 / 접기
SOLUTION
# src/mytool/__init__.py
__version__ = "0.1.0"

# src/mytool/cli.py
import argparse
from mytool import __version__

def main():
    p = argparse.ArgumentParser(prog="mytool")
    p.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
    p.parse_args()

# mytool --version
▶ 실행 결과
mytool 0.1.0
과제 10

최종 1 — 비동기 크롤러

목표: 여러 URL 을 동시 fetch 하고 길이/응답시간을 출력한다.

요구사항
  • httpx + asyncio.gather
  • 각 URL 의 (url, status, ms, bytes) 출력
  • 타임아웃 10초
💡 힌트

time.perf_counter()

asyncio.gather

입출력 예시
https://example.com 200 305ms 1256B
https://httpbin.org/get 200 412ms 380B
채점
  • · 비동기 동시 실행
  • · 에러 처리
▶정답 코드 펼치기 / 접기
SOLUTION
import asyncio
import time
import httpx

async def fetch(client, url):
    start = time.perf_counter()
    try:
        r = await client.get(url, timeout=10)
        ms = int((time.perf_counter() - start) * 1000)
        return f"{url} {r.status_code} {ms}ms {len(r.text)}B"
    except Exception as e:
        return f"{url} ERROR {e}"

async def main(urls):
    async with httpx.AsyncClient() as client:
        results = await asyncio.gather(*(fetch(client, u) for u in urls))
    for line in results:
        print(line)

asyncio.run(main(["https://example.com", "https://httpbin.org/get"]))
▶ 실행 결과
https://example.com 200 305ms 1256B
https://httpbin.org/get 200 412ms 380B
과제 11

최종 2 — CLI 패키지

목표: argparse 기반 CLI 패키지를 만들어 로컬 설치한다.

요구사항
  • src/myapp/cli.py + pyproject.toml
  • myapp greet --name=홍길동 → 인사 출력
💡 힌트

[project.scripts] myapp = 'myapp.cli:main'

입출력 예시
안녕, 홍길동!
채점
  • · pip install -e . 동작
  • · CLI 출력 일치
▶정답 코드 펼치기 / 접기
SOLUTION
# pyproject.toml
[project]
name = "myapp"
version = "0.1.0"
[project.scripts]
myapp = "myapp.cli:main"
[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"

# src/myapp/cli.py
import argparse

def main():
    p = argparse.ArgumentParser(prog="myapp")
    sub = p.add_subparsers(dest="cmd", required=True)
    g = sub.add_parser("greet")
    g.add_argument("--name", required=True)
    args = p.parse_args()
    if args.cmd == "greet":
        print(f"안녕, {args.name}!")

# pip install -e .
# myapp greet --name=홍길동
▶ 실행 결과
안녕, 홍길동!
과제 12

최종 3 — 데이터 파이프라인

목표: CSV → 필터 → 집계 → 출력 흐름을 짠다.

요구사항
  • pandas 로 read_csv
  • score >= 80 만 필터
  • dept 별 mean 출력
💡 힌트

StringIO 로 인라인 CSV

입출력 예시
dept
A    88.5
Name: score, dtype: float64
채점
  • · 필터
  • · groupby + mean
▶정답 코드 펼치기 / 접기
SOLUTION
import pandas as pd
from io import StringIO

csv = """name,score,dept
Alice,92,A
Bob,78,B
Charlie,85,A
Dave,67,B"""

df = pd.read_csv(StringIO(csv))
high = df[df.score >= 80]
print(high.groupby("dept")["score"].mean())
▶ 실행 결과
dept
A    88.5
Name: score, dtype: float64
예제 코드 / 강의 자료

전체 강의 자료와 예제 코드는 GitHub에서 자유롭게 받아볼 수 있습니다.

GitHub에서 보기 ↗

댓글 0

아직 댓글이 없습니다. 첫 댓글을 남겨 주세요.

댓글은 회원만 쓸 수 있습니다.

광고·욕설·비방·개인정보·불법 내용이 담긴 댓글은 숨기거나 지웁니다. 댓글은 닉네임과 함께 공개됩니다. 개인정보처리방침