4. 클래스와 객체지향
4.1 클래스 기초
4.1.1 클래스 정의와 객체 생성
# 기본 클래스 정의
class Person:
# 클래스 변수 (모든 인스턴스가 공유)
species = "Homo sapiens"
# 생성자 메서드
def __init__(self, name, age):
# 인스턴스 변수
self.name = name
self.age = age
# 인스턴스 메서드
def introduce(self):
return f"안녕하세요, 저는 {self.name}이고 {self.age}세입니다."
def have_birthday(self):
self.age += 1
print(f"{self.name}의 나이가 {self.age}세가 되었습니다.")
# 객체 생성
person1 = Person("Alice", 25)
person2 = Person("Bob", 30)
# 메서드 호출
print(person1.introduce()) # "안녕하세요, 저는 Alice이고 25세입니다."
person1.have_birthday() # "Alice의 나이가 26세가 되었습니다."
# 속성 접근
print(person1.name) # "Alice"
print(person1.age) # 26
print(Person.species) # "Homo sapiens"
4.1.2 클래스 메서드와 정적 메서드
class MathUtils:
pi = 3.14159
def __init__(self, name):
self.name = name
# 인스턴스 메서드
def instance_method(self):
return f"{self.name}에서 호출됨"
# 클래스 메서드
@classmethod
def circle_area(cls, radius):
return cls.pi * radius ** 2
# 정적 메서드
@staticmethod
def add(a, b):
return a + b
# 사용 예시
math_obj = MathUtils("계산기")
# 인스턴스 메서드 (객체를 통해 호출)
print(math_obj.instance_method())
# 클래스 메서드 (클래스를 통해 호출)
print(MathUtils.circle_area(5)) # 78.53975
# 정적 메서드 (클래스나 객체 모두를 통해 호출 가능)
print(MathUtils.add(3, 4)) # 7
print(math_obj.add(3, 4)) # 7
4.2 상속
4.2.1 기본 상속
# 부모 클래스
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
pass # 추상 메서드 (자식 클래스에서 구현)
def info(self):
return f"이름: {self.name}"
# 자식 클래스
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # 부모 클래스 생성자 호출
self.breed = breed
def speak(self): # 메서드 오버라이딩
return "멍멍!"
def fetch(self): # 자식 클래스만의 메서드
return f"{self.name}이 공을 가져왔습니다."
class Cat(Animal):
def speak(self):
return "야옹!"
def climb(self):
return f"{self.name}이 나무에 올라갔습니다."
# 사용 예시
dog = Dog("바둑이", "진돗개")
cat = Cat("나비")
print(dog.info()) # "이름: 바둑이"
print(dog.speak()) # "멍멍!"
print(dog.fetch()) # "바둑이이 공을 가져왔습니다."
print(cat.info()) # "이름: 나비"
print(cat.speak()) # "야옹!"
print(cat.climb()) # "나비이 나무에 올라갔습니다."
# 다형성
animals = [dog, cat]
for animal in animals:
print(f"{animal.name}: {animal.speak()}")
4.2.2 다중 상속
class Flyable:
def fly(self):
return "날고 있습니다."
class Swimmable:
def swim(self):
return "수영하고 있습니다."
class Duck(Animal, Flyable, Swimmable):
def speak(self):
return "꽥꽥!"
duck = Duck("도날드")
print(duck.speak()) # "꽥꽥!"
print(duck.fly()) # "날고 있습니다."
print(duck.swim()) # "수영하고 있습니다."
# MRO (Method Resolution Order) 확인
print(Duck.__mro__) # 메서드 검색 순서
4.3 캡슐화
4.3.1 접근 제어자
class BankAccount:
def __init__(self, owner, initial_balance=0):
self.owner = owner # public
self._balance = initial_balance # protected (관례상 비공개)
self.__account_number = self._generate_account_number() # private
def _generate_account_number(self): # protected 메서드
import random
return f"ACC{random.randint(100000, 999999)}"
def __validate_amount(self, amount): # private 메서드
return amount > 0
# 잔액 조회 (getter)
def get_balance(self):
return self._balance
# 입금
def deposit(self, amount):
if self.__validate_amount(amount):
self._balance += amount
return True
return False
# 출금
def withdraw(self, amount):
if self.__validate_amount(amount) and amount <= self._balance:
self._balance -= amount
return True
return False
def get_account_info(self):
return f"계좌주: {self.owner}, 계좌번호: {self.__account_number}"
# 사용 예시
account = BankAccount("Alice", 1000)
print(account.owner) # "Alice" (public 접근 가능)
print(account.get_balance()) # 1000
account.deposit(500)
print(account.get_balance()) # 1500
# print(account.__account_number) # AttributeError (private 접근 불가)
# 하지만 name mangling으로 접근 가능 (권장하지 않음)
print(account._BankAccount__account_number) # 실제 계좌번호
4.3.2 프로퍼티 (Property)
class Temperature:
def __init__(self):
self._celsius = 0
# getter
@property
def celsius(self):
return self._celsius
# setter
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("절대영도보다 낮을 수 없습니다.")
self._celsius = value
# 계산된 속성
@property
def fahrenheit(self):
return (self._celsius * 9/5) + 32
@fahrenheit.setter
def fahrenheit(self, value):
self._celsius = (value - 32) * 5/9
@property
def kelvin(self):
return self._celsius + 273.15
# 사용 예시
temp = Temperature()
temp.celsius = 25
print(f"섭씨: {temp.celsius}°C") # 25°C
print(f"화씨: {temp.fahrenheit}°F") # 77.0°F
print(f"켈빈: {temp.kelvin}K") # 298.15K
temp.fahrenheit = 100
print(f"섭씨: {temp.celsius}°C") # 37.77777777777778°C
4.4 특수 메서드 (매직 메서드)
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
# 문자열 표현
def __str__(self):
return f"Vector({self.x}, {self.y})"
def __repr__(self):
return f"Vector({self.x}, {self.y})"
# 연산자 오버로딩
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return Vector(self.x - other.x, self.y - other.y)
def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
# 비교 연산자
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __lt__(self, other):
return self.magnitude() < other.magnitude()
# 길이 반환
def __len__(self):
return int(self.magnitude())
# 인덱싱 지원
def __getitem__(self, index):
if index == 0:
return self.x
elif index == 1:
return self.y
else:
raise IndexError("벡터는 0, 1 인덱스만 지원합니다.")
def magnitude(self):
return (self.x**2 + self.y**2)**0.5
# 사용 예시
v1 = Vector(3, 4)
v2 = Vector(1, 2)
print(v1) # Vector(3, 4)
print(v1 + v2) # Vector(4, 6)
print(v1 - v2) # Vector(2, 2)
print(v1 * 2) # Vector(6, 8)
print(v1 == v2) # False
print(len(v1)) # 5
print(v1[0], v1[1]) # 3 4
5. 모듈과 패키지
5.1 모듈
5.1.1 모듈 생성과 import
# math_utils.py 파일 생성
def add(a, b):
"""두 수를 더합니다."""
return a + b
def multiply(a, b):
"""두 수를 곱합니다."""
return a * b
PI = 3.14159
class Calculator:
def __init__(self):
self.history = []
def calculate(self, operation, a, b):
if operation == "add":
result = add(a, b)
elif operation == "multiply":
result = multiply(a, b)
else:
result = None
self.history.append(f"{operation}({a}, {b}) = {result}")
return result
# 실행될 때만 동작하는 코드
if __name__ == "__main__":
print("math_utils 모듈이 직접 실행되었습니다.")
print(f"PI 값: {PI}")
# main.py 파일에서 모듈 사용
# 1. 전체 모듈 import
import math_utils
result = math_utils.add(3, 4)
print(result) # 7
# 2. 특정 함수만 import
from math_utils import add, PI
result = add(5, 6)
print(f"결과: {result}, PI: {PI}")
# 3. 별칭 사용
import math_utils as math
result = math.multiply(3, 4)
# 4. 모든 것을 import (권장하지 않음)
from math_utils import *
calc = Calculator()
print(calc.calculate("add", 10, 20))
5.1.2 내장 모듈 사용
# datetime 모듈
import datetime
from datetime import datetime, timedelta
now = datetime.now()
print(f"현재 시간: {now}")
tomorrow = now + timedelta(days=1)
print(f"내일: {tomorrow}")
# random 모듈
import random
print(random.randint(1, 10)) # 1~10 사이 정수
print(random.choice([1, 2, 3, 4, 5])) # 리스트에서 랜덤 선택
print(random.random()) # 0~1 사이 실수
# os 모듈
import os
print(os.getcwd()) # 현재 작업 디렉토리
print(os.listdir('.')) # 현재 디렉토리 파일 목록
# sys 모듈
import sys
print(sys.version) # Python 버전
print(sys.path) # 모듈 검색 경로
# math 모듈
import math
print(math.sqrt(16)) # 4.0
print(math.pi) # 3.141592653589793
print(math.ceil(4.3)) # 5 (올림)
print(math.floor(4.7)) # 4 (내림)
# json 모듈
import json
data = {"name": "Alice", "age": 25}
json_string = json.dumps(data) # 딕셔너리를 JSON 문자열로
print(json_string) # {"name": "Alice", "age": 25}
parsed_data = json.loads(json_string) # JSON 문자열을 딕셔너리로
print(parsed_data) # {'name': 'Alice', 'age': 25}
5.2 패키지
5.2.1 패키지 구조 생성
my_package/
__init__.py
math_operations/
__init__.py
basic.py
advanced.py
string_operations/
__init__.py
formatting.py
validation.py
# my_package/__init__.py
"""
수학과 문자열 연산을 위한 패키지
"""
__version__ = "1.0.0"
__author__ = "Your Name"
# 패키지 수준에서 import할 수 있는 것들
from .math_operations.basic import add, subtract
from .string_operations.formatting import capitalize_words
# my_package/math_operations/__init__.py
from .basic import add, subtract, multiply, divide
from .advanced import power, sqrt
# my_package/math_operations/basic.py
def add(a, b):
return a + b
def subtract(a, b):
return a - b
def multiply(a, b):
return a * b
def divide(a, b):
if b == 0:
raise ValueError("0으로 나눌 수 없습니다.")
return a / b
# my_package/math_operations/advanced.py
import math
def power(base, exponent):
return base ** exponent
def sqrt(number):
return math.sqrt(number)
# my_package/string_operations/formatting.py
def capitalize_words(text):
return ' '.join(word.capitalize() for word in text.split())
def remove_spaces(text):
return text.replace(' ', '')
# 패키지 사용 예시
# 1. 전체 패키지 import
import my_package
result = my_package.add(3, 4)
# 2. 서브패키지 import
from my_package.math_operations import basic
result = basic.multiply(3, 4)
# 3. 특정 모듈 import
from my_package.math_operations.advanced import power
result = power(2, 3)
# 4. 여러 모듈에서 import
from my_package.math_operations import add, subtract
from my_package.string_operations.formatting import capitalize_words
print(add(10, 5))
print(capitalize_words("hello world"))
5.2.2 상대 import와 절대 import
# my_package/math_operations/basic.py 내에서
# 절대 import
from my_package.string_operations.formatting import capitalize_words
# 상대 import
from ..string_operations.formatting import capitalize_words # 상위 패키지에서
from .advanced import power # 같은 패키지에서
# my_package/math_operations/calculator.py
from .basic import add, subtract # 같은 디렉토리
from ..string_operations import formatting # 상위 디렉토리의 다른 패키지
class Calculator:
def __init__(self):
self.result = 0
def add(self, value):
self.result = add(self.result, value)
return self
def subtract(self, value):
self.result = subtract(self.result, value)
return self
def get_result(self):
return self.result
5.3 모듈과 패키지 고급 활용
5.3.1 동적 import
import importlib
# 문자열로 모듈 import
module_name = "math"
math_module = importlib.import_module(module_name)
print(math_module.sqrt(16)) # 4.0
# 조건부 import
try:
import numpy as np
HAS_NUMPY = True
except ImportError:
HAS_NUMPY = False
print("NumPy가 설치되어 있지 않습니다.")
if HAS_NUMPY:
# NumPy 기능 사용
arr = np.array([1, 2, 3])
else:
# 대안 구현
arr = [1, 2, 3]
5.3.2 all 사용
# module_with_all.py
__all__ = ['public_function', 'PublicClass']
def public_function():
"""외부에서 사용할 수 있는 함수"""
return "public"
def _private_function():
"""내부적으로만 사용하는 함수"""
return "private"
class PublicClass:
"""외부에서 사용할 수 있는 클래스"""
pass
class _PrivateClass:
"""내부적으로만 사용하는 클래스"""
pass
# 사용할 때
from module_with_all import *
# public_function과 PublicClass만 import됨
# _private_function과 _PrivateClass는 import되지 않음
5.3.3 패키지 초기화
# my_package/__init__.py
import logging
# 패키지 로거 설정
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
# 패키지 전역 설정
_config = {
'debug': False,
'version': '1.0.0'
}
def get_config(key):
"""패키지 설정 값 조회"""
return _config.get(key)
def set_config(key, value):
"""패키지 설정 값 변경"""
_config[key] = value
# 패키지 import 시 실행되는 초기화 코드
print(f"my_package v{_config['version']} loaded")
# 편의를 위한 별칭
from .math_operations.basic import add as sum_numbers
from .string_operations.formatting import capitalize_words as title_case