객체 지향 프로그래밍(OOP) 개요
파이썬은 함수형 프로그래밍과 객체 지향 프로그래밍(OOP)이라는 두 가지 주요 패러다임을 지원합니다. 함수형 프로그래밍으로도 모든 기능을 구현할 수 있지만, 특정 데이터를 중심으로 관련 동작을 묶어 관리해야 할 때는 객체 지향 접근 방식이 더 효율적일 수 있습니다. 예를 들어, 여러 작업을 수행할 때마다 사용자 정보(이름, 나이, 성별 등)를 반복적으로 전달해야 한다면, 이는 비효율적이며 코드의 가독성을 떨어뜨릴 수 있습니다.
다음은 여러 사용자 활동을 기록하는 간단한 예시입니다. 각 활동마다 사용자의 고유 정보가 반복해서 사용됩니다.
# 함수형 접근 방식 예시
def log_user_action(user_id, status, location, activity_desc):
print(f"[{user_id}] 상태: {status}, 위치: {location}, 활동: {activity_desc}")
log_user_action("user_A", "활성", "서울", "상품 세부 정보 확인")
log_user_action("user_A", "활성", "서울", "장바구니에 아이템 추가")
log_user_action("user_A", "활성", "서울", "추천 상품 보기")
log_user_action("user_B", "비활성", "부산", "로그아웃")
log_user_action("user_B", "비활성", "부산", "프로필 업데이트")
log_user_action("user_B", "비활성", "부산", "할인 정보 검색")
위 코드에서 user_id, status, location과 같은 정보는 각 사용자 활동마다 반복적으로 전달됩니다. 이는 데이터베이스 연결 정보나 사용자 인증 정보처럼 여러 작업에서 공통적으로 사용되는 데이터에 비유할 수 있습니다. 이러한 반복을 줄이고 관련 데이터를 더 효율적으로 관리하기 위해 클래스를 활용할 수 있습니다.
클래스를 사용하면 특정 데이터(속성)와 그 데이터를 조작하는 함수(메서드)를 하나의 "객체"로 묶을 수 있습니다. 다음은 위 예시를 클래스 형태로 변환한 초기 버전입니다.
# 클래스 기반 접근 방식 예시 (초기 버전)
class UserActivityLogger:
def log_activity(self, user_id, status, location, activity_desc):
print(f"[{user_id}] 상태: {status}, 위치: {location}, 활동: {activity_desc}")
logger_a = UserActivityLogger() # UserActivityLogger 클래스의 객체 생성
logger_a.log_activity("user_A", "활성", "서울", "상품 세부 정보 확인")
logger_a.log_activity("user_A", "활성", "서울", "장바구니에 아이템 추가")
logger_a.log_activity("user_A", "활성", "서울", "추천 상품 보기")
이 초기 클래스 버전은 여전히 user_id, status, location을 메서드 호출마다 전달하고 있지만, 데이터를 캡슐화하고 객체 지향 개념을 도입하기 위한 첫걸음입니다.
함수와 클래스(메서드)의 차이
파이썬에서 함수와 클래스 내 메서드는 모두 코드 블록을 정의하고 실행하는 수단이지만, 동작 방식에 차이가 있습니다.
- 함수 정의 및 호출: 일반 함수는
def 함수명(매개변수):형태로 정의하며,함수명(인자)처럼 직접 호출합니다. - 클래스 및 메서드 정의/호출:
class 클래스명:키워드를 사용하여 클래스를 정의합니다. 클래스 내부에 정의된 함수는 '메서드'라고 부릅니다.- 메서드는
def 메서드명(self, 매개변수):형태로 정의하며, 첫 번째 매개변수로 반드시self를 포함해야 합니다.self는 해당 메서드를 호출한 '인스턴스(객체)' 자신을 가리킵니다. - 클래스 메서드를 호출하려면 먼저
객체명 = 클래스명()형태로 클래스의 인스턴스(객체)를 생성해야 합니다. - 그 후, 생성된 객체를 통해
객체명.메서드명(인자)형태로 메서드를 호출합니다.
다음 예시를 통해 클래스와 메서드의 기본적인 구조 및 호출 방식을 이해할 수 있습니다.
class MyCalculator:
# 'self'는 이 메서드를 호출하는 객체 자신을 참조합니다.
def add_numbers(self, num1, num2):
print(f"숫자 {num1}과 {num2}를 더합니다...")
return num1 + num2
# MyCalculator 클래스의 인스턴스(객체)를 생성합니다.
calc_instance = MyCalculator()
# 생성된 객체를 통해 add_numbers 메서드를 호출합니다.
# 이때 'self' 매개변수에는 자동으로 calc_instance 객체 자체가 전달됩니다.
result = calc_instance.add_numbers(10, 25)
print(f"결과: {result}") # 출력: 결과: 35
self의 역할 이해하기
메서드의 첫 번째 매개변수인 self는 파이썬에서 매우 중요한 개념입니다. self는 해당 메서드를 호출하는 객체(인스턴스) 자체를 가리키는 참조입니다. 파이썬은 객체의 메서드를 호출할 때 자동으로 해당 객체를 self 매개변수에 전달합니다.
이를 통해 메서드는 자신을 호출한 특정 객체의 속성(데이터)에 접근하거나 변경할 수 있습니다. 예를 들어, str 클래스의 객체인 문자열 변수 my_string = "hello"가 있을 때, my_string.upper()를 호출하면 my_string 객체 자체에 정의된 upper 메서드가 실행되고, 이 메서드는 my_string의 값을 변경하지 않고 새로운 대문자 문자열을 반환합니다.
다음 코드를 통해 self가 실제로 호출하는 객체와 동일한 참조임을 확인할 수 있습니다.
class DataProcessor:
def display_info(self, value):
print(f"메서드 내 self 객체: {self}")
print(f"전달받은 값: {value}")
processor_A = DataProcessor()
print(f"생성된 processor_A 객체: {processor_A}")
# processor_A.display_info(100) 호출 시, processor_A 객체가 'self'로 전달됩니다.
processor_A.display_info(100)
processor_B = DataProcessor()
print(f"생성된 processor_B 객체: {processor_B}")
# processor_B.display_info(200) 호출 시, processor_B 객체가 'self'로 전달됩니다.
processor_B.display_info(200)
출력 결과를 보면, processor_A가 display_info를 호출했을 때의 self 값과 processor_A 자체의 메모리 주소가 동일함을 알 수 있습니다. processor_B의 경우에도 마찬가지입니다. 즉, self는 메서드를 실행하는 특정 인스턴스를 나타냅니다.
self를 이용한 인스턴스 속성 접근
self는 인스턴스 내부에 데이터를 저장하고 그 데이터에 접근하는 데 활용됩니다. 이는 객체가 자신만의 고유한 상태(데이터)를 가질 수 있게 합니다.
class UserProfile:
def show_profile_details(self, message):
print(f"사용자 객체: {self}")
# self를 통해 인스턴스에 저장된 속성(user_id, username, email)에 접근합니다.
print(f"ID: {self.user_id}, 이름: {self.username}, 이메일: {self.email}, 메시지: {message}")
# 첫 번째 사용자 인스턴스 생성 및 속성 할당
user1 = UserProfile()
user1.user_id = "u001"
user1.username = "Alice"
user1.email = "alice@example.com"
user1.show_profile_details("환영합니다!")
# 두 번째 사용자 인스턴스 생성 및 속성 할당
user2 = UserProfile()
user2.user_id = "u002"
user2.username = "Bob"
user2.email = "bob@example.com"
user2.show_profile_details("새로운 로그인 감지.")
각 UserProfile 객체(user1, user2)는 고유한 user_id, username, email 속성을 가집니다. show_profile_details 메서드는 self를 통해 현재 자신을 호출한 객체의 이 속성들을 가져와 출력할 수 있습니다. 이는 데이터를 객체 내부에 "캡슐화"하여 관리하는 기본 원리입니다.
이러한 캡슐화 개념을 활용하면, 특정 객체에 고정적으로 연결되는 속성들은 한 번만 설정하고, 메서드를 호출할 때는 변경되는 내용만 전달하도록 코드를 개선할 수 있습니다. 아래는 이전의 UserActivityLogger 예시를 개선한 버전입니다.
# 개선된 클래스 기반 접근 방식 (데이터 캡슐화)
class TaskScheduler:
def execute_task(self, description):
# self를 통해 인스턴스에 저장된 공통 속성(task_id, priority, owner)에 접근합니다.
print(f"[작업 ID: {self.task_id}] 우선순위: {self.priority}, 담당자: {self.owner}, 동작: {description}")
scheduler1 = TaskScheduler()
scheduler1.task_id = "TSK-001"
scheduler1.priority = "높음"
scheduler1.owner = "개발팀 A"
scheduler1.execute_task("데이터베이스 백업 실행")
scheduler1.execute_task("주간 보고서 생성")
scheduler1.execute_task("서버 재시작 확인")
이처럼, task_id, priority, owner와 같이 객체에 고정된 정보는 객체 생성 후 속성으로 할당하고, 메서드에서는 변화하는 description만 인자로 받도록 하여 코드의 중복을 줄이고 효율성을 높일 수 있습니다.
데이터베이스 작업 예시로 확장하면, 특정 테이블이나 연결에 대한 정보(product_id, category, price)는 객체에 캡슐화하고, 각 CRUD(생성, 읽기, 업데이트, 삭제) 메서드에서는 실제 작업 내용만 인자로 전달하여 유연하게 사용할 수 있습니다.
class ProductManager:
def add_item(self, details):
print(f"상품 ID: {self.product_id}, 카테고리: {self.category}, 가격: {self.price} - 항목 추가: {details}")
def remove_item(self, details):
print(f"상품 ID: {self.product_id}, 카테고리: {self.category}, 가격: {self.price} - 항목 제거: {details}")
def update_item(self, details):
print(f"상품 ID: {self.product_id}, 카테고리: {self.category}, 가격: {self.price} - 항목 업데이트: {details}")
def get_item_info(self, details):
print(f"상품 ID: {self.product_id}, 카테고리: {self.category}, 가격: {self.price} - 정보 조회: {details}")
product_db = ProductManager()
product_db.product_id = "PRD-XYZ"
product_db.category = "전자제품"
product_db.price = 599.99
product_db.add_item("새 노트북 모델 S")
product_db.update_item("가격 650.00으로 변경")
product_db.remove_item("재고 정리")
product_db.get_item_info("현재 재고 수준 표시")
생성자 메서드: __init__
__init__ 메서드는 파이썬 클래스의 특별한 메서드로, "생성자" 혹은 "초기화 메서드"라고 불립니다. 객체가 생성될 때 자동으로 호출되며, 주로 인스턴스의 초기 상태를 설정하는 데 사용됩니다.
객체 = 클래스명() 코드가 실행되면, 파이썬 내부적으로 두 가지 중요한 작업이 발생합니다:
- 새로운 객체(인스턴스)가 생성됩니다.
- 생성된 객체의
__init__메서드가 자동으로 호출됩니다. 이때 새로 생성된 객체가self매개변수로 전달됩니다.
다음 예시를 통해 __init__ 메서드가 언제, 어떻게 호출되는지 확인할 수 있습니다.
class GreetingBot:
# __init__ 메서드는 객체가 생성될 때 자동으로 실행됩니다.
def __init__(self):
print("GreetingBot 인스턴스가 생성되었습니다!")
bot1 = GreetingBot() # GreetingBot() 호출 시 __init__ 메서드가 자동으로 실행됩니다.
print(f"생성된 객체: {bot1}")
위 코드를 실행하면, bot1 = GreetingBot() 라인에서 먼저 "GreetingBot 인스턴스가 생성되었습니다!"라는 메시지가 출력되고, 그 후에 bot1 객체의 정보가 출력되는 것을 볼 수 있습니다. 이는 __init__ 메서드가 객체 생성과 동시에 자동으로 호출되었음을 의미합니다.
__init__ 메서드는 일반 메서드와 달리 직접 호출하는 것이 아니라, 클래스의 인스턴스를 생성할 때 파이썬 인터프리터에 의해 자동으로 호출됩니다. 이 점이 z.f()처럼 명시적으로 호출해야 하는 일반 메서드와 가장 큰 차이입니다.
__init__을 이용한 인스턴스 초기화
__init__ 메서드는 매개변수를 받을 수 있으며, 이를 통해 객체를 생성하는 시점에 필요한 초기 데이터를 전달받아 인스턴스 속성을 설정할 수 있습니다. 이는 객체의 "생성"과 "초기화"를 한 번에 처리하여 코드의 일관성과 간결성을 높입니다.
class Book:
def __init__(self, title, author):
print(f"새로운 책 '{title}'(이)가 생성 중입니다.")
self.title = title # 전달받은 title을 인스턴스 속성 self.title에 저장
self.author = author # 전달받은 author를 인스턴스 속성 self.author에 저장
self.is_available = True # 모든 책의 초기 재고 상태는 'True'로 설정
def get_info(self):
return f"제목: {self.title}, 저자: {self.author}, 재고 여부: {self.is_available}"
# 객체 생성 시 __init__에 필요한 인자(title, author)를 전달합니다.
book1 = Book("파이썬 프로그래밍 첫걸음", "김작가")
print(book1.get_info())
book2 = Book("데이터 과학의 기초", "이연구원")
print(book2.get_info())
이처럼 __init__ 메서드를 사용하면, 객체를 생성하는 순간에 필수적인 속성들을 초기화할 수 있습니다. 이는 여러 속성을 객체 생성 후 일일이 할당하는 것보다 훨씬 간결하고 오류 발생 가능성을 줄여줍니다.
예를 들어, 여러 개의 설정을 가진 객체를 만든다고 가정해 봅시다. __init__을 사용하지 않으면 객체를 만든 후 각 설정을 개별적으로 할당해야 합니다.
# __init__ 없이 속성 개별 할당
class OldConfig:
def display_settings(self):
print(f"설정 A: {self.setting_a}, 설정 B: {self.setting_b}")
old_conf = OldConfig()
old_conf.setting_a = "Value A"
old_conf.setting_b = 123
old_conf.display_settings()
반면, __init__을 활용하면 객체 생성 시점에 모든 설정을 한 줄로 전달하여 초기화할 수 있어 코드가 훨씬 깔끔해집니다.
# __init__을 사용하여 초기화
class AppConfig:
def __init__(self, key1, key2, key3, key4):
self.config_key1 = key1
self.config_key2 = key2
self.config_key3 = key3
self.config_key4 = key4
def display_all_settings(self):
print(f"Key1: {self.config_key1}, Key2: {self.config_key2}, "
f"Key3: {self.config_key3}, Key4: {self.config_key4}")
# 한 줄로 여러 속성 초기화
app_settings = AppConfig("DebugMode", True, 8080, ["admin", "user"])
app_settings.display_all_settings()
print(f"개별 접근: {app_settings.config_key2}") # 초기화된 속성에 직접 접근 가능
__init__과 기본값 설정
__init__ 메서드는 필수적인 속성뿐만 아니라, 모든 인스턴스가 공통적으로 가질 기본값을 설정하는 데도 유용합니다. 다음은 학생 객체를 생성할 때 모든 신입생의 학년을 1학년으로 기본 설정하는 예시입니다.
class Student:
def __init__(self, student_id, student_name):
self.student_id = student_id
self.student_name = student_name
self.grade_level = 1 # 모든 신입 학생의 기본 학년은 1학년으로 설정
def print_student_info(self):
print(f"학번: {self.student_id}, 이름: {self.student_name}, 학년: {self.grade_level}")
student_s1 = Student("2023001", "김민준")
student_s1.print_student_info() # 학년이 1로 출력됩니다.
student_s2 = Student("2023002", "박서연")
student_s2.print_student_info() # 학년이 1로 출력됩니다.
이처럼 __init__ 메서드를 사용하면 객체 생성과 동시에 인스턴스의 초기 상태를 유연하고 효율적으로 구성할 수 있습니다.