Python 모듈과 패키지 | import, pip, 가상환경 완벽 정리
이 글의 핵심
Python 모듈과 패키지: import, pip, 가상환경 모듈 (Module)이란?·import 문법.
들어가며
”코드를 모듈로 나누기”
모듈과 패키지는 코드를 구조화하고 재사용하기 위한 핵심 개념입니다.
실무 활용 사례: 데이터 분석, 웹 개발, 자동화 프로젝트에서 실제로 사용한 패턴과 코드를 바탕으로 정리했습니다. 초보자가 흔히 겪는 오류와 해결법을 포함합니다.
실무에서 느낀 Python의 매력
처음 Python을 배울 때는 “이게 정말 프로그래밍 언어인가?” 싶을 정도로 간결했습니다. C++에서 10줄로 작성하던 코드가 Python에서는 2~3줄로 끝나는 경우가 많았죠. 특히 데이터 분석 프로젝트를 진행하면서 Pandas와 NumPy의 강력함을 체감했습니다. 엑셀로 몇 시간 걸리던 작업이 Python 스크립트로는 몇 초 만에 끝나는 걸 보고 동료들이 놀라워했던 기억이 납니다. 하지만 처음부터 순탄하지만은 않았습니다. 들여쓰기 하나 잘못해서 몇 시간을 헤맨 적도 있고, 가상환경 설정이 꼬여서 프로젝트 전체를 다시 시작한 적도 있습니다. 이런 시행착오를 겪으며 깨달은 건, 환경 설정을 처음부터 제대로 하는 것이 얼마나 중요한지였습니다. 이 글에서는 제가 겪은 실수들을 바탕으로, 여러분이 같은 시행착오를 겪지 않도록 실전 팁을 담았습니다.
1. 모듈 (Module)이란?
모듈의 정의
모듈(Module)은 Python 코드를 담은 .py 파일입니다. 함수, 클래스, 변수를 모듈로 만들어 다른 파일에서 재사용할 수 있습니다.
왜 모듈을 사용할까?
- 코드 재사용: 같은 코드를 여러 곳에서 사용
- 네임스페이스 관리: 이름 충돌 방지
- 유지보수 용이: 기능별로 파일 분리
모듈 만들기
예제: 수학 유틸리티 모듈
math_utils.py처럼 파일 하나를 모듈로 두면, 자주 쓰는 도구를 정리해 둔 공구함과 같습니다. 다른 스크립트에서는 import math_utils로 공구함만 가져와 함수 이름으로 꺼내 쓰면 됩니다.
# math_utils.py (모듈 파일)
"""
수학 관련 유틸리티 함수 모음
"""
def add(a, b):
"""두 수를 더합니다."""
return a + b
def multiply(a, b):
"""두 수를 곱합니다."""
return a * b
def power(base, exponent):
"""base의 exponent 제곱을 계산합니다."""
return base ** exponent
# 모듈 레벨 상수
PI = 3.14159
E = 2.71828
# 모듈이 직접 실행될 때만 실행되는 코드
if __name__ == '__main__':
print("math_utils 모듈 테스트")
print(f"2 + 3 = {add(2, 3)}")
print(f"2 * 3 = {multiply(2, 3)}")
모듈 사용하기
# main.py (모듈을 사용하는 파일)
import math_utils
# 모듈의 함수 호출
result1 = math_utils.add(3, 5)
print(result1) # 8
result2 = math_utils.multiply(4, 7)
print(result2) # 28
# 모듈의 상수 사용
print(math_utils.PI) # 3.14159
# 원의 넓이 계산
radius = 5
area = math_utils.PI * math_utils.power(radius, 2)
print(f"반지름 {radius}인 원의 넓이: {area}") # 78.53975
__name__ 변수의 동작
# math_utils.py
print(f"__name__ = {__name__}")
if __name__ == '__main__':
print("이 모듈이 직접 실행되었습니다")
else:
print("이 모듈이 import 되었습니다")
# 실행 결과:
# 1. python math_utils.py 실행 시:
# __name__ = __main__
# 이 모듈이 직접 실행되었습니다
# 2. import math_utils 실행 시:
# __name__ = math_utils
# 이 모듈이 import 되었습니다
모듈 검색 경로
Python은 모듈을 다음 순서로 검색합니다:
import sys
# 모듈 검색 경로 확인
for path in sys.path:
print(path)
# 출력 예시:
# 1. 현재 디렉토리 (스크립트가 있는 위치)
# 2. PYTHONPATH 환경 변수
# 3. 표준 라이브러리 경로
# 4. site-packages (pip로 설치한 패키지)
# 경로 추가 (런타임)
sys.path.append('/custom/path')
2. import 문법
import의 4가지 방법
Python에서 모듈을 가져오는 방법은 크게 4가지입니다. 각각의 장단점을 이해하고 상황에 맞게 사용해야 합니다.
방법 1: 모듈 전체 import
import math
print(math.sqrt(16)) # 4.0
print(math.pi) # 3.141592653589793
print(math.factorial(5)) # 120
# 장점:
# - 어디서 온 함수인지 명확 (math.sqrt → math 모듈의 sqrt)
# - 이름 충돌 방지
# 단점:
# - 매번 모듈명을 붙여야 함 (타이핑 많음)
언제 사용?
- 여러 함수를 사용할 때
- 코드의 출처를 명확히 하고 싶을 때
방법 2: 특정 요소만 import
from math import sqrt, pi, factorial
print(sqrt(16)) # 4.0 (math. 없이 사용)
print(pi) # 3.141592653589793
print(factorial(5)) # 120
# 장점:
# - 간결한 코드 (모듈명 생략)
# - 필요한 것만 가져옴
# 단점:
# - 출처가 불명확 (sqrt가 어디서 왔는지 알기 어려움)
# - 이름 충돌 가능
이름 충돌 예시:
# 문제 상황
from math import sqrt
from numpy import sqrt # math.sqrt를 덮어씀!
print(sqrt(16)) # numpy.sqrt 실행 (의도와 다를 수 있음)
# 해결 방법: 별칭 사용
from math import sqrt as math_sqrt
from numpy import sqrt as np_sqrt
print(math_sqrt(16)) # 4.0
print(np_sqrt(16)) # 4.0
언제 사용?
- 특정 함수를 자주 사용할 때
- 코드가 짧고 출처가 명확할 때
방법 3: 별칭(Alias) 사용
import math as m
import numpy as np
import pandas as pd
print(m.sqrt(16)) # 4.0
print(np.array([1, 2, 3])) # [1 2 3]
print(pd.DataFrame({'a': [1, 2]}))
# 장점:
# - 긴 모듈명을 짧게 사용
# - 출처 명확 + 타이핑 간결
# - 관습적 별칭 (np, pd) 사용 시 가독성 향상
# 단점:
# - 비표준 별칭 사용 시 혼란 가능
관습적 별칭:
import numpy as np # ✅ 표준
import pandas as pd # ✅ 표준
import matplotlib.pyplot as plt # ✅ 표준
import tensorflow as tf # ✅ 표준
import numpy as n # ❌ 비표준 (혼란 유발)
import pandas as p # ❌ 비표준
언제 사용?
- 긴 모듈명을 반복 사용할 때
- 관습적 별칭이 있는 라이브러리 (numpy → np)
방법 4: 모든 것 import (비추천)
from math import *
print(sqrt(16)) # 4.0
print(pi) # 3.141592653589793
# 장점:
# - 모든 함수를 바로 사용 가능
# 단점:
# - 어디서 온 함수인지 알 수 없음
# - 이름 충돌 위험 높음
# - 코드 리뷰 어려움
# - PEP 8 스타일 가이드 위반
문제 상황:
from math import *
from statistics import *
# mean 함수가 두 모듈에 모두 있음!
# 어느 mean이 사용되는지 알 수 없음
result = mean([1, 2, 3, 4, 5])
언제 사용?
- 거의 사용하지 않음 (대화형 셸에서만 가끔)
- 프로덕션 코드에서는 절대 사용 금지
import 방법 비교표
| 방법 | 문법 | 사용 예시 | 장점 | 단점 | 추천 상황 |
|---|---|---|---|---|---|
| 모듈 전체 | import math | math.sqrt(16) | 출처 명확 | 타이핑 많음 | 여러 함수 사용 |
| 특정 요소 | from math import sqrt | sqrt(16) | 간결 | 출처 불명확 | 특정 함수만 사용 |
| 별칭 | import math as m | m.sqrt(16) | 출처 명확 + 간결 | 별칭 기억 필요 | 긴 모듈명 |
| 전체 import | from math import * | sqrt(16) | 매우 간결 | 충돌 위험 높음 | 사용 금지 |
상대 import vs 절대 import
절대 import: 프로젝트 루트부터 전체 경로 지정
# 프로젝트 구조:
# myproject/
# ├── main.py
# └── mypackage/
# ├── __init__.py
# ├── utils.py
# └── core/
# └── engine.py
# mypackage/core/engine.py에서
from mypackage.utils import helper # 절대 import
상대 import: 현재 위치 기준으로 경로 지정
# mypackage/core/engine.py에서
from ..utils import helper # 상대 import (상위 디렉토리)
from . import config # 상대 import (같은 디렉토리)
# . : 현재 디렉토리
# ...: 상위 디렉토리
# ....: 상위의 상위 디렉토리
비교:
# 절대 import
# ✅ 장점: 명확, 어디서든 동일
# ❌ 단점: 패키지명 변경 시 모든 import 수정 필요
# 상대 import
# ✅ 장점: 패키지 이동/이름 변경 시 유리
# ❌ 단점: 스크립트를 직접 실행할 수 없음 (python engine.py → 에러)
권장 사항: 절대 import를 기본으로 사용하고, 패키지 내부에서만 상대 import 사용
3. 패키지 (Package)
패키지란?
패키지(Package)는 모듈들을 담은 디렉토리입니다. __init__.py 파일이 있으면 Python이 해당 디렉토리를 패키지로 인식합니다.
왜 패키지를 사용할까?
- 계층적 구조: 관련 모듈을 그룹화
- 네임스페이스: 모듈 이름 충돌 방지
- 배포 용이: 패키지 단위로 배포
패키지 구조 예시
myproject/
├── main.py # 실행 파일
├── requirements.txt # 의존성
└── mypackage/ # 패키지 디렉토리
├── __init__.py # 패키지 초기화 (필수*)
├── module1.py # 모듈 1
├── module2.py # 모듈 2
└── subpackage/ # 하위 패키지
├── __init__.py
└── module3.py
# *Python 3.3+에서는 __init__.py가 선택사항이지만,
# 명시적으로 만드는 것이 권장됩니다.
패키지 만들기
1단계: 디렉토리와 파일 생성
# mypackage/math_ops.py
def add(a, b):
"""덧셈"""
return a + b
def subtract(a, b):
"""뺄셈"""
return a - b
# mypackage/string_ops.py
def reverse(text):
"""문자열 뒤집기"""
return text[::-1]
def capitalize_words(text):
"""각 단어의 첫 글자 대문자"""
return ' '.join(word.capitalize() for word in text.split())
2단계: __init__.py 작성
# mypackage/__init__.py
"""
mypackage: 유틸리티 함수 모음
"""
# 하위 모듈에서 함수 가져오기
from .math_ops import add, subtract
from .string_ops import reverse, capitalize_words
# 패키지 버전
__version__ = '1.0.0'
# __all__: from mypackage import *에서 가져올 항목
__all__ = ['add', 'subtract', 'reverse', 'capitalize_words']
# 패키지 초기화 코드 (선택)
print(f"mypackage v{__version__} 로드됨")
3단계: 패키지 사용
# main.py
import mypackage
# __init__.py에서 가져온 함수 사용
print(mypackage.add(3, 5)) # 8
print(mypackage.reverse("Hello")) # olleH
# 또는 직접 모듈 import
from mypackage.math_ops import add
from mypackage.string_ops import reverse
print(add(10, 20)) # 30
print(reverse("Python")) # nohtyP
# 또는 __init__.py에서 정의한 함수 직접 import
from mypackage import add, reverse
print(add(1, 2)) # 3
__init__.py의 역할
# 1. 빈 __init__.py: 디렉토리를 패키지로 표시만
# mypackage/__init__.py
# (내용 없음)
# 2. 함수 재export: 편리한 import 경로 제공
# mypackage/__init__.py
from .math_ops import add, subtract
# 이제 from mypackage import add 가능
# from mypackage.math_ops import add 대신
# 3. 패키지 초기화: 설정, 로깅 등
# mypackage/__init__.py
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
logger.info("패키지 초기화")
# 4. __all__ 정의: 공개 API 명시
# mypackage/__init__.py
__all__ = ['add', 'subtract'] # 공개 함수만
하위 패키지 (Subpackage)
복잡한 프로젝트는 패키지 안에 패키지를 만들 수 있습니다.
myproject/
├── main.py
└── mypackage/
├── __init__.py
├── math_ops.py
├── string_ops.py
└── advanced/ # 하위 패키지
├── __init__.py
├── statistics.py
└── algorithms.py
하위 패키지 사용:
# mypackage/advanced/statistics.py
def mean(numbers):
"""평균 계산"""
return sum(numbers) / len(numbers)
def median(numbers):
"""중앙값 계산"""
sorted_nums = sorted(numbers)
n = len(sorted_nums)
mid = n // 2
return sorted_nums[mid] if n % 2 == 1 else (sorted_nums[mid-1] + sorted_nums[mid]) / 2
# main.py에서 사용
from mypackage.advanced.statistics import mean, median
data = [1, 2, 3, 4, 5]
print(f"평균: {mean(data)}") # 3.0
print(f"중앙값: {median(data)}") # 3
4. 표준 라이브러리 (Standard Library)
표준 라이브러리란?
Python 설치 시 기본 제공되는 모듈 모음입니다. 별도 설치 없이 import만으로 사용 가능합니다.
자주 사용하는 표준 라이브러리
1. os - 운영체제 기능
import os
# 현재 작업 디렉토리
print(os.getcwd()) # C:\Users\JB\workspace\pkglog.com
# 디렉토리 변경
os.chdir('/path/to/directory')
# 디렉토리 생성
os.mkdir("new_folder") # 단일 디렉토리
os.makedirs("parent/child/grandchild") # 중첩 디렉토리
# 파일/디렉토리 존재 확인
print(os.path.exists('file.txt')) # True/False
print(os.path.isfile('file.txt')) # 파일인지
print(os.path.isdir('folder')) # 디렉토리인지
# 경로 조합 (OS 독립적)
path = os.path.join('folder', 'subfolder', 'file.txt')
print(path) # Windows: folder\subfolder\file.txt
# Linux/Mac: folder/subfolder/file.txt
# 환경 변수
print(os.environ.get('PATH')) # PATH 환경 변수
os.environ['MY_VAR'] = 'value' # 환경 변수 설정
# 파일 목록
files = os.listdir('.') # 현재 디렉토리의 모든 파일/폴더
print(files)
2. datetime - 날짜와 시간
from datetime import datetime, timedelta, date, time
# 현재 날짜/시간
now = datetime.now()
print(now) # 2026-03-29 14:30:45.123456
# 포맷팅
formatted = now.strftime("%Y-%m-%d %H:%M:%S")
print(formatted) # 2026-03-29 14:30:45
# 날짜 계산
tomorrow = now + timedelta(days=1)
next_week = now + timedelta(weeks=1)
two_hours_ago = now - timedelta(hours=2)
print(f"내일: {tomorrow.strftime('%Y-%m-%d')}")
print(f"다음 주: {next_week.strftime('%Y-%m-%d')}")
# 문자열 → datetime
date_str = "2026-12-31 23:59:59"
parsed = datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S")
print(parsed) # 2026-12-31 23:59:59
# 날짜만
today = date.today()
print(today) # 2026-03-29
# 시간만
current_time = time(14, 30, 0) # 14:30:00
print(current_time)
3. random - 난수 생성
import random
# 정수 난수
print(random.randint(1, 10)) # 1~10 (양 끝 포함)
print(random.randrange(0, 10, 2)) # 0, 2, 4, 6, 8 중 하나
# 실수 난수
print(random.random()) # 0.0 ~ 1.0
print(random.uniform(1.0, 10.0)) # 1.0 ~ 10.0
# 리스트에서 선택
colors = ['빨강', '파랑', '초록', '노랑']
print(random.choice(colors)) # 하나 선택
print(random.choices(colors, k=3)) # 중복 허용 3개
print(random.sample(colors, k=2)) # 중복 없이 2개
# 리스트 섞기 (in-place)
numbers = [1, 2, 3, 4, 5]
random.shuffle(numbers)
print(numbers) # [3, 1, 5, 2, 4] (무작위 순서)
# 시드 설정 (재현 가능한 난수)
random.seed(42)
print(random.randint(1, 100)) # 항상 같은 값
4. json - JSON 처리
import json
# Python 객체 → JSON 문자열
data = {"name": "철수", "age": 25, "hobbies": ["독서", "영화"]}
json_str = json.dumps(data, ensure_ascii=False, indent=2)
print(json_str)
# {
# "name": "철수",
# "age": 25,
# "hobbies": ["독서", "영화"]
# }
# JSON 문자열 → Python 객체
parsed = json.loads(json_str)
print(parsed['name']) # 철수
print(type(parsed)) # <class 'dict'>
# 파일로 저장
with open('data.json', 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
# 파일에서 읽기
with open('data.json', 'r', encoding='utf-8') as f:
loaded = json.load(f)
print(loaded)
5. sys - 시스템 관련
import sys
# Python 버전
print(sys.version) # 3.11.0 (main, Oct 24 2022, ...)
# 명령줄 인자
# python script.py arg1 arg2
print(sys.argv) # ['script.py', 'arg1', 'arg2']
# 모듈 검색 경로
print(sys.path)
# 프로그램 종료
# sys.exit(0) # 정상 종료
# sys.exit(1) # 에러 종료
6. pathlib - 경로 처리 (현대적 방법)
from pathlib import Path
# 현재 디렉토리
current = Path.cwd()
print(current) # C:\Users\JB\workspace\pkglog.com
# 경로 조합
file_path = Path('folder') / 'subfolder' / 'file.txt'
print(file_path) # folder\subfolder\file.txt
# 파일 존재 확인
if file_path.exists():
print("파일 존재")
# 파일 읽기/쓰기
file_path.write_text("Hello, World!", encoding='utf-8')
content = file_path.read_text(encoding='utf-8')
print(content) # Hello, World!
# 파일 정보
print(file_path.name) # file.txt
print(file_path.stem) # file
print(file_path.suffix) # .txt
print(file_path.parent) # folder\subfolder
# 디렉토리 내 파일 목록
for file in Path('.').glob('*.py'):
print(file)
5. pip 패키지 관리
pip란?
pip는 Python 패키지를 설치하고 관리하는 패키지 관리자입니다. PyPI(Python Package Index)에서 패키지를 다운로드합니다.
pip 기본 명령어
# 패키지 설치
pip install requests
# 설치 과정:
# 1. PyPI에서 requests 패키지 검색
# 2. 최신 버전 다운로드
# 3. 의존성 패키지도 자동 설치
# 4. site-packages에 설치
# 특정 버전 설치
pip install requests==2.28.0 # 정확한 버전
pip install "requests>=2.28.0" # 2.28.0 이상
pip install "requests>=2.28.0,<3.0.0" # 범위 지정
# 패키지 업그레이드
pip install --upgrade requests
pip install -U requests # 단축 명령
# 패키지 정보 확인
pip show requests
# Name: requests
# Version: 2.28.0
# Summary: Python HTTP for Humans.
# Location: C:\Python311\Lib\site-packages
# Requires: charset-normalizer, idna, urllib3, certifi
# 패키지 삭제
pip uninstall requests
pip uninstall -y requests # 확인 없이 삭제
# 설치된 패키지 목록
pip list
# Package Version
# ---------- -------
# pip 23.0.1
# requests 2.28.0
# setuptools 65.5.0
# 오래된 패키지 확인
pip list --outdated
# Package Version Latest Type
# -------- -------- ------- -----
# requests 2.28.0 2.31.0 wheel
# requirements.txt 생성
pip freeze > requirements.txt
# requirements.txt로 설치
pip install -r requirements.txt
requirements.txt 작성법
# requirements.txt
# 1. 정확한 버전 (프로덕션 권장)
requests==2.28.0
flask==2.3.0
pandas==2.0.0
# 2. 최소 버전
numpy>=1.24.0
# 3. 범위 지정
django>=4.0.0,<5.0.0
# 4. 주석
# 웹 프레임워크
flask==2.3.0
# 데이터 처리
pandas==2.0.0
numpy==1.24.0
# 5. 개발 의존성 분리 (선택)
# requirements-dev.txt
pytest==7.3.0
black==23.3.0
flake8==6.0.0
pip 고급 사용법
# 1. 로컬 패키지 설치 (개발 모드)
pip install -e .
# -e: editable mode (소스 코드 수정 시 재설치 불필요)
# 2. GitHub에서 직접 설치
pip install git+https://github.com/user/repo.git
# 3. 특정 브랜치/태그 설치
pip install git+https://github.com/user/repo.git@branch_name
# 4. 로컬 .whl 파일 설치
pip install package-1.0.0-py3-none-any.whl
# 5. 여러 패키지 동시 설치
pip install requests flask pandas
# 6. 캐시 무시하고 설치
pip install --no-cache-dir requests
# 7. 사용자 디렉토리에 설치 (권한 없을 때)
pip install --user requests
pip 트러블슈팅
# 문제 1: pip 버전이 오래됨
pip install --upgrade pip
# 문제 2: SSL 인증서 오류
pip install --trusted-host pypi.org --trusted-host files.pythonhosted.org requests
# 문제 3: 권한 오류 (Windows)
# 관리자 권한으로 실행 또는
pip install --user requests
# 문제 4: 패키지 충돌
pip install --force-reinstall requests
# 문제 5: 의존성 확인
pip check # 의존성 문제 확인
6. 가상환경 (Virtual Environment)
venv 사용
# 가상환경 생성
python -m venv myenv
# 가상환경 활성화
# Windows
myenv\Scripts\activate
# Mac/Linux
source myenv/bin/activate
# 패키지 설치 (가상환경 내)
pip install requests
# 가상환경 비활성화
deactivate
7. 가상환경 심화
venv 상세 설명
# 가상환경 생성
python -m venv myenv
# myenv: 가상환경 이름 (관례: venv, .venv, env)
# 디렉토리 구조:
# myenv/
# ├── Scripts/ # Windows (실행 파일)
# ├── bin/ # Mac/Linux (실행 파일)
# ├── Lib/ # 설치된 패키지
# └── pyvenv.cfg # 설정 파일
# 가상환경 활성화
# Windows (PowerShell)
myenv\Scripts\Activate.ps1
# Windows (CMD)
myenv\Scripts\activate.bat
# Mac/Linux
source myenv/bin/activate
# 활성화 확인:
# (myenv) PS C:\Users\JB\workspace\pkglog.com>
# 프롬프트 앞에 (myenv) 표시
# Python 경로 확인
which python # Mac/Linux
where python # Windows
# 출력: myenv/bin/python (가상환경의 Python)
# 패키지 설치 (가상환경 내)
pip install requests
pip install flask pandas
# 설치된 패키지 확인
pip list
# requirements.txt 생성
pip freeze > requirements.txt
# 가상환경 비활성화
deactivate
# 프롬프트에서 (myenv) 사라짐
가상환경 실전 워크플로우
# 새 프로젝트 시작
mkdir myproject
cd myproject
# 가상환경 생성 및 활성화
python -m venv venv
venv\Scripts\activate # Windows
# 패키지 설치
pip install flask sqlalchemy pytest
# 개발 작업...
# requirements.txt 생성 (배포용)
pip freeze > requirements.txt
# Git에 커밋 (venv는 제외)
echo "venv/" >> .gitignore
git add .
git commit -m "Initial commit"
# 다른 환경에서 복원
git clone <repo>
cd myproject
python -m venv venv
venv\Scripts\activate
pip install -r requirements.txt
가상환경 vs 전역 환경
| 구분 | 전역 환경 | 가상환경 |
|---|---|---|
| 설치 위치 | Python 설치 디렉토리 | 프로젝트 디렉토리 |
| 패키지 격리 | ❌ 모든 프로젝트 공유 | ✅ 프로젝트별 독립 |
| 버전 충돌 | ⚠️ 발생 가능 | ✅ 없음 |
| 권장 사용 | 시스템 도구만 | 모든 프로젝트 |
8. 실전 예제
예제 1: 완전한 프로젝트 구조
myproject/
├── venv/ # 가상환경 (Git 제외)
├── src/ # 소스 코드
│ ├── __init__.py
│ ├── main.py # 진입점
│ ├── config.py # 설정
│ ├── utils/ # 유틸리티 패키지
│ │ ├── __init__.py
│ │ ├── file_utils.py # 파일 처리
│ │ └── string_utils.py # 문자열 처리
│ └── models/ # 데이터 모델
│ ├── __init__.py
│ └── user.py
├── tests/ # 테스트
│ ├── __init__.py
│ ├── test_utils.py
│ └── test_models.py
├── docs/ # 문서
│ └── README.md
├── .gitignore # Git 제외 파일
├── requirements.txt # 의존성
├── requirements-dev.txt # 개발 의존성
├── setup.py # 패키지 설정 (배포용)
└── README.md # 프로젝트 설명
예제 2: 간단한 웹 스크래퍼 프로젝트
프로젝트 구조:
web_scraper/
├── venv/
├── scraper/
│ ├── __init__.py
│ ├── main.py
│ ├── parser.py
│ └── storage.py
├── requirements.txt
└── README.md
코드 구현:
# scraper/parser.py
import requests
from bs4 import BeautifulSoup
def fetch_page(url):
"""웹 페이지 가져오기"""
response = requests.get(url, timeout=10)
response.raise_for_status()
return response.text
def parse_titles(html):
"""HTML에서 제목 추출"""
soup = BeautifulSoup(html, 'html.parser')
titles = [h2.text.strip() for h2 in soup.find_all('h2')]
return titles
# scraper/storage.py
import json
from pathlib import Path
def save_to_json(data, filename):
"""데이터를 JSON 파일로 저장"""
with open(filename, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
def load_from_json(filename):
"""JSON 파일에서 데이터 로드"""
if not Path(filename).exists():
return None
with open(filename, 'r', encoding='utf-8') as f:
return json.load(f)
# scraper/__init__.py
"""
웹 스크래퍼 패키지
"""
from .parser import fetch_page, parse_titles
from .storage import save_to_json, load_from_json
__version__ = '1.0.0'
__all__ = ['fetch_page', 'parse_titles', 'save_to_json', 'load_from_json']
# scraper/main.py
from scraper import fetch_page, parse_titles, save_to_json
def main():
url = 'https://example.com'
print(f"페이지 가져오는 중: {url}")
html = fetch_page(url)
print("제목 파싱 중...")
titles = parse_titles(html)
save_to_json({'titles': titles}, 'output.json')
print(f"✅ {len(titles)}개 제목 저장 완료")
if __name__ == '__main__':
main()
requirements.txt:
requests==2.31.0
beautifulsoup4==4.12.0
실행:
# 가상환경 설정
python -m venv venv
venv\Scripts\activate
# 의존성 설치
pip install -r requirements.txt
# 실행
python -m scraper.main
9. 모듈 import 문제 해결
문제 1: ModuleNotFoundError
# 에러: ModuleNotFoundError: No module named 'mymodule'
# 원인 1: 모듈이 설치되지 않음
# 해결: pip install mymodule
# 원인 2: 모듈이 sys.path에 없음
import sys
print(sys.path) # 모듈 검색 경로 확인
# 해결: 경로 추가
sys.path.append('/path/to/module')
# 원인 3: 가상환경이 활성화되지 않음
# 해결: 가상환경 활성화 확인
import sys
print(sys.prefix) # 가상환경 경로 확인
문제 2: 순환 import (Circular Import)
# module_a.py
from module_b import func_b
def func_a():
return func_b()
# module_b.py
from module_a import func_a # 순환 import!
def func_b():
return func_a()
# 실행 시: ImportError: cannot import name 'func_b'
# 해결 1: import를 함수 내부로 이동 (지연 import)
# module_a.py
def func_a():
from module_b import func_b # 함수 호출 시에만 import
return func_b()
# 해결 2: 공통 모듈로 분리
# common.py
def shared_func():
pass
# module_a.py
from common import shared_func
# module_b.py
from common import shared_func
# 해결 3: 구조 재설계 (권장)
# 순환 의존성이 생기지 않도록 모듈 구조 개선
문제 3: 상대 import 에러
# 에러: ImportError: attempted relative import with no known parent package
# 원인: 스크립트를 직접 실행
# python mypackage/module.py # ❌
# 해결: 패키지를 모듈로 실행
# python -m mypackage.module # ✅
# 또는 절대 import 사용
# from mypackage.utils import helper # ✅
문제 4: ImportError vs ModuleNotFoundError
# ModuleNotFoundError: 모듈 자체를 찾을 수 없음
# import nonexistent_module
# ImportError: 모듈은 있지만 특정 항목을 찾을 수 없음
# from math import nonexistent_function
# 해결: 모듈 내용 확인
import math
print(dir(math)) # 사용 가능한 모든 항목 출력
10. import 순서와 선택적 의존성
한 파일 안에서 import는 표준 라이브러리 → 서드파티 → 내 프로젝트 순으로 두면, 의존 관계가 한눈에 들어옵니다(PEP 8 권장). NumPy처럼 있으면 쓰고 없으면 다른 경로로 가는 코드를 쓸 때는 try/except ImportError로 선택적 import를 나눕니다.
# 1. 표준 라이브러리
import os
import sys
from datetime import datetime
# 빈 줄
# 2. 서드파티 라이브러리
import requests
import numpy as np
from flask import Flask
# 빈 줄
# 3. 로컬 모듈
from mypackage import utils
from mypackage.models import User
# 각 그룹 내에서는 알파벳 순으로 정렬
2. 조건부 import
# 선택적 의존성 (패키지가 없어도 동작)
try:
import numpy as np
HAS_NUMPY = True
except ImportError:
HAS_NUMPY = False
print("NumPy가 설치되지 않았습니다. 기본 기능만 사용합니다.")
def process_data(data):
if HAS_NUMPY:
return np.array(data).mean()
else:
return sum(data) / len(data)
# 사용
result = process_data([1, 2, 3, 4, 5])
print(result) # NumPy 있으면 np.mean(), 없으면 순수 Python
3. 지연 import (Lazy Import)
# 무거운 모듈은 필요할 때만 import
def process_image(image_path):
# PIL은 함수 호출 시에만 import (프로그램 시작 시간 단축)
from PIL import Image
img = Image.open(image_path)
return img.resize((100, 100))
# 장점: 프로그램 시작 시간 단축
# 단점: 첫 호출 시 약간 느림
4. __all__ 사용
# mymodule.py
def public_func():
"""공개 함수"""
pass
def _private_func():
"""비공개 함수 (관례: _ 접두사)"""
pass
def __internal_func():
"""내부 함수 (강한 비공개: __ 접두사)"""
pass
# 공개 API 명시
__all__ = ['public_func']
# 다른 파일에서
from mymodule import *
# public_func만 import됨 (_private_func, __internal_func는 제외)
# 명시적 import는 여전히 가능
from mymodule import _private_func # 가능하지만 권장하지 않음
5. 모듈 리로드 (개발 중)
# 모듈 수정 후 재import가 필요할 때
import importlib
import mymodule
# 모듈 리로드
importlib.reload(mymodule)
# 주의: 프로덕션 코드에서는 사용하지 말 것
# 개발/디버깅 용도로만 사용
11. 연습 문제
문제 1: 간단한 유틸리티 패키지 만들기
다음 구조의 패키지를 만들고 사용하세요:
utils/
├── __init__.py
├── math_utils.py # add, multiply 함수
└── text_utils.py # reverse, count_words 함수
문제 2: requirements.txt 작성
다음 패키지를 포함하는 requirements.txt를 작성하세요:
- requests (2.31.0)
- flask (2.3.0 이상, 3.0.0 미만)
- pandas (최신 버전)
문제 3: 표준 라이브러리 활용
datetime과 random을 사용하여 다음 기능을 구현하세요:
- 오늘부터 7일 후 날짜 출력
- 1~100 중 랜덤 숫자 10개 생성 (중복 없이)
정답 보기
**문제 1**: ```python # utils/math_utils.py def add(a, b): return a + b def multiply(a, b): return a * b # utils/text_utils.py def reverse(text): return text[::-1] def count_words(text): return len(text.split()) # utils/__init__.py from .math_utils import add, multiply from .text_utils import reverse, count_words __all__ = ['add', 'multiply', 'reverse', 'count_words'] # main.py from utils import add, reverse print(add(3, 5)) # 8 print(reverse("Hello")) # olleH ``` **문제 2**: ```text requests==2.31.0 flask>=2.3.0,<3.0.0 pandas ``` **문제 3**: ```python # 필요한 모듈 import from datetime import datetime, timedelta import random # 7일 후 today = datetime.now() after_7_days = today + timedelta(days=7) print(after_7_days.strftime("%Y-%m-%d")) # 랜덤 숫자 10개 (중복 없이) random_numbers = random.sample(range(1, 101), k=10) print(random_numbers) ```심화 부록: 구현·운영 관점
이 부록은 앞선 본문에서 다룬 주제(「Python 모듈과 패키지 | import, pip, 가상환경 완벽 정리」)를 구현·런타임·운영 관점에서 다시 압축합니다. 도메인별 세부 구현은 글마다 다르지만, 입력 검증 → 핵심 연산 → 부작용(I/O·네트워크·동시성) → 관측의 흐름으로 장애를 나누면 원인 추적이 빨라집니다.
내부 동작과 핵심 메커니즘
flowchart TD A[입력·요청·이벤트] --> B[파싱·검증·디코딩] B --> C[핵심 연산·상태 전이] C --> D[부작용: I/O·네트워크·동시성] D --> E[결과·관측·저장]
sequenceDiagram participant C as 클라이언트/호출자 participant B as 경계(런타임·게이트웨이·프로세스) participant D as 의존성(API·DB·큐·파일) C->>B: 요청/이벤트 B->>D: 조회·쓰기·RPC D-->>B: 지연·부분 실패·재시도 가능 B-->>C: 응답 또는 오류(코드·상관 ID)
- 불변 조건(Invariant): 버퍼 경계, 프로토콜 상태, 트랜잭션 격리, FD 상한 등 단계별로 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
- 결정성: 순수 층과 시간·네트워크·스케줄에 의존하는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
- 경계 비용: 직렬화, 인코딩, syscall 횟수, 락 경합, 할당·GC, 캐시 미스를 의심 목록에 둡니다.
- 백프레셔: 생산자가 소비자보다 빠를 때 버퍼·큐·스트림에서 속도를 줄이는 신호를 어디에 둘지 정의합니다.
프로덕션 운영 패턴
| 영역 | 운영 관점 질문 |
|---|---|
| 관측성 | 요청 단위 상관 ID, 에러율·지연 p95/p99, 의존성 타임아웃·재시도가 대시보드에 보이는가 |
| 안전성 | 입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가 |
| 신뢰성 | 재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가 |
| 성능 | 캐시·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가 |
| 배포 | 롤백 룬북, 카나리/블루그린, 마이그레이션·피처 플래그가 문서화되어 있는가 |
| 용량 | 피크 트래픽·디스크·FD·스레드 풀 상한을 주기적으로 검증하는가 |
스테이징은 데이터 양·네트워크 RTT·동시성을 프로덕션에 가깝게 맞출수록 재현율이 올라갑니다.
확장 예시: 엔드투엔드 미니 시나리오
앞선 본문 주제(「Python 모듈과 패키지 | import, pip, 가상환경 완벽 정리」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.
- 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
- 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
- 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
- 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
- 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값을 점검한다.
handle(request):
ctx = newCorrelationId()
validated = validateSchema(request)
authorize(validated, ctx)
result = domainCore(validated)
persistOrEmit(result, idempotentKey)
recordMetrics(ctx, latency, outcome)
return result
문제 해결(Troubleshooting)
| 증상 | 가능 원인 | 조치 |
|---|---|---|
| 간헐적 실패 | 레이스, 타임아웃, 외부 의존성, DNS | 최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검 |
| 성능 저하 | N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스 | 프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거 |
| 메모리 증가 | 캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납 | 상한·TTL·힙/FD 스냅샷 비교 |
| 빌드·배포만 실패 | 환경 변수, 권한, 플랫폼 차이, lockfile | CI 로그와 로컬 diff, 런타임·이미지 버전 핀 |
| 설정 불일치 | 프로필·시크릿·기본값, 리전 | 스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화 |
| 데이터 불일치 | 비멱등 재시도, 부분 쓰기, 캐시 무효화 누락 | 멱등 키·아웃박스·트랜잭션 경계 재검토 |
권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.
배포 전에는 git add → git commit → git push 후 npm run deploy 순서를 권장합니다.
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. Python 모듈과 패키지: import, pip, 가상환경 완벽 정리. 모듈 (Module)이란?·import 문법로 흐름을 잡고 원리·코드·실무 적용을 한글로 정리합니다. Python·모듈·module 중심으로 … 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. Python 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- Python 클래스 | 객체지향 프로그래밍(OOP) 완벽 정리
- Python 파일 처리 | 읽기, 쓰기, CSV, JSON 완벽 정리
- Python 실전 시리즈 전체 목차 | #01~#23 학습 경로·영문 글·연관 글
- Python 환경 설정 | Windows/Mac에서 Python 설치하고 시작하기
- [Python Modules and Packages | import· pip](/en/blog/python-series-06-modules/
이 글에서 다루는 키워드 (관련 검색어)
Python, 모듈, module, 패키지, package, import, pip 등으로 검색하시면 이 글이 도움이 됩니다.