22. 모듈과 패키지로 코드 구성하기
Python 프로그램이 커질수록 모든 코드를 하나의 파일에 넣어두는 것은 비현실적입니다. 관련된 함수(function), 클래스(class), 변수(variable)를 서로 다른 프로그램에서 재사용할 수 있는 별도의 파일로 정리하고 싶어질 것입니다. Python의 모듈(module) 및 패키지(package) 시스템은 바로 이 기능을 제공합니다—코드를 효과적으로 정리하고, 공유하고, 재사용할 수 있는 방법입니다.
이 장에서는 Python의 import 시스템이 어떻게 동작하는지, 여러분만의 모듈을 만들고 사용하는 방법, 그리고 여러 모듈을 패키지로 구성하는 방법을 살펴보겠습니다. 또한 가져오기 가능한 모듈이면서도 독립 실행 스크립트로도 동작하는 파일을 작성할 수 있게 해주는 특별한 __name__ 변수도 살펴보겠습니다.
22.1) 모듈이란 무엇이며 import는 어떻게 동작하는가
모듈 이해하기
모듈(module)은 정의(definition)와 문(statement)을 담고 있는 Python 파일을 말합니다. 여러분이 만드는 어떤 .py 파일이든 모듈입니다. calculator.py라는 파일에 함수를 작성하면, 그 파일은 다른 Python 파일에서 사용할 수 있는 calculator라는 이름의 모듈이 됩니다.
모듈은 몇 가지 중요한 목적을 갖습니다:
- 코드 재사용성(Code Reusability): 함수를 한 번 작성해 여러 프로그램에서 사용하기
- 구성(Organization): 관련 기능을 함께 묶기
- 네임스페이스 관리(Namespace Management): 충돌을 피하기 위해 이름을 분리해서 유지하기
- 유지보수성(Maintainability): 작고 목적이 분명한 파일은 이해하고 수정하기가 더 쉽습니다
이 동작을 확인하기 위해 간단한 모듈을 만들어 보겠습니다. greetings.py라는 파일을 생성하세요:
# greetings.py
def say_hello(name):
"""친근한 인사를 반환합니다."""
return f"Hello, {name}!"
def say_goodbye(name):
"""작별 메시지를 반환합니다."""
return f"Goodbye, {name}. See you soon!"
# 모듈 레벨 변수
default_greeting = "Welcome"이 파일은 이제 모듈입니다. 다른 Python 파일에서 사용할 수 있는 두 개의 함수와 하나의 변수를 포함하고 있습니다.
import 문
모듈의 코드를 사용하려면 import(import) 해야 합니다. import 문은 Python에게 모듈을 로드하고 그 내용을 사용할 수 있게 하라고 지시합니다. 같은 디렉터리에 main.py라는 다른 파일을 만들겠습니다:
# main.py
import greetings
message = greetings.say_hello("Alice")
print(message) # Output: Hello, Alice!
farewell = greetings.say_goodbye("Bob")
print(farewell) # Output: Goodbye, Bob. See you soon!
print(greetings.default_greeting) # Output: Welcomemain.py를 실행하면, Python은 import greetings 문을 실행합니다. 내부적으로는 다음과 같은 일이 일어납니다:
중요: Python은 프로그램에서 어떤 모듈이 처음 import될 때만 그 모듈의 코드를 실행합니다. 같은 프로그램에서 이후의 import는 이미 로드된 모듈을 재사용합니다. 이는 중복 실행을 방지하고 시간을 절약해 줍니다.
모듈 내용에 접근하기
모듈을 import한 뒤에는 점 표기법(dot notation) module_name.item_name을 사용해 그 내용에 접근합니다. 이는 5장과 14장에서 배운 것처럼 text.upper() 같은 문자열 메서드나 numbers.append() 같은 리스트 메서드에 접근하는 방식과 비슷합니다.
import greetings
# 함수에 접근
result = greetings.say_hello("Charlie")
# 변수에 접근
greeting = greetings.default_greeting
# 모듈에 무엇이 들어있는지 확인할 수도 있습니다
print(dir(greetings)) # 모듈에 정의된 모든 이름을 나열합니다점 표기법은 각 이름이 어디에서 왔는지 명확하게 해줍니다. greetings.say_hello()를 보면 이 함수가 greetings 모듈에서 왔다는 것을 즉시 알 수 있습니다.
모듈 검색 경로
import greetings를 작성했을 때 Python은 greetings.py를 어떻게 찾을까요? Python은 특정한 순서로 모듈을 검색합니다:
- 현재 디렉터리(Current Directory): 실행 중인 스크립트가 들어 있는 디렉터리
- PYTHONPATH:
PYTHONPATH환경 변수에 나열된 디렉터리(설정된 경우) - 표준 라이브러리(Standard Library): Python에 내장된 모듈 디렉터리
- site-packages: pip로 설치된 서드파티 패키지
sys.path를 확인하면 Python의 검색 경로를 볼 수 있습니다:
import sys
for path in sys.path:
print(path)Output (example - your actual paths will differ based on your system and Python installation):
/home/user/projects/myproject
/usr/lib/python3.11
/usr/lib/python3.11/lib-dynload
/usr/local/lib/python3.11/site-packages출력 결과의 첫 번째 경로가 현재 작업 디렉터리입니다. Python은 이 디렉터리를 가장 먼저 검색하므로, 같은 디렉터리에 있는 모듈을 찾을 수 있습니다.
모듈 이름과 파일 이름
모듈 이름은 .py 확장자를 뺀 파일명입니다. 파일이 string_utils.py라면 모듈 이름은 string_utils입니다. 모듈 이름은 3장에서 배운 Python 식별자(identifier) 규칙을 따라야 합니다:
- 문자 또는 밑줄로 시작해야 합니다
- 문자, 숫자, 밑줄만 포함해야 합니다
- Python 키워드(keyword)일 수 없습니다
# 유효한 모듈 이름(및 파일명)
import data_processor # data_processor.py
import user_auth # user_auth.py
import _internal_helpers # _internal_helpers.py
# Invalid - would cause errors
# import 2d_graphics # Can't start with digit
# import my-module # Hyphens not allowed
# import class # 'class' is a keyword흔한 함정: 표준 라이브러리 모듈 가리기(Shadowing)
표준 라이브러리 모듈과 같은 이름으로 여러분의 모듈을 만들지 않도록 주의하세요. 프로젝트 디렉터리에 random.py라는 파일을 만들면, Python은 표준 라이브러리의 random 모듈 대신 여러분의 파일을 import하게 되어 혼란스러운 오류가 발생할 수 있습니다:
# 여러분의 파일: random.py
def my_function():
return 42
# 프로젝트의 다른 파일
import random
print(random.randint(1, 6)) # ERROR! Your random.py doesn't have randint()이를 피하려면, 해당 이름이 표준 라이브러리에서 이미 사용되고 있는지 모듈을 만들기 전에 확인하세요. Python 대화형 셸에서 import를 시도해볼 수 있습니다. 오류 없이 import된다면, 그 이름은 이미 사용 중입니다.
import 중에 무슨 일이 일어나는가
모듈을 import할 때 실제로 어떤 일이 일어나는지 살펴보겠습니다. demo_module.py라는 파일을 만드세요:
# demo_module.py
print("Module is being loaded!")
def greet():
print("Hello from demo_module")
print("Module loading complete!")이제 import 해봅시다:
# test_import.py
print("Before import")
import demo_module
print("After import")
demo_module.greet()Output:
Before import
Module is being loaded!
Module loading complete!
After import
Hello from demo_moduledemo_module.py 안의 print() 문이 import 중에 실행된다는 점을 확인하세요. 이는 모듈을 import하면 그 모듈의 최상위(top-level) 코드가 모두 실행된다는 것을 보여줍니다. 함수 정의는 나중에 사용하기 위해 저장되지만, 함수 밖의 코드는 즉시 실행됩니다.
같은 프로그램에서 동일한 모듈을 다시 import하면 로딩 메시지가 다시 나타나지 않습니다:
import demo_module # 첫 번째 import - 모듈 코드 실행
import demo_module # 두 번째 import - 캐시된 모듈 사용
import demo_module # 세 번째 import - 여전히 캐시된 모듈 사용Output:
Module is being loaded!
Module loading complete!모듈 코드는 import를 몇 번 하든 한 번만 실행됩니다.
22.2) import, from, as: 다양한 import 방식
Python은 모듈과 그 내용을 import하는 여러 방법을 제공합니다. 각 방식은 import한 이름에 접근하는 방식과 네임스페이스에 미치는 영향이 다릅니다.
기본 import 문
이미 보았던 기본 import 문은 모듈 전체를 로드합니다:
import math
result = math.sqrt(16)
print(result) # Output: 4.0
pi_value = math.pi
print(pi_value) # Output: 3.141592653589793이 방식에서는 항상 모듈 이름을 접두사로 사용합니다. 이렇게 하면 코드가 매우 명확해져서, 이름이 어디에서 왔는지 항상 알 수 있습니다.
from으로 특정 이름 가져오기
가끔은 모듈에서 한두 개만 필요할 때가 있습니다. from 문을 사용하면 특정 이름을 네임스페이스로 직접 가져올 수 있습니다:
from math import sqrt, pi
result = sqrt(25) # 'math.' 접두사가 필요 없습니다
print(result) # Output: 5.0
print(pi) # Output: 3.141592653589793이제 math. 접두사 없이 sqrt와 pi를 직접 사용할 수 있습니다. 자주 사용하는 경우에는 편리합니다.
우리의 greetings 모듈로도 다른 예시를 보겠습니다:
# from import 사용
from greetings import say_hello
message = say_hello("Diana") # 직접 접근
print(message) # Output: Hello, Diana!
# 하지만 say_goodbye는 사용할 수 없습니다. say_goodbye는 import 하지 않았기 때문입니다.
# say_goodbye("Diana") # NameError: name 'say_goodbye' is not defined한 문장으로 여러 이름을 import할 수도 있습니다:
from greetings import say_hello, say_goodbye, default_greeting
print(say_hello("Eve")) # Output: Hello, Eve!
print(say_goodbye("Frank")) # Output: Goodbye, Frank!
print(default_greeting) # Output: Welcome와일드카드 import(그리고 피해야 하는 이유)
Python은 *를 사용해 모듈에서 모든 것을 import할 수 있습니다:
from math import *
print(sqrt(9)) # Output: 3.0
print(cos(0)) # Output: 1.0
print(pi) # Output: 3.141592653589793이는 모듈의 모든 public 이름(밑줄로 시작하지 않는 이름)을 import합니다. 편리해 보이지만, 일반적으로는 나쁜 습관으로 여겨집니다. 이유는 다음과 같습니다:
- 네임스페이스 오염(Namespace Pollution): 어떤 이름을 import했는지 정확히 알기 어렵습니다
- 이름 충돌(Name Conflicts): import된 이름이 여러분의 변수 등을 덮어쓸 수 있습니다
- 가독성(Readability): 코드를 읽는 사람이 이름이 어디서 왔는지 알 수 없습니다
# 문제가 될 수 있는 예시
from math import *
# 코드의 나중 부분에서...
def sqrt(x):
"""여러분만의 제곱근 함수."""
return x ** 0.5
# 어떤 sqrt를 쓰고 있나요? 여러분의 것인가 math의 것인가?
result = sqrt(16) # 혼란스럽습니다!모범 사례(Best Practice): 특정 이름만 import하거나 기본 import 문을 사용하세요. from module import *는 실험을 위한 대화형 세션을 제외하고는 피하는 것이 좋습니다.
as로 import 이름 바꾸기
모듈이나 함수 이름이 길거나, 이름 충돌을 피하고 싶을 때가 있습니다. as 키워드를 사용하면 별칭(alias)을 만들 수 있습니다:
import math as m
result = m.sqrt(36)
print(result) # Output: 6.0이 방식은 이름이 긴 모듈이나, 흔한 관례를 따를 때 특히 유용합니다:
import datetime as dt
today = dt.date.today()
print(today) # Output: 2025-12-19 (or current date)특정 import 항목에 대해서도 이름을 바꿀 수 있습니다:
from math import sqrt as square_root
result = square_root(49)
print(result) # Output: 7.0이 방식은 이름 충돌이 있을 때 도움이 됩니다:
from math import sqrt as math_sqrt
def sqrt(x):
"""입력 검증이 포함된 사용자 정의 제곱근."""
if x < 0:
return None
return math_sqrt(x)
print(sqrt(25)) # Output: 5.0 (your function)
print(sqrt(-4)) # Output: None (your function)import 스타일 조합하기
같은 파일 안에서 여러 import 스타일을 섞을 수도 있습니다:
import math
from datetime import date, time
from random import randint as random_int
# math는 접두사와 함께 사용
radius = 5
area = math.pi * radius ** 2
# date와 time은 직접 사용
today = date.today()
current_time = time(14, 30)
# 이름을 바꾼 함수 사용
dice_roll = random_int(1, 6)올바른 import 스타일 선택하기
다음은 결정 가이드입니다:
import module을 사용할 때:
- 모듈에서 여러 항목이 필요할 때
- 이름이 어디에서 왔는지 최대한 명확히 하고 싶을 때
- 모듈 이름이 짧고 명확할 때
from module import name을 사용할 때:
- 특정 항목이 한두 개만 필요할 때
- 이름이 독특해서 충돌 가능성이 낮을 때
- 해당 이름을 자주 사용할 때
import module as alias를 사용할 때:
- 모듈 이름이 매우 길 때
import numpy as np같은 흔한 관례를 따를 때- 다른 모듈과의 충돌을 피해야 할 때
프로덕션 코드에서는 from module import *를 피하세요:
- 대화형 셸에서 빠르게 실험할 때만 사용하세요
- 다른 사람이 import할 모듈에서는 절대 사용하지 마세요
좋은 import 습관을 보여주는 완전한 예제를 보겠습니다:
# data_processor.py
import math
from statistics import mean, median
from datetime import datetime as dt
def calculate_statistics(numbers):
"""숫자 리스트에 대한 다양한 통계를 계산합니다."""
if not numbers:
return None
avg = mean(numbers)
mid = median(numbers)
std_dev = math.sqrt(sum((x - avg) ** 2 for x in numbers) / len(numbers))
return {
'mean': avg,
'median': mid,
'std_dev': std_dev,
'timestamp': dt.now()
}
# 함수 테스트
data = [10, 20, 30, 40, 50]
stats = calculate_statistics(data)
print(f"Mean: {stats['mean']}") # Output: Mean: 30.0
print(f"Median: {stats['median']}") # Output: Median: 30
print(f"Std Dev: {stats['std_dev']:.2f}") # Output: Std Dev: 14.14이 예제는 다음을 보여줍니다:
- 전체 모듈을 위해
import math사용(나중에 다른 math 함수를 사용할 수도 있음) - 자주 사용하는 특정 함수들을 위해
from statistics import mean, median사용 - 흔히 별칭을 사용하는 모듈을 위해
from datetime import datetime as dt사용
22.3) 자주 쓰는 Python 표준 라이브러리 모듈 개요
Python에는 풍부한 표준 라이브러리(standard library)가 포함되어 있습니다—일반적인 프로그래밍 작업에 대한 해법을 제공하는 모듈의 모음입니다. 이 모듈들은 항상 사용 가능하며, 별도의 설치가 필요하지 않습니다. 표준 라이브러리에 무엇이 있는지 이해하면 “바퀴를 다시 발명”하는 일을 피할 수 있습니다.
math 모듈
math 모듈은 기본 사칙연산을 넘어선 수학 함수를 제공합니다:
import math
# 삼각 함수
angle_rad = math.radians(45) # 도(degree)를 라디안(radian)으로 변환
print(math.sin(angle_rad)) # Output: 0.7071067811865476
print(math.cos(angle_rad)) # Output: 0.7071067811865475
# 올림/내림과 절댓값
print(math.ceil(4.2)) # Output: 5 (올림)
print(math.floor(4.8)) # Output: 4 (내림)
print(math.fabs(-7.5)) # Output: 7.5 (float로 절댓값)
# 지수와 로그
print(math.exp(2)) # Output: 7.38905609893065 (e^2)
print(math.log(100)) # Output: 4.605170185988092 (자연로그)
print(math.log10(100)) # Output: 2.0 (상용로그)
# 상수
print(math.pi) # Output: 3.141592653589793
print(math.e) # Output: 2.7182818284590454장에서 배웠듯이, math 모듈은 고급 수학 연산에 필수적입니다.
random 모듈
random 모듈은 의사 난수(pseudo-random) 값을 생성하고 무작위 선택을 수행합니다:
import random
# 난수 정수
dice = random.randint(1, 6) # 1부터 6까지의 난수 정수(양 끝 포함)
print(f"Dice roll: {dice}")
# 난수 실수
probability = random.random() # 0.0부터 1.0까지의 난수 실수
print(f"Probability: {probability:.4f}")
# 시퀀스에서 임의 선택
colors = ['red', 'blue', 'green', 'yellow']
chosen_color = random.choice(colors)
print(f"Chosen color: {chosen_color}")
# 리스트를 제자리에서 섞기
deck = ['A', 'K', 'Q', 'J', '10']
random.shuffle(deck)
print(f"Shuffled deck: {deck}")
# 중복 없이 무작위 샘플
lottery_numbers = random.sample(range(1, 50), 6)
print(f"Lottery numbers: {sorted(lottery_numbers)}")Output (example - will vary due to randomness):
Dice roll: 4
Probability: 0.7382
Chosen color: green
Shuffled deck: ['Q', 'A', '10', 'K', 'J']
Lottery numbers: [7, 15, 23, 31, 38, 42]datetime 모듈
datetime 모듈은 날짜(date)와 시간(time)을 다룹니다:
from datetime import date, time, datetime, timedelta
# 현재 날짜와 시간
today = date.today()
now = datetime.now()
print(f"Today: {today}") # Output: Today: 2025-12-19
print(f"Now: {now}") # Output: Now: 2025-12-19 14:30:45.123456
# 특정 날짜/시간 만들기
birthday = date(1990, 5, 15)
meeting_time = time(14, 30)
appointment = datetime(2025, 12, 25, 10, 0)
print(f"Birthday: {birthday}") # Output: Birthday: 1990-05-15
print(f"Meeting: {meeting_time}") # Output: Meeting: 14:30:00
print(f"Appointment: {appointment}") # Output: Appointment: 2025-12-25 10:00:00
# timedelta로 날짜 연산
tomorrow = today + timedelta(days=1)
next_week = today + timedelta(weeks=1)
print(f"Tomorrow: {tomorrow}") # Output: Tomorrow: 2025-12-20
print(f"Next week: {next_week}") # Output: Next week: 2025-12-26
# 구성 요소 추출
print(f"Year: {today.year}") # Output: Year: 2025
print(f"Month: {today.month}") # Output: Month: 12
print(f"Day: {today.day}") # Output: Day: 19os 모듈
os 모듈은 운영체제 기능을 제공합니다. 26장에서 자세히 다루겠지만, 여기서는 미리보기로 살펴보겠습니다:
import os
# 현재 작업 디렉터리
current_dir = os.getcwd()
print(f"Current directory: {current_dir}")
# 디렉터리의 파일 목록
files = os.listdir('.')
print(f"Files: {files[:3]}") # 처음 3개 파일만 표시
# 경로가 존재하는지 확인
exists = os.path.exists('myfile.txt')
print(f"File exists: {exists}")
# 경로 구성 요소 결합(운영체제 간 호환)
file_path = os.path.join('data', 'users', 'profile.txt')
print(f"Path: {file_path}") # Output: data/users/profile.txt (or data\users\profile.txt on Windows)sys 모듈
sys 모듈은 시스템 관련 파라미터와 함수를 제공합니다:
import sys
# Python 버전 정보
print(f"Python version: {sys.version}")
print(f"Version info: {sys.version_info}")
# 플랫폼 정보
print(f"Platform: {sys.platform}") # Output: linux, darwin, win32, etc.
# 최대 정수 크기
print(f"Max int: {sys.maxsize}")statistics 모듈
statistics 모듈은 통계 계산을 위한 함수를 제공합니다:
import statistics
grades = [85, 92, 78, 90, 88, 95, 82]
# 중심 경향
avg = statistics.mean(grades)
mid = statistics.median(grades)
mode_val = statistics.mode([1, 2, 2, 3, 3, 3, 4])
print(f"Mean: {avg}") # Output: Mean: 87.14285714285714
print(f"Median: {mid}") # Output: Median: 88
print(f"Mode: {mode_val}") # Output: Mode: 3
# 산포
std_dev = statistics.stdev(grades)
variance = statistics.variance(grades)
print(f"Standard deviation: {std_dev:.2f}") # Output: Standard deviation: 5.90
print(f"Variance: {variance:.2f}") # Output: Variance: 34.81collections 모듈
collections 모듈은 특수한 컨테이너 타입을 제공합니다. 39장에서 더 자세히 다루겠지만, 여기서는 간단히 맛보기로 보겠습니다:
from collections import Counter, defaultdict
# Counter - 발생 횟수 세기
text = "hello world"
letter_counts = Counter(text)
print(letter_counts) # Output: Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})
print(letter_counts['l']) # Output: 3
# defaultdict - 기본값이 있는 딕셔너리
word_lists = defaultdict(list)
word_lists['fruits'].append('apple')
word_lists['fruits'].append('banana')
word_lists['vegetables'].append('carrot')
print(dict(word_lists)) # Output: {'fruits': ['apple', 'banana'], 'vegetables': ['carrot']}더 많은 표준 라이브러리 모듈 찾기
Python의 표준 라이브러리에는 200개가 넘는 모듈이 있습니다. 여러 방법으로 살펴볼 수 있습니다:
# 사용 가능한 모든 모듈 보기(잠시 걸립니다)
help('modules')
# 특정 모듈 도움말 보기
import math
help(math)
# 모듈에 무엇이 들어있는지 보기
import random
print(dir(random))Python 문서(https://docs.python.org/3/library/)에는 모든 표준 라이브러리 모듈에 대한 포괄적인 정보가 제공됩니다. 경험이 쌓일수록 어떤 모듈이 여러분의 작업에 가장 유용한지 알게 될 것입니다.
22.4) 나만의 모듈 만들고 사용하기
나만의 모듈을 만드는 일은 간단합니다—어떤 Python 파일이든 모듈이 될 수 있습니다. 핵심은 모듈이 집중적이고, 재사용 가능하며, 이해하기 쉽도록 코드를 신중하게 구성하는 것입니다.
간단한 모듈 만들기
학생 성적을 다루는 모듈을 만들어 보겠습니다. grade_calculator.py라는 파일을 생성하세요:
# grade_calculator.py
"""학생 성적을 계산하고 분석하는 모듈."""
def calculate_average(grades):
"""성적 리스트의 평균을 계산합니다."""
if not grades:
return 0
return sum(grades) / len(grades)
def get_letter_grade(numeric_grade):
"""숫자 성적을 문자 성적으로 변환합니다."""
if numeric_grade >= 90:
return 'A'
elif numeric_grade >= 80:
return 'B'
elif numeric_grade >= 70:
return 'C'
elif numeric_grade >= 60:
return 'D'
else:
return 'F'
def find_highest(grades):
"""리스트에서 가장 높은 성적을 찾습니다."""
if not grades:
return None
return max(grades)
def find_lowest(grades):
"""리스트에서 가장 낮은 성적을 찾습니다."""
if not grades:
return None
return min(grades)
# 모듈 레벨 상수
PASSING_GRADE = 60
HONOR_ROLL_THRESHOLD = 90이제 이 모듈을 사용하는 다른 파일을 만드세요:
# student_report.py
import grade_calculator
# 학생의 시험 점수
test_scores = [85, 92, 78, 88, 95]
# 통계 계산
average = grade_calculator.calculate_average(test_scores)
letter = grade_calculator.get_letter_grade(average)
highest = grade_calculator.find_highest(test_scores)
lowest = grade_calculator.find_lowest(test_scores)
# 리포트 생성
print("Student Grade Report")
print("=" * 40)
print(f"Test Scores: {test_scores}")
print(f"Average: {average:.1f}")
print(f"Letter Grade: {letter}")
print(f"Highest Score: {highest}")
print(f"Lowest Score: {lowest}")
# 우등생 여부 확인
if average >= grade_calculator.HONOR_ROLL_THRESHOLD:
print("Status: HONOR ROLL!")
elif average >= grade_calculator.PASSING_GRADE:
print("Status: Passing")
else:
print("Status: Needs Improvement")Output:
Student Grade Report
========================================
Test Scores: [85, 92, 78, 88, 95]
Average: 87.6
Letter Grade: B
Highest Score: 95
Lowest Score: 78
Status: Passing모듈 문서화
grade_calculator.py 맨 위에 있는 docstring을 확인하세요. 이 모듈 레벨 docstring은 모듈이 무엇을 하는지 설명합니다. 누군가 help()를 사용하면 표시됩니다:
import grade_calculator
help(grade_calculator)이는 모듈 docstring과 모든 함수 docstring을 포함한 모듈 문서를 표시합니다. 좋은 문서는 모듈을 더 쉽게 사용할 수 있게 해줍니다.
모듈 레벨 변수와 상수
모듈은 모듈을 사용하는 모든 곳에서 공유되는 변수를 포함할 수 있습니다. 이는 보통 설정(configuration)이나 상수(constants)에 사용됩니다:
# config.py
"""애플리케이션 설정."""
# 데이터베이스 설정
DB_HOST = "localhost"
DB_PORT = 5432
DB_NAME = "myapp"
# 애플리케이션 설정
MAX_LOGIN_ATTEMPTS = 3
SESSION_TIMEOUT = 1800 # seconds
DEBUG_MODE = False
# 파일 경로
DATA_DIR = "/var/data"
LOG_DIR = "/var/log"
# 기능 플래그
ENABLE_CACHING = True
ENABLE_LOGGING = True모듈에서 설정을 사용하는 방법:
# app.py
import config
def connect_database():
"""config 설정을 사용해 데이터베이스에 연결합니다."""
print(f"Connecting to {config.DB_HOST}:{config.DB_PORT}")
print(f"Database: {config.DB_NAME}")
if config.DEBUG_MODE:
print("DEBUG: Connection details logged")
def check_login_attempts(attempts):
"""로그인 시도 횟수가 제한을 초과했는지 확인합니다."""
if attempts >= config.MAX_LOGIN_ATTEMPTS:
print(f"Too many attempts! Maximum is {config.MAX_LOGIN_ATTEMPTS}")
return False
return True
connect_database()
print(check_login_attempts(2)) # Output: True
print(check_login_attempts(4)) # Output: Too many attempts! Maximum is 3Output:
Connecting to localhost:5432
Database: myapp
True
Too many attempts! Maximum is 3중요: 모듈 레벨 변수는 모든 import에서 공유됩니다. 모듈 변수를 수정하면, 해당 모듈을 사용하는 모든 코드에 변경이 영향을 미칩니다:
# file1.py
import config
config.DEBUG_MODE = True
print(f"File1 - Debug mode: {config.DEBUG_MODE}")
# file2.py
import config
print(f"File2 - Debug mode: {config.DEBUG_MODE}") # Will be True!이 동작은 유용할 수도 있지만 예상 밖일 수도 있습니다. 모듈 레벨 변수를 수정할 때는 주의하세요.
모듈의 비공개(Private) 이름
관례적으로, 밑줄로 시작하는 이름은 모듈의 비공개(private) 또는 내부(internal) 항목으로 간주합니다:
# user_manager.py
"""사용자 계정을 관리하는 모듈."""
# 비공개 헬퍼 함수
def _validate_email(email):
"""이메일 형식을 검증하는 내부 함수."""
return '@' in email and '.' in email
# 공개 함수
def create_user(username, email):
"""새 사용자 계정을 생성합니다."""
if not _validate_email(email):
return None
user = {
'username': username,
'email': email,
'active': True
}
return user
# 비공개 상수
_MAX_USERNAME_LENGTH = 20
# 공개 상수
MIN_PASSWORD_LENGTH = 8from user_manager import *를 사용하면 비공개 이름(밑줄로 시작하는 이름)은 import되지 않습니다. 하지만 필요하다면 여전히 명시적으로 접근할 수는 있습니다:
import user_manager
# 공개 함수 - 사용을 의도한 함수
user = user_manager.create_user("alice", "alice@example.com")
# 비공개 함수 - 접근은 가능하지만 의존하면 안 됩니다
# (향후 버전에서 바뀔 수 있습니다)
is_valid = user_manager._validate_email("test@test.com")밑줄 접두사는 다른 프로그래머에게 보내는 신호입니다: “이건 구현 세부사항입니다. 그대로 유지된다고 기대하지 마세요.”
22.5) 패키지와 __init__.py 이해하기
프로젝트가 커지면 관련된 여러 모듈을 패키지(package)로 구성하고 싶어질 것입니다. 패키지는 Python 모듈을 포함하는 디렉터리와 특별한 __init__.py 파일로 이루어집니다.
패키지란 무엇인가?
패키지(package)는 여러 모듈을 계층적 구조로 구성하는 방법입니다. Python 파일이 들어 있는 폴더라고 생각하면 되며, 그 폴더 자체를 import할 수 있습니다.
간단한 패키지 구조는 다음과 같습니다:
myproject/
main.py
utilities/
__init__.py
text.py
math.py
file.py이 구조에서 utilities는 text, math, file 세 개의 모듈을 포함하는 패키지입니다. __init__.py 파일(비어 있어도 됨)은 Python에게 utilities가 패키지임을 알려줍니다.
간단한 패키지 만들기
데이터 처리를 위한 패키지를 만들어 보겠습니다. 먼저 다음 디렉터리 구조를 만드세요:
data_tools/
__init__.py
validators.py
formatters.pyvalidators.py를 만드세요:
# data_tools/validators.py
"""데이터 검증 함수."""
def is_valid_email(email):
"""이메일이 기본적으로 유효한 형식인지 확인합니다."""
return '@' in email and '.' in email.split('@')[1]
def is_valid_phone(phone):
"""전화번호 형식이 유효한지 확인합니다(간단한 검사)."""
digits = ''.join(c for c in phone if c.isdigit())
return len(digits) == 10
def is_positive_number(value):
"""값이 양수인지 확인합니다."""
try:
return float(value) > 0
except (ValueError, TypeError):
return Falseformatters.py를 만드세요:
# data_tools/formatters.py
"""데이터 포매팅 함수."""
def format_phone(phone):
"""전화번호를 (XXX) XXX-XXXX 형식으로 포맷합니다."""
digits = ''.join(c for c in phone if c.isdigit())
if len(digits) != 10:
return phone
return f"({digits[:3]}) {digits[3:6]}-{digits[6:]}"
def format_currency(amount):
"""숫자를 통화 형식으로 포맷합니다."""
return f"${amount:,.2f}"
def format_percentage(value, decimals=1):
"""숫자를 백분율 형식으로 포맷합니다."""
return f"{value * 100:.{decimals}f}%"비어 있는 __init__.py를 만드세요:
# data_tools/__init__.py
"""데이터 처리 도구 패키지."""패키지에서 import하기
이제 패키지에서 여러 방식으로 import할 수 있습니다:
# Method 1: 패키지에서 모듈 import
import data_tools.validators
email = "user@example.com"
is_valid = data_tools.validators.is_valid_email(email)
print(f"Email valid: {is_valid}") # Output: Email valid: True
# Method 2: from으로 특정 모듈 import
from data_tools import formatters
phone = "1234567890"
formatted = formatters.format_phone(phone)
print(f"Formatted phone: {formatted}") # Output: Formatted phone: (123) 456-7890
# Method 3: 특정 함수 import
from data_tools.validators import is_valid_phone
from data_tools.formatters import format_currency
print(is_valid_phone("555-1234")) # Output: False (not 10 digits)
print(format_currency(1234.56)) # Output: $1,234.56__init__.py 파일
__init__.py 파일은 두 가지 목적을 갖습니다:
- 디렉터리를 패키지로 표시: Python은
__init__.py가 있는 디렉터리를 패키지로 인식합니다 - 패키지 초기화 코드:
__init__.py의 코드는 패키지가 처음 import될 때 실행됩니다
__init__.py는 비어 있을 수도 있지만, 패키지를 더 쉽게 쓰도록 코드를 포함할 수도 있습니다:
# data_tools/__init__.py
"""데이터 처리 도구 패키지."""
# 자주 쓰는 함수를 패키지 네임스페이스로 import
from data_tools.validators import is_valid_email, is_valid_phone
from data_tools.formatters import format_phone, format_currency
# 패키지 버전
__version__ = '1.0.0'
# 패키지 레벨 상수
DEFAULT_CURRENCY_SYMBOL = '$'이제 사용자는 패키지에서 직접 import할 수 있습니다:
# Instead of: from data_tools.validators import is_valid_email
# You can write:
from data_tools import is_valid_email, format_currency
print(is_valid_email("test@test.com")) # Output: True
print(format_currency(99.99)) # Output: $99.9922.6) __name__ 변수와 if __name__ == "__main__": 파일을 스크립트로 실행하기
Python 파일은 두 가지 목적을 가질 수 있습니다. 모듈로 import될 수도 있고, 독립 실행 스크립트로 실행될 수도 있습니다. 특별한 __name__ 변수는 두 경우 모두에서 잘 동작하는 코드를 작성할 수 있게 해줍니다.
__name__ 이해하기
모든 Python 모듈에는 __name__이라는 내장 변수가 있습니다. Python은 파일이 어떻게 사용되는지에 따라 이 변수를 다르게 설정합니다:
- import될 때:
__name__은 모듈 이름으로 설정됩니다 - 직접 실행될 때:
__name__은"__main__"으로 설정됩니다
직접 확인해 봅시다. demo_name.py라는 파일을 만드세요:
# demo_name.py
print(f"The __name__ variable is: {__name__}")이제 직접 실행합니다:
python demo_name.pyOutput:
The __name__ variable is: __main__이제 다른 파일에서 import해 봅시다:
# test_import.py
import demo_nameOutput:
The __name__ variable is: demo_namedemo_name.py를 직접 실행하면 Python은 __name__을 "__main__"으로 설정합니다. import하면 __name__을 모듈 이름("demo_name")으로 설정합니다.
if __name__ == "__main__": 패턴
이 동작을 이용하면 파일이 import될 때는 실행되지 않고, 직접 실행될 때만 실행되는 코드를 작성할 수 있습니다. 이는 다음 패턴으로 구현합니다:
if __name__ == "__main__":
# 이 코드는 파일이 직접 실행될 때만 실행됩니다
pass왜 유용한지 살펴보겠습니다. math_utils.py를 만드세요:
# math_utils.py
"""수학 연산을 위한 유틸리티 함수."""
def calculate_area(radius):
"""원의 넓이를 계산합니다."""
return 3.14159 * radius ** 2
def calculate_circumference(radius):
"""원의 둘레를 계산합니다."""
return 2 * 3.14159 * radius
# 테스트 코드 - 파일이 직접 실행될 때만 실행됨
if __name__ == "__main__":
print("Testing math_utils functions...")
test_radius = 5
area = calculate_area(test_radius)
circumference = calculate_circumference(test_radius)
print(f"Radius: {test_radius}")
print(f"Area: {area:.2f}")
print(f"Circumference: {circumference:.2f}")이 파일을 직접 실행하면:
python math_utils.pyOutput:
Testing math_utils functions...
Radius: 5
Area: 78.54
Circumference: 31.42하지만 import하면:
# use_math_utils.py
import math_utils
# 테스트 코드는 실행되지 않습니다!
area = math_utils.calculate_area(10)
print(f"Area of circle: {area:.2f}") # Output: Area of circle: 314.16if __name__ == "__main__": 블록 안의 테스트 코드는 import 중에는 실행되지 않습니다. 이렇게 하면 모듈에 테스트 코드, 예시, 데모를 포함하더라도 이를 import하는 코드에는 영향을 주지 않습니다.
if __name__ == "__main__":의 일반적인 사용처
테스트와 데모
모듈 사용 방법을 보여주는 예시를 포함합니다:
# string_tools.py
def reverse_string(text):
"""문자열을 뒤집습니다."""
return text[::-1]
def count_vowels(text):
"""텍스트의 모음 개수를 셉니다."""
vowels = 'aeiouAEIOU'
return sum(1 for char in text if char in vowels)
if __name__ == "__main__":
# 데모 코드
sample = "Hello, World!"
print(f"Original: {sample}")
print(f"Reversed: {reverse_string(sample)}")
print(f"Vowels: {count_vowels(sample)}")이 장에서는 모듈과 패키지를 사용해 Python 코드를 구성하는 방법을 배웠습니다. import 시스템이 어떻게 동작하는지, 코드를 import하는 다양한 방법, 그리고 우리만의 모듈과 패키지를 만드는 방법을 살펴보았습니다. 또한 __name__ 변수와 파일이 import 가능한 모듈이면서도 독립 실행 스크립트로도 동작하도록 해주는 if __name__ == "__main__": 패턴도 배웠습니다.
이러한 구성 도구는 프로그램이 커질수록 점점 더 중요해집니다. 다음 장에서는 함수를 데이터처럼 사용하는 방법과 간단한 함수형 프로그래밍 기법을 살펴보며, 여기서 구축한 코드 구성의 탄탄한 기초 위에서 계속 발전해 나가겠습니다.