jju71 님의 블로그
[Python] 객체지향 프로그래밍 4대 패러다임 본문
객체지향 프로그래밍(OOP) 4대 패러다임은 캡슐화, 상속, 다형성, 추상화입니다. 캡슐화는 데이터(속성)와 기능(메서드)을 하나로 묶고 외부에서 직접 접근을 제한하여 객체를 보호하는 개념이며, 상속은 기존 클래스의 속성과 메서드를 물려받아 재사용하고 확장하는 구조를 의미합니다. 다형성은 같은 이름의 메서드라도 객체의 종류에 따라 다르게 동작할 수 있게 하는 특징으로, 코드의 유연성을 높여줍니다. 추상화는 복잡한 내부 구현은 숨기고 핵심적인 기능만 외부에 제공하는 것으로, 사용자는 세부 동작을 몰라도 객체를 쉽게 사용할 수 있게 해줍니다. 이 네가지 개념을 통해 코드의 재사용성, 확장성, 유지보수성을 크게 향상시킬 수 있습니다.
1. 캡슐화
캡슐화(encapsulation)는 객체지향 프로그래밍에서 데이터(속성)와 이를 처리하는 메서드를 하나로 묶고, 외부에서 내부 구현에 직접 접근하지 못하도록 제한하여 객체를 보호하는 개념입니다. 파이썬에서는 public, _protected, __private와 같은 네이밍 규칠을 통해 접근 수준을 구분하며, 필요할 경우 getter/setter 메서드나 @property를 사용해 안전하게 값을 읽고 수정할 수 있습니다. 이를 통해 객체의 상태를 임의로 변경하는 것을 방지하고, 코드의 안정성과 유지보수성을 높일 수 있습니다.
class Fruit:
def __init__(self, name, price):
self.name = name # public
self.__price = price # private (캡슐화)
# fruit.__price 로 접근불가, 같은 클래스 내에서만 접근가능, 외부에서 볼 수 없다
# getter
def get_price(self):
return self.__price
def set_price(self, price):
if price > 0:
self.__price = price
else:
print("가격은 0보다 커야 합니다.")
def print_info(self):
print(f"과일: {self.name}")
print(f"가격: {self.__price}")
class Apple(Fruit): # 상속
def __init__(self, name, price, origin):
super().__init__(name, price)
self.origin = origin
def print_origin(self):
print(f"{self.name}의 원산지: {self.origin}")
apple = Apple("사과", 3000, "한국")
apple.print_info()
apple.print_origin()
print("가격 조회:", apple.get_price())
print("----- 가격 수정 -----")
apple.set_price(3500)
print("수정된 가격:", apple.get_price())
print("----- 잘못된 값 입력 -----")
apple.set_price(-1000) # 검증 실패
print("----- 외부 접근 시도 -----")
apple.__price = 10000 # 새로운 변수 생성 (실제 private 변경 아님)
print("getter로 확인:", apple.get_price()) # 여전히 3500
print("직접 접근:", apple.__price) # 10000 (다른 변수)
--------------------------------------------------------------
과일: 사과
가격: 3000
사과의 원산지: 한국
가격 조회: 3000
----- 가격 수정 -----
수정된 가격: 3500
----- 잘못된 값 입력 -----
가격은 0보다 커야 합니다.
----- 외부 접근 시도 -----
getter로 확인: 3500
직접 접근: 10000
self__price는 실제로 private가 되는 것이 아니라 내부적으로 _Fruit_price으로 바뀝니다.
class Fruit:
def __init__(self, name, price):
self.name = name
self.__price = price # private
# getter → 속성처럼 접근 가능
@property
def price(self):
return self.__price
# setter → 값 할당 시 자동 호출
@price.setter
def price(self, value):
if value > 0:
self.__price = value
else:
print("가격은 0보다 커야 합니다.")
def print_info(self):
print(f"과일: {self.name}")
print(f"가격: {self.price}") # 함수가 아니라 속성처럼 사용
class Apple(Fruit):
def __init__(self, name, price, origin):
super().__init__(name, price)
self.origin = origin
def print_origin(self):
print(f"{self.name}의 원산지: {self.origin}")
2. 상속
상속(inheritance)은 객체지향 프로그래밍에서 기존 클래스(부모 클래스)의 속성과 메서드를 새로운 클래스(자식 클래스)가 물려받아 재사용하고 확장하는 개념입니다. 이를 통해 공통 기능을 중복 작성하지 않고 코드 재사용성을 높일 수 있으며, 자식 클래스는 부모의 기능을 그대로 사용하거나 필요한 부분만 추가하거나 오버라이딩(재정의)하여 동작을 변경할 수 있습니다. 파이썬에서는 class 자식클래스(부모클래스) 형태로 상속을 구현하며, super()를 사용해 부모 클래스의 초기화나 메서드를 호출할 수 있습니다. 이러한 구조는 코드의 유지보수성과 확장성을 크게 향상시키는 핵심 개념입니다.
class Parent:
pass
class Chile(Parent):
pass
1. 상속구조
class Fruit:
def __init__(self, name, quantity): # 생성자. 객체 만들면 자동으로 호출
self.name = name
self.quantity = quantity
def store(self, place):
print(f'{self.name}를 {place}에 보관합니다')
def sell(self, amount):
print(f'{self.name}를 {amount}개 판매합니다')
fruit = Fruit('과일', 100)
fruit.store('창고')
fruit.sell(10)
class Apple(Fruit): # Fruit를 상속받음
pass # 생성자가 없으니까 부모의 생성자를 따라가야함.
2. 생성자 호출
자식 클래스에 __init__이 없을 때 자동 호출
class Fruit:
def __init__(self, name):
print("Fruit 생성자 호출")
self.name = name
class Apple(Fruit):
pass
apple = Apple("사과")
------------------------------------
Fruit 생성자 호출
자식 클래스에 __init__을 직접 정의하면 부모 생성자 호출 안됨
class Fruit:
def __init__(self, name):
print("Fruit 생성자 호출")
self.name = name
class Apple(Fruit): # 물려받았지만 덮어쓰기, 오버라이딩
def __init__(self):
print("Apple 생성자 호출")
# self.name = name
apple = Apple()
print(apple.name) # AttributeError: 'Apple' object has no attribute 'name'
# self.name 이 부모쪽에 있는데 부모를 호출하지 않았으니까 print하며 에러발생
----------------------------------------------------------------
Apple 생성자 호출
super() 함수는 현재 클래스의 부모 클래스를 참조하며, 부모 클래스의 생성자를 호출할 수 있습니다.
class Fruit:
def __init__(self, name):
print("Fruit 생성자 호출")
self.name = name
class Apple(Fruit):
def __init__(self, name):
super().__init__(name) # 부모 생성자 호출
print("Apple 생성자 호출")
apple = Apple("사과")
print(apple.name)
--------------------------------------------------
Fruit 생성자 호출
Apple 생성자 호출
사과
3. 오버라이딩
오버라이딩(overriding)은 상속 관계에서 부모 클래스에 이미 정의된 메서드를 자식 클래스에서 동일한 이름으로 다시 정의하며, 동작을 변경하는 것을 의미합니다. 이를 통해 자식 클래스는 부모의 기본 기능을 그대로 사용할 수도 있고, 필요에 따라 일부 또는 전체 동작을 자신에게 맞게 수정할 수 있습니다. 파이썬에서는 별도의 키워드 없이 같은 메서드 이름을 정의하면 자동으로 오버라이딩이 이루어지며, 필요한 경우 super()를 사용해 부모의 메서드를 함께 호출할 수도 있습니다.
class Fruit:
def __init__(self, name, quantity):
self.name = name
self.quantity = quantity
def eat(self): # 메서드
print(f'{self.name}를 먹습니다')
def store(self, place):
print(f'{self.name}를 {place}에 보관합니다')
class Apple(Fruit): # 상속
def wash(self): # 추가로 확장
print(f'{self.name}를 깨끗이 씻습니다')
# 오버라이딩
def eat(self): # 부모를 덮어씀
print(f'{self.name}를 아주 맛있게 먹습니다')
# 부모 메서드 호출
def superEat(self): # 일반함수에 super를 써서 부모를 호출
super().eat()
apple = Apple('사과', 10)
apple.eat() # 오버라이딩된 메서드
apple.store('냉장고') # 부모 메서드
apple.wash() # 자식 메서드
-----------------------------------------------------------------------------
사과를 아주 맛있게 먹습니다
사과를 냉장고에 보관합니다
사과를 깨끗이 씻습니다
print("----- 부모 메서드 호출 -----")
apple.superEat() # 부모 eat 호출
fruit = Fruit('과일', 20) # 부모
fruit.eat()
fruit.store('창고')
# fruit.wash() (Fruit에는 없음)
---------------------------------------
----- 부모 메서드 호출 -----
사과를 먹습니다
과일를 먹습니다
과일를 창고에 보관합니다
4. 다중 상속
다중 상속은 클래스가 둘 이상의 부모 클래스로부터 상속을 받는 기능을 의미합니다. 파이썬은 다른 많은 객체 지향 언어와 달리 다중 상속을 지원합니다. 다중 상속을 사용하면 코드의 재사용성을 향상시킬 수 있지만, 동시에 복잡성이 높아지기 때문에 주의해야 합니다.
class Parent1:
pass
class Parent2:
pass
class Child(Parent1, Parent2):
pass
class Fruit:
def __init__(self, name, quantity): # 생성자
self.name = name
self.quantity = quantity
def eat(self):
print(f'{self.name}를 먹습니다')
def sleep(self, hour):
print(f'{self.name}를 {hour}시간 동안 보관합니다')
# def superSleep(self, hour):
# super().sleep()
class Storage:
def __init__(self, name, quantity): # 생성자
self.name = name
self.quantity = quantity
def process(self, hour):
print(f'{self.name}를 {hour}시간 동안 가공합니다')
def sleep(self, hour):
print(f'{self.name}를 {hour}시간 동안 저온 숙성합니다')
# 다중 상속
class ProcessedFruit(Fruit, Storage):
pass
# sleep 메서드 충돌
fruit = ProcessedFruit('사과', 10)
fruit.eat() # Fruit
fruit.process(2) # Storage
fruit.sleep(8) # MRO에 따라 Fruit의 sleep 실행, ProcessedFruit의 바로 위 부모를 참음(Fruit)
print(ProcessedFruit.mro()) # mro 메서드(다이아몬드)
# fruit.superSleep(5)
---------------------------------------------------------------------------------------
사과를 먹습니다
사과를 2시간 동안 가공합니다
사과를 8시간 동안 보관합니다
[<class '__main__.ProcessedFruit'>, <class '__main__.Fruit'>,
<class '__main__.Storage'>, <class 'object'>]
Object 클래스
파이썬의 object클래스는 모든 클래스의 최상위 부모 클래스(루트 클래스)로, 사용자가 클래스를 정의할 때 명시적으로 상속하지 않아도 자동으로 상속되는 기본 클래스입니다. 이 클래스는 객체가 가져야 할 최소한의 공통기능을 제공하며, __str__, __repr__, __eq__ 같은 기본 매직 메서드의 기본 구현을 포함하고 있습니다. 따라서 모든 파이썬 객체는 내부적으로 object를 기반으로 동작하며, 새로운 클래스를 정의할 때도 이 기본 기능들을 물려받아 사용할 수 있습니다. 즉, object 클래스는 파이썬 객체 시스템의 가장 기초가 되는 공통 부모입니다.
MRO
MRO(Method Resolution Order)는 파이썬에서 다중 상속을 사용할 때, 메서드나 속성을 찾는 순서를 정의하는 규칙입니다. MRO는 특히 여러 부모 클래스를 상속받는 경우에 어떤 부모 클래스에서 메서드를 먼저 찾을지를 결정하며, 이를 통해 클래스 간의 메서드 충돌을 해결할 수 있습니다.
class Base:
def hello(self):
print("Base")
class Clean(Base):
def hello(self):
print("Clean")
super().hello()
class Pack(Base):
def hello(self):
print("Pack")
super().hello()
class Product(Clean, Pack): # 상속
def hello(self):
print("Product")
super().hello() # 부모 중에 hello를 쫓아감. clean과 pack 둘다 hello가 있어서 충돌
p = Product()
p.hello()
print(Product.mro())
---------------------------------
Product
Clean
Pack
Base
[<class '__main__.Product'>, <class '__main__.Clean'>, <class '__main__.Pack'>,
<class '__main__.Base'>, <class 'object'>]
3. 다형성
다형성(polymorphism)은 같은 이름의 메서드나 함수를 호출하더라도, 객체의 종류에 따라 서로 다른 방식으로 동작하는 특성을 의미합니다. 파이썬은 정적 타입 언어처럼 명시적인 인터페이스 구현을 요구하지 않고, 동일한 메서드를 가지고 있으면 타입과 관계없이 사용할 수 있는 덕 타이핑(duck typing)을 기반으로 다형성을 자연스럽게 지원합니다. 예를 들어 여러 객체가 pay()나 speak() 같은 동일한 메서드를 가지고 있으면, 하나의 함수에서 객체의 타입을 구분하지 않고도 공통된 방식으로 처리할 수 있습니다. 이러한 특징은 코드의 유연성과 확장성을 높여주며, 새로운 기능을 추가할 때 기존 코드를 수정하지 않아도 되는 장점을 제공합니다.
덕 타이핑(duck typing): 객체의 실제 클래스가 무엇인지 중요하지 않고, 필요한 메서드나 기능을 가지고 있으면 같은 방식으로 사용하는 것
class CardPayment:
def pay(self, amount):
print(f"카드로 {amount}원 결제합니다")
class Cashpayment:
def pay(self, amount):
print(f"현금으로 {amount}원 결제합니다")
class Kakaopay:
def pay(self, amount):
print(f"카카오페이로 {amount}원 결제합니다")
def process_payment(payment, amount):
payment.pay(amount)
card = CardPayment()
cash = CashPayment()
kakao = KakaoPay()
process_payment(card, 10000)
process_payment(cash, 5000)
process_payment(kakao, 2000)
-------------------------------------------------
카드로 10000원 결제합니다
현금으로 5000원 결제합니다
카카오페이로 2000원 결제합니다.
4. 추상화
추상화(abstraction)는 복잡한 내부 구현을 숨기고, 외부에서는 필요한 핵심 기능만 간단하게 사용할 수 있도록 만드는 개념입니다. 사용자는 객체가 내부적으로 어떻게 동작하는지 몰라도, 제공된 메서드만으로 기능을 사용할 수 있으며, 이를 통해 코드의 복잡도를 줄이고 이해와 사용을 쉽게합니다. 파이썬에서는 일반 클래스 설계뿐 아니라 abc 모듈의 ABC와 @abstractmethod를 사용해 추상 클래스를 정의하고, 반드시 구현해야 하는 메서드를 강제함으로써 일관된 구조를 유지할 수 있습니다. 즉, 추상화는 "어떻게 동작하는지"보다 "무엇을 할 수 있는지"에 집중하게 만드는 설계 방식입니다.
from abc import ABC, abstractmethod #abc = 모듈이름, ABC = 클래스, abstractmethod = 함수
# abstractmethod를 쓰기 위해 abc를 불러옴
# 추상 클래스
class Payment(ABC): # 상속받아 오버라이딩하면 동작하게 해줄게
@abstractmethod # 메소드를 구현하려고 강제하는 데코레이터, 강제로 오버라이딩(하지 않으면 에러)
def pay(self, amount):
pass # 반드시 구현해야하는 메서드
# 카드 결제
class CardPayment(Payment):
def pay(self, amount):
print(f"카드로 {amount}원 결제합니다")
# 현금 결제
class CashPayment(Payment):
def pay(self, amount):
print(f"현금으로 {amount}원 결제합니다")
# 카카오페이
class KakaoPay(Payment):
def pay(self, amount):
print(f"카카오페이로 {amount}원 결제합니다")
# 공통 처리 함수
def process_payment(payment: Payment, amount):
payment.pay(amount)
# 사용
card = CardPayment()
cash = CashPayment()
kakao = KakaoPay()
process_payment(card, 10000)
process_payment(cash, 5000)
process_payment(kakao, 2000)'Python' 카테고리의 다른 글
| [Python] 매직 메서드 (0) | 2026.05.29 |
|---|---|
| [Python] 예외처리 (0) | 2026.05.29 |
| [Python] 객체지향 프로그래밍(OOP) (0) | 2026.05.28 |
| [Python] 사용자 정의 함수 (1) | 2026.05.28 |
| [Python] 컴퓨터에서 음수 표현, 2의 보수 (0) | 2026.05.28 |
