← 파이썬 강의 목록으로
⚙️
중급 (Intermediate)
*args/**kwargs · lambda · 고차함수 · 클로저 · list/dict/set comp · yield · 메모리 효율 · import · __name__ · __init__.py · pip

1~3주차 — 함수 심화 · 컴프리헨션과 제너레이터 · 모듈과 패키지

가변 인자(*args, **kwargs), 한 줄 람다, map/filter/sorted with key 같은 고차함수, 그리고 함수 안의 함수가 외부 변수를 기억하는 클로저까지 함수의 표현력을 한 단계 끌어올립니다. 리스트·딕셔너리·집합 컴프리헨션으로 한 줄에 변환·필터·매핑을 처리하고, yield로 만드는 제너레이터로 메모리 효율적인 코드를 짭니다. 자체 모듈 만들고 import, __name__ == "__main__" 패턴, 폴더를 패키지로 만드는 __init__.py, 그리고 pip 으로 외부 패키지 설치까지 익힙니다.

*argslambdahigher-orderclosurecomprehensiongeneratoryieldmodulepackageimportpip
소요 시간
⏱ 2시간 + 2시간 + 2시간
난이도
📊 중급
선수 조건
🎯 기초 9주차
결과물
표현력 있는 함수형 코드 작성 / 한 줄 컴프리헨션 + 큰 데이터에 강한 제너레이터 / 여러 파일/폴더로 분리된 작은 패키지 작성

이 강의에서 배우는 것

  • 1*args, **kwargs 로 가변 인자를 받는다
  • 2람다(lambda) 함수를 사용한다
  • 3고차함수 (map, filter, sorted with key) 를 활용한다
  • 4클로저(closure)의 동작을 이해한다
  • 5리스트·딕셔너리·집합 컴프리헨션을 작성한다
  • 6제너레이터 함수와 표현식을 이해한다
  • 7메모리 효율적인 코드를 짠다
  • 8자체 모듈을 만들고 import한다
  • 9__name__ == "__main__" 패턴을 안다
  • 10패키지 구조와 __init__.py 의 역할을 이해한다
  • 11pip 으로 외부 패키지를 설치한다

1주차 — 함수 심화

가변 인자(*args, **kwargs), 한 줄 람다, map/filter/sorted with key 같은 고차함수, 그리고 함수 안의 함수가 외부 변수를 기억하는 클로저까지 함수의 표현력을 한 단계 끌어올립니다.

1. *args, **kwargs

가변 개수의 인자를 받습니다.

python
def add_all(*args):           # args는 튜플
    return sum(args)

print(add_all(1, 2, 3))       # 6
print(add_all(1, 2, 3, 4, 5)) # 15

def info(**kwargs):           # kwargs는 딕셔너리
    for k, v in kwargs.items():
        print(f"{k}: {v}")

info(name="홍길동", age=20)

조합도 가능: def f(a, b, *args, **kwargs):

2. 람다 함수

이름 없는 한 줄 함수. 즉석 사용에 적합.

python
square = lambda x: x * x
print(square(5))   # 25

# 보통 다른 함수의 인자로 전달
nums = [1, 2, 3, 4, 5]
print(list(map(lambda x: x * 2, nums)))  # [2, 4, 6, 8, 10]

3. 고차함수

함수를 인자로 받는 함수.

python
nums = [1, 2, 3, 4, 5]

# map: 변환
print(list(map(lambda x: x * x, nums)))      # [1, 4, 9, 16, 25]

# filter: 필터링
print(list(filter(lambda x: x % 2, nums)))   # [1, 3, 5]

# sorted with key
people = [("Alice", 30), ("Bob", 25), ("Charlie", 28)]
people.sort(key=lambda p: p[1])
print(people)

4. 클로저

함수 안에서 정의된 함수가 바깥 변수를 기억.

python
def make_counter():
    count = 0
    def inc():
        nonlocal count    # 바깥 변수 수정 시 nonlocal 필요
        count += 1
        return count
    return inc

c = make_counter()
print(c(), c(), c())   # 1 2 3

5. 자주 하는 실수

  1. *args 의 위치 — 일반 매개변수 뒤, **kwargs 앞
  2. 람다에 너무 많은 로직 — 길어지면 그냥 def. 람다는 한 줄
  3. sorted 와 sort 혼동 — sorted() 는 새 리스트, list.sort() 는 제자리
  4. 클로저에서 nonlocal 누락 — count += 1 만 쓰면 UnboundLocalError

6. FAQ

Q1. 언제 *args 를 써야 하나요?

인자 개수가 정해지지 않을 때. 예: print, max(1, 2, 3, ...).

Q2. 람다와 일반 함수의 성능 차이?

거의 없음. 람다는 가독성·간결함을 위한 문법 설탕.

Q3. 클로저는 어디에 쓰나요?

데코레이터, 콜백, 상태가 있는 함수. 객체 대신 가벼운 대안.

2주차 — 컴프리헨션과 제너레이터

리스트·딕셔너리·집합 컴프리헨션으로 한 줄에 변환·필터·매핑을 처리하고, yield로 만드는 제너레이터로 메모리 효율적인 코드를 짭니다.

1. 리스트 컴프리헨션

[표현식 for 변수 in 시퀀스 if 조건] 한 줄로 리스트 생성.

python
# 기존
squares = []
for x in range(10):
    squares.append(x * x)

# 컴프리헨션
squares = [x * x for x in range(10)]

조건 추가:

python
even_sq = [x * x for x in range(10) if x % 2 == 0]
# [0, 4, 16, 36, 64]

중첩:

python
pairs = [(i, j) for i in range(3) for j in range(3) if i != j]

2. 딕셔너리·집합 컴프리헨션

python
words = ["apple", "banana", "cherry"]

# 딕셔너리: {키: 값 for ...}
length_map = {w: len(w) for w in words}
# {'apple': 5, 'banana': 6, 'cherry': 6}

# 집합: {표현식 for ...}
unique_lens = {len(w) for w in words}
# {5, 6}

3. 제너레이터 함수

return 대신 yield. 결과를 한 번에 만들지 않고, 필요할 때마다 하나씩.

python
def fib(n):
    a, b = 0, 1
    for _ in range(n):
        yield a
        a, b = b, a + b

for x in fib(10):
    print(x, end=" ")    # 0 1 1 2 3 5 8 13 21 34

리스트로 한 번에 보고 싶으면 list(fib(10)).

4. 제너레이터 표현식

대괄호 대신 소괄호 → 메모리 절약.

python
# 리스트 컴프리헨션 (메모리 다 잡아먹음)
total = sum([x * x for x in range(1_000_000)])

# 제너레이터 표현식 (한 번에 하나씩)
total = sum(x * x for x in range(1_000_000))

5. 언제 어느 것?

상황도구
작은 데이터 + 인덱스 필요리스트 컴프리헨션
큰 데이터 + 한 번 순회제너레이터
키-값 매핑딕셔너리 컴프리헨션
중복 제거집합 컴프리헨션

6. 자주 하는 실수

  1. 컴프리헨션이 너무 길고 복잡 — 가독성 떨어지면 그냥 for 루프
  2. 제너레이터를 두 번 순회 — 한 번 다 돌면 빈 상태. 다시 호출 필요.
  3. yield 가 함수 어디에든 있으면 함수 전체가 제너레이터 — return 으로 일반 값 못 돌려줌

7. FAQ

Q1. 컴프리헨션이 for문보다 항상 빠른가요?

보통 약간 빠름. 가장 큰 차이는 가독성과 의도 표현.

Q2. 제너레이터의 길이를 알 수 있나요?

안 됨. 길이가 필요하면 list로 변환 (메모리 사용).

Q3. yield 가 여러 개여도 되나요?

네. 호출할 때마다 다음 yield 까지 실행됨.

3주차 — 모듈과 패키지

자체 모듈 만들고 import, __name__ == "__main__" 패턴, 폴더를 패키지로 만드는 __init__.py, 그리고 pip 으로 외부 패키지 설치까지 익힙니다.

1. 모듈

.py 파일이 곧 모듈. import로 다른 파일에서 사용.

python
# calc.py
def add(a, b):
    return a + b

def sub(a, b):
    return a - b
python
# main.py
import calc
print(calc.add(3, 5))

from calc import add, sub
print(add(1, 2), sub(5, 3))

from calc import add as plus
print(plus(10, 20))

2. __name__ == "__main__"

스크립트로 직접 실행될 때만 동작하는 코드.

python
# mymod.py
def main():
    print("실행됨")

if __name__ == "__main__":
    main()

이 패턴 덕분에 import mymod 해도 main() 이 자동 실행되지 않음.

3. 패키지

폴더에 여러 모듈을 묶은 것. __init__.py 가 있어야 패키지로 인식 (Python 3.3+ 부터 없어도 됨).

text
mypkg/
├── __init__.py
├── math_utils.py
└── string_utils.py
python
from mypkg.math_utils import add
from mypkg.string_utils import slugify

4. __init__.py 활용

python
# mypkg/__init__.py
from .math_utils import add, sub
from .string_utils import slugify

이렇게 하면 from mypkg import add 가능 (간편).

5. pip 으로 외부 패키지 설치

bash
pip install requests
pip install requests==2.31.0       # 특정 버전
pip install -r requirements.txt    # 일괄 설치
pip list                            # 설치된 목록

6. 자주 하는 실수

  1. 순환 import — A가 B를, B가 A를 import → ImportError
  2. 상대 import 잘못 — 패키지 내부에서는 from .module import ...
  3. 모듈 이름이 표준 라이브러리와 충돌 — random.py 라는 파일을 만들면 표준 random이 가려짐

7. FAQ

Q1. import 와 from 중 뭘 쓰나요?

사용하는 함수가 1-2개면 from, 많거나 모듈 이름을 명시하고 싶으면 import.

Q2. pip 가 안 깔려있어요

python -m ensurepip 또는 python -m pip --version 으로 확인.

Q3. 가상환경(venv)은 왜 쓰나요?

프로젝트마다 라이브러리 버전을 분리. 다음 학기·실무에서 거의 필수.

💻 예제 (examples)

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

01_args_kwargs.py— 가변 인자
CODE
def add_all(*args):
    return sum(args)

def info(**kwargs):
    for k, v in kwargs.items():
        print(f"{k}: {v}")

print(add_all(1, 2, 3))
print(add_all(1, 2, 3, 4, 5))
info(name="홍길동", age=20, city="서울")
▶ 실행 결과
6
15
name: 홍길동
age: 20
city: 서울
02_lambda.py— 람다 함수
CODE
square = lambda x: x * x
add = lambda a, b: a + b
print(square(5))
print(add(3, 4))

nums = [1, 2, 3, 4, 5]
doubled = list(map(lambda x: x * 2, nums))
print(doubled)
▶ 실행 결과
25
7
[2, 4, 6, 8, 10]
03_higher_order.py— map / filter / sorted
CODE
nums = [1, 2, 3, 4, 5]

print(list(map(lambda x: x * x, nums)))
print(list(filter(lambda x: x % 2, nums)))

people = [("Alice", 30), ("Bob", 25), ("Charlie", 28)]
people.sort(key=lambda p: p[1])
print(people)
▶ 실행 결과
[1, 4, 9, 16, 25]
[1, 3, 5]
[('Bob', 25), ('Charlie', 28), ('Alice', 30)]
04_closure.py— 클로저와 nonlocal
CODE
def make_counter():
    count = 0
    def inc():
        nonlocal count
        count += 1
        return count
    return inc

c1 = make_counter()
c2 = make_counter()   # 독립적 클로저
print(c1(), c1(), c1())   # 1 2 3
print(c2())               # 1 (c1과 독립)
▶ 실행 결과
1 2 3
1
01_list_comp.py— 리스트 컴프리헨션
CODE
squares = [x * x for x in range(10)]
print(squares)

even_sq = [x * x for x in range(10) if x % 2 == 0]
print(even_sq)

# 중첩
pairs = [(i, j) for i in range(3) for j in range(3) if i != j]
print(pairs)
▶ 실행 결과
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
[0, 4, 16, 36, 64]
[(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
02_dict_set_comp.py— 딕셔너리·집합 컴프리헨션
CODE
words = ["apple", "banana", "cherry"]

length_map = {w: len(w) for w in words}
print(length_map)

unique_lens = {len(w) for w in words}
print(unique_lens)
▶ 실행 결과
{'apple': 5, 'banana': 6, 'cherry': 6}
{5, 6}
03_generator.py— 제너레이터 함수 (피보나치)
CODE
def fib(n):
    a, b = 0, 1
    for _ in range(n):
        yield a
        a, b = b, a + b

for x in fib(10):
    print(x, end=" ")
print()

# 리스트로
print(list(fib(10)))
▶ 실행 결과
0 1 1 2 3 5 8 13 21 34
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
04_generator_expr.py— 제너레이터 표현식 (메모리 비교)
CODE
# 리스트는 메모리에 다 만들어둠
total1 = sum([x * x for x in range(1_000_000)])

# 제너레이터는 한 번에 하나씩
total2 = sum(x * x for x in range(1_000_000))

print(total1 == total2, total1)
▶ 실행 결과
True 333332833333500000
calc.py + main.py— 자체 모듈 import
CODE
# calc.py
def add(a, b): return a + b
def sub(a, b): return a - b

# main.py
import calc
from calc import add, sub

print(calc.add(3, 5))
print(add(10, 20), sub(5, 3))
▶ 실행 결과
8
30 2
main_guard.py— __name__ == "__main__" 패턴
CODE
def greet(name):
    return f"안녕, {name}!"

def main():
    print(greet("홍길동"))

if __name__ == "__main__":
    main()
▶ 실행 결과
안녕, 홍길동!
use_pkg.py— 패키지 사용
CODE
# mypkg/__init__.py 에서 from .math_utils import add 한 경우
from mypkg import add
from mypkg.string_utils import slugify

print(add(2, 3))
print(slugify("Hello World!"))
▶ 실행 결과
5
hello-world
use_external.py— 외부 패키지 (requests)
CODE
# pip install requests 후
import requests

r = requests.get("https://httpbin.org/json")
print(r.status_code)
print(r.json()["slideshow"]["title"])
▶ 실행 결과
200
Sample Slide Show

📝 과제 (exercises)

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

과제 1

stats(*nums) — 평균/분산

목표: 가변 인자로 받은 숫자들의 평균과 분산을 출력한다.

요구사항
  • *nums 로 받기
  • 평균과 분산을 한 줄에 출력 (분산은 표본분산 또는 모분산)
입출력 예시
평균: 3.0, 분산: 2.0
▶정답 코드 펼치기 / 접기
SOLUTION
def stats(*nums):
    n = len(nums)
    if n == 0:
        print("(빈 입력)")
        return
    mean = sum(nums) / n
    var = sum((x - mean) ** 2 for x in nums) / n
    print(f"평균: {mean}, 분산: {var}")

stats(1, 2, 3, 4, 5)
▶ 실행 결과
평균: 3.0, 분산: 2.0
과제 2

학생 점수 정렬 (sorted with key)

목표: 학생 리스트를 점수 내림차순 + 이름 오름차순으로 정렬한다.

요구사항
  • 데이터: [("Alice", 92), ("Bob", 88), ("Charlie", 92), ...]
  • 1차: 점수 내림차순, 2차: 이름 오름차순
입출력 예시
[('Alice', 92), ('Charlie', 92), ('Bob', 88)]
▶정답 코드 펼치기 / 접기
SOLUTION
students = [("Alice", 92), ("Bob", 88), ("Charlie", 92)]
students.sort(key=lambda s: (-s[1], s[0]))
print(students)
▶ 실행 결과
[('Alice', 92), ('Charlie', 92), ('Bob', 88)]
과제 3

make_counter 호출 카운터

목표: make_counter() 로 만든 카운터 함수를 두 개 호출해 독립성을 보인다.

요구사항
  • make_counter() 가 inc 함수를 반환
  • 각 카운터는 자신만의 count 를 유지
입출력 예시
c1: 1
c1: 2
c2: 1
c1: 3
▶정답 코드 펼치기 / 접기
SOLUTION
def make_counter():
    count = 0
    def inc():
        nonlocal count
        count += 1
        return count
    return inc

c1 = make_counter()
c2 = make_counter()
print(f"c1: {c1()}")
print(f"c1: {c1()}")
print(f"c2: {c2()}")
print(f"c1: {c1()}")
▶ 실행 결과
c1: 1
c1: 2
c2: 1
c1: 3
과제 4

1~100 소수 (컴프리헨션)

목표: 컴프리헨션으로 1~100 사이 소수만 추출.

요구사항
  • is_prime 함수 정의
  • 리스트 컴프리헨션으로 필터링
입출력 예시
[2, 3, 5, 7, 11, 13, ..., 97]
▶정답 코드 펼치기 / 접기
SOLUTION
def is_prime(n):
    if n < 2: return False
    return all(n % i for i in range(2, int(n**0.5) + 1))

primes = [n for n in range(1, 101) if is_prime(n)]
print(primes)
▶ 실행 결과
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
과제 5

단어 길이 매핑

목표: 단어 리스트 → {단어: 길이} 딕셔너리 (컴프리헨션).

요구사항
  • dict comprehension 사용
입출력 예시
{'apple': 5, 'pear': 4, 'kiwi': 4, 'banana': 6}
▶정답 코드 펼치기 / 접기
SOLUTION
words = ["apple", "pear", "kiwi", "banana"]
length_map = {w: len(w) for w in words}
print(length_map)
▶ 실행 결과
{'apple': 5, 'pear': 4, 'kiwi': 4, 'banana': 6}
과제 6

큰 파일 라인 단위 제너레이터

목표: 파일을 라인 단위로 읽고 길이만 합계 (메모리 효율).

요구사항
  • 파일을 한 줄씩 yield 하는 제너레이터 함수
  • sum 으로 길이 합계
💡 힌트

with open(path) as f: for line in f: yield line

입출력 예시
라인 수: 3, 총 글자 수: 35
▶정답 코드 펼치기 / 접기
SOLUTION
def lines(path):
    with open(path, encoding="utf-8") as f:
        for line in f:
            yield line.rstrip("\n")

# 가상 파일 (실제로는 path 받음)
sample = ["첫 번째 줄입니다", "두 번째 줄", "세 번째 줄입니다 끝"]

# 데모 (제너레이터 흉내)
def lines_from_list(lst):
    for x in lst:
        yield x

count = 0
total = 0
for line in lines_from_list(sample):
    count += 1
    total += len(line)
print(f"라인 수: {count}, 총 글자 수: {total}")
▶ 실행 결과
라인 수: 3, 총 글자 수: 26
과제 7

calculator/ 패키지

목표: basic + advanced 두 모듈로 나눈 calculator 패키지 만들기.

요구사항
  • calculator/__init__.py — basic·advanced 함수 import 하여 패키지 레벨에서 노출
  • calculator/basic.py — add, sub, mul, div
  • calculator/advanced.py — power, sqrt, factorial
입출력 예시
8
1024
120
▶정답 코드 펼치기 / 접기
SOLUTION
# calculator/basic.py
def add(a, b): return a + b
def sub(a, b): return a - b
def mul(a, b): return a * b
def div(a, b): return a / b if b else None

# calculator/advanced.py
def power(a, b): return a ** b
def sqrt(x): return x ** 0.5
def factorial(n): return 1 if n <= 1 else n * factorial(n - 1)

# calculator/__init__.py
from .basic import add, sub, mul, div
from .advanced import power, sqrt, factorial

# 사용
from calculator import add, power, factorial
print(add(3, 5))
print(power(2, 10))
print(factorial(5))
▶ 실행 결과
8
1024
120
과제 8

string_utils.py — slugify, truncate

목표: 두 가지 문자열 유틸 함수가 있는 모듈을 만든다.

요구사항
  • slugify(s): 공백 → -, 소문자, 비영숫자 제거
  • truncate(s, n): n 글자로 자르고 "…" 추가
입출력 예시
hello-world
긴 문장입니다…
▶정답 코드 펼치기 / 접기
SOLUTION
# string_utils.py
import re

def slugify(s):
    s = s.lower()
    s = re.sub(r"[^a-z0-9가-힣\s-]", "", s)
    return re.sub(r"\s+", "-", s).strip("-")

def truncate(s, n):
    return s if len(s) <= n else s[:n] + "…"

# 사용
print(slugify("Hello World!"))
print(truncate("긴 문장입니다 정말로", 7))
▶ 실행 결과
hello-world
긴 문장입니다…
과제 9

__name__ == "__main__" 활용

목표: import 시에는 동작 안 하고, 직접 실행 시에만 데모를 보여주는 모듈.

요구사항
  • 함수 1개 정의 (자유)
  • if __name__ == "__main__": 안에서 데모 호출
  • import 했을 때 데모가 실행되지 않는지 확인
입출력 예시
[직접 실행] greet 데모: 안녕, 홍길동!
[import 실행] (조용함)
▶정답 코드 펼치기 / 접기
SOLUTION
# greet.py
def greet(name):
    return f"안녕, {name}!"

if __name__ == "__main__":
    print("[데모]", greet("홍길동"))

# other.py
import greet  # 모듈 import — 데모는 실행되지 않음
print(greet.greet("김영희"))
▶ 실행 결과
# greet.py 직접 실행 시
[데모] 안녕, 홍길동!

# other.py 실행 시
안녕, 김영희!
예제 코드 / 강의 자료

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

GitHub에서 보기 ↗

댓글 0

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

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

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