Python 기초 문법을 익힌 뒤에는 코드를 어떻게 구조화할 것인지가 중요해진다.
함수만으로도 많은 프로그램을 만들 수 있지만 상태와 기능을 하나로 묶거나, 여러 파일로 코드를 나누거나, 예상 가능한 오류를 안전하게 처리하려면 클래스·모듈·패키지·예외 처리 개념이 필요하다.
사용자 데이터를 딕셔너리로 관리할 수 있다.
user = {
"name": "kim",
"point": 1000,
}
포인트 변경 함수도 만들 수 있다.
def add_point(
user,
amount,
):
user["point"] += amount
작은 코드에서는 충분하다.
하지만 동일한 구조의 사용자가 많아지고 각 사용자가 자신의 상태를 가져야 한다면 클래스로 묶는 것이 자연스럽다.
class User:
pass
객체 생성:
user = User()
user는 User 클래스의 인스턴스다.
클래스는 사용자 정의 타입, 인스턴스는 그 타입으로 만든 실제 객체라고 이해하면 된다.
__init__객체 생성 시 초기 상태를 설정한다.
class User:
def __init__(
self,
name,
point=0,
):
self.name = name
self.point = point
user = User(
"kim",
1000,
)
print(user.name)
print(user.point)
self인스턴스 메서드의 첫 번째 매개변수는 관례적으로 self를 사용한다.
class User:
def __init__(
self,
name,
):
self.name = name
def greet(self):
print(
f"안녕하세요. "
f"{self.name}입니다."
)
user = User("kim")
user.greet()
self는 현재 메서드를 호출한 인스턴스를 가리킨다.
class User:
def __init__(
self,
name,
point=0,
):
self.name = name
self.point = point
def add_point(
self,
amount,
):
self.point += amount
def summary(self):
return (
f"{self.name}: "
f"{self.point}P"
)
user = User("kim")
user.add_point(300)
user.add_point(500)
print(user.summary())
각 인스턴스는 독립적인 상태를 가진다.
인스턴스마다 다른 값:
class User:
def __init__(
self,
name,
):
self.name = name
모든 인스턴스가 공유하는 값:
class User:
service_name = "My Service"
변경 가능한 리스트 같은 객체를 클래스 변수로 둘 때는 주의해야 한다.
class User:
tags = []
모든 인스턴스가 같은 리스트를 공유한다.
각 객체마다 별도 리스트가 필요하다면 다음처럼 만든다.
class User:
def __init__(self):
self.tags = []
공통 기능을 가진 부모 클래스를 확장할 수 있다.
class Account:
def __init__(
self,
email,
):
self.email = email
def login(self):
print(
f"{self.email} 로그인"
)
class AdminAccount(
Account
):
def delete_user(
self,
user_id,
):
print(
f"{user_id}번 "
"사용자 삭제"
)
admin = AdminAccount(
"admin@example.com"
)
admin.login()
admin.delete_user(10)
부모의 동작을 자식 클래스에서 다시 정의할 수 있다.
class Notification:
def send(
self,
message,
):
print(
f"알림: {message}"
)
class EmailNotification(
Notification
):
def send(
self,
message,
):
print(
f"이메일 발송: "
f"{message}"
)
super()부모 클래스 초기화나 메서드를 활용할 때 사용한다.
class AdminAccount(
Account
):
def __init__(
self,
email,
level,
):
super().__init__(
email
)
self.level = level
Python의 내장 문법과 객체를 연결할 수 있다.
__str__class User:
def __init__(
self,
name,
):
self.name = name
def __str__(self):
return (
f"User("
f"name={self.name}"
f")"
)
print(User("kim"))
__len__class Team:
def __init__(
self,
members,
):
self.members = members
def __len__(self):
return len(
self.members
)
Python 파일 하나는 모듈로 사용할 수 있다.
price_utils.py
def calculate_discount(
price,
rate,
):
return (
price
* (1 - rate)
)
다른 파일:
import price_utils
result = (
price_utils
.calculate_discount(
50000,
0.1,
)
)
from ... import ...from math import sqrt
print(sqrt(16))
출처가 명확해야 하는 코드에서는 다음처럼 모듈명을 유지하는 편이 읽기 좋을 수 있다.
import math
print(
math.sqrt(16)
)
from module import *는 어떤 이름이 들어왔는지 알기 어려워질 수 있어 피하는 편이 좋다.
__name__ == "__main__"파일이 직접 실행된 경우에만 특정 코드를 실행하고 싶을 때 사용한다.
def main():
print("프로그램 시작")
if __name__ == "__main__":
main()
다른 파일에서 import할 때는 main()이 자동 실행되지 않는다.
여러 모듈을 디렉터리 구조로 묶을 수 있다.
my_app/
main.py
services/
__init__.py
user_service.py
order_service.py
utils/
__init__.py
price.py
사용:
from services.user_service import create_user
from utils.price import calculate_total
현대 Python은 namespace package도 지원하지만 일반적인 프로젝트에서는 __init__.py를 두는 구조를 여전히 자주 볼 수 있다.
ModuleNotFoundError가 발생한다면먼저 현재 Python과 모듈 검색 경로를 확인한다.
import sys
print(sys.executable)
for path in sys.path:
print(path)
외부 패키지라면 설치 환경도 확인한다.
python -m pip --version
python -m pip list
실행 중 발생할 수 있는 오류를 처리한다.
try:
age = int(
input("나이: ")
)
except ValueError:
print(
"숫자를 입력해 주세요."
)
try:
number = int(
input("숫자: ")
)
print(
100 / number
)
except ValueError:
print(
"정수가 필요합니다."
)
except ZeroDivisionError:
print(
"0으로 나눌 수 없습니다."
)
처리 가능한 구체적인 예외를 지정하는 것이 좋다.
else예외가 없었을 때만 실행된다.
try:
number = int(
input("숫자: ")
)
except ValueError:
print("잘못된 입력")
else:
print(
f"입력 성공: "
f"{number}"
)
finally예외 여부와 관계없이 실행된다.
try:
print("작업")
finally:
print("정리")
파일과 같은 자원은 with 문을 사용하는 편이 더 편리한 경우가 많다.
raise프로그램 규칙에 맞지 않는 상태를 직접 예외로 만들 수 있다.
def withdraw(
balance,
amount,
):
if amount <= 0:
raise ValueError(
"금액은 0보다 "
"커야 합니다."
)
if amount > balance:
raise ValueError(
"잔액이 부족합니다."
)
return (
balance
- amount
)
class InsufficientStockError(
Exception
):
pass
def purchase(
stock,
quantity,
):
if quantity > stock:
raise (
InsufficientStockError(
"재고가 부족합니다."
)
)
비즈니스 의미가 분명해진다.
다음 패턴은 문제를 숨길 수 있다.
try:
...
except:
pass
오류를 처리할 수 없다면 적어도 로그를 남기거나 상위 코드에 다시 전달하는 것이 좋다.
try:
...
except ValueError:
print("값 처리 실패")
raise
상태가 거의 없는 단순 계산은 함수가 간단하다.
def normalize_email(
email,
):
return (
email
.strip()
.lower()
)
반면 다음 조건이 많다면 클래스를 고려할 수 있다.
self는 현재 인스턴스, __init__()은 초기화를 담당한다..py 파일 하나는 모듈로 사용할 수 있고 여러 모듈을 패키지로 구조화할 수 있다.__name__ == "__main__" 패턴은 직접 실행과 import를 구분한다.try-except는 실행 중 예상 가능한 예외를 처리한다.raise를 사용하면 프로그램 규칙 위반을 명시적인 오류로 표현할 수 있다.