Mixins
Coddy Python 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 64개 중 38번째.
Mixins는 클래스에 추가 기능을 "mix in"하는 데 사용되는 특별한 종류의 다중 상속입니다. Mixins는 그 자체로 완전한 클래스가 아니면서 특정 메서드를 제공합니다.
다음은 간단한 mixin의 예입니다:
class JSONSerializableMixin:
def to_json(self):
import json
return json.dumps(self.__dict__)이 mixin이 수행하는 작업을 자세히 살펴보겠습니다:
self.__dict__- 이 특수attribute에는 객체의 모든attribute와 해당 값이 dictionary로 포함되어 있습니다json.dumps()- 이 function은 Python dictionary를 JSON 형식의 문자열로 변환합니다- 이
mixin은 자신을 상속하는 모든 class에 이 JSON 직렬화 functionality을 "혼합해 넣습니다"
이제 실제로 어떻게 작동하는지 살펴보겠습니다:
class User(JSONSerializableMixin):
def __init__(self, name, email):
self.name = name
self.email = email이 mixin은 이를 상속하는 모든 클래스에 JSON functionality을 추가합니다:
user = User("Alice", "alice@example.com")
print(user.to_json())출력:
{"name": "Alice", "email": "alice@example.com"}핵심 통찰: 이제 User 클래스는 to_json() 메서드를 직접 정의하지 않고도 갖게 되었습니다. mixin이 그 기능을 "혼합해 넣었습니다"!
서로 다른 기능을 위한 여러 믹스인을 만드세요:
class PrintableMixin:
def pretty_print(self):
for key, value in self.__dict__.items():
print(f"{key}: {value}")
class ComparableMixin:
def __eq__(self, other):
return self.__dict__ == other.__dict__각 mixin은 어떤 class가 mixin을 사용하는지에 관계없이 객체의 attributes와 함께 작업하기 위해 self.__dict__에 액세스합니다. 이것이 mixin의 강력한 점입니다. mixin은 어떤 class의 attributes와도 작동하는 재사용 가능한 functionality를 제공합니다.
하나의 클래스에서 여러 mixins를 결합합니다:
class Product(JSONSerializableMixin, PrintableMixin, ComparableMixin):
def __init__(self, name, price):
self.name = name
self.price = price
product1 = Product("Laptop", 999)
product2 = Product("Laptop", 999)모든 믹스인 기능을 사용하세요:
print(product1.to_json()) # JSONSerializableMixin에서
product1.pretty_print() # PrintableMixin에서
print(product1 == product2) # ComparableMixin에서출력:
{"name": "Laptop", "price": 999}
name: Laptop
price: 999
Truemixins의 주요 특징:
- 그 자체로 인스턴스화하도록 설계되지 않음
- 구체적이고 재사용 가능한 functionality 제공
- 일반적으로
__init__methods를 가지지 않음 - 이름이 "Mixin" 또는 "able"로 끝나는 경우가 많음
- 다중 inheritance와 결합할 수 있음
- 유연하게 사용할 수 있도록
self.__dict__또는 다른 일반적인 객체 기능과 함께 작동함
핵심 요점: Mixins는 복잡한 inheritance 트리를 만들지 않고도 서로 다른 class 계층 구조 간에 functionality를 공유할 방법을 제공합니다. Mixins를 사용하면 직렬화, 비교 또는 출력을 위한 특정 기능을 필요한 모든 class에 "mix in"할 수 있습니다. 이는 코드 재사용을 촉진하고 class가 기본 책임에 집중하도록 합니다.
챌린지
중급이 과제에서는 mixin과 inheritance를 사용하여 간단한 전자 상거래 시스템을 구현합니다.
다음 파일에 필요한 클래스를 구현하세요(각 파일의 TODO 주석을 따르세요).
printablemixin.py- formatted 출력 functionality를 갖춘 PrintableMixin을 생성하세요discountmixin.py- 가격 인하 계산을 위한 DiscountMixin을 구현하세요shippablemixin.py- 무게 및 배송 cost 기능을 위한 ShippableMixin을 작성하세요product.py- 적절한 mixin inheritance를 사용하는 기본 Product class를 개발하세요physicalproduct.py- Product를 확장하는 PhysicalProduct class를 생성하세요digitalproduct.py- 특별한 할인 behavior를 갖는 DigitalProduct class를 구현하세요
직접 해보기
from product import Product
from physicalproduct import PhysicalProduct
from digitalproduct import DigitalProduct
from printablemixin import PrintableMixin
from discountmixin import DiscountMixin
from shippablemixin import ShippableMixin
def test_basic_functionality():
# Test basic functionality of all classes
p = Product("Laptop", 1000)
assert p.print_details() == "Product: Laptop, Price: $1000", f"Print details failed: {p.print_details()}"
assert p.apply_discount(10) == 900, f"Discount calculation failed: {p.apply_discount(10)}"
physical = PhysicalProduct("Desk", 500)
physical.set_weight(30)
assert physical.calculate_shipping() == 15, f"Shipping calculation failed: {physical.calculate_shipping()}"
digital = DigitalProduct("Software", 200)
assert digital.apply_discount(10) == 180, f"Digital discount failed: {digital.apply_discount(10)}"
print("Basic functionality test passed!")
def test_edge_cases():
# 0과 음수 값 같은 엣지 케이스 테스트
p = Product("Free Item", 0)
assert p.apply_discount(10) == 0, f"Zero price discount failed: {p.apply_discount(10)}"
p_neg = Product("Negative Item", -100)
assert p_neg.apply_discount(10) == -90, f"Negative price discount failed: {p_neg.apply_discount(10)}"
physical = PhysicalProduct("Empty Box", 10)
physical.set_weight(0)
assert physical.calculate_shipping() == 0, f"Zero weight shipping failed: {physical.calculate_shipping()}"
physical_neg = PhysicalProduct("Anti-Gravity Item", 10)
physical_neg.set_weight(-5)
assert physical_neg.calculate_shipping() == -2.5, f"Negative weight shipping failed: {physical_neg.calculate_shipping()}"
print("Edge cases test passed!")
def test_large_values():
# 매우 큰 값으로 테스트
p = Product("Expensive Item", 1000000)
assert p.apply_discount(10) == 900000, f"Large value discount failed: {p.apply_discount(10)}"
physical = PhysicalProduct("Heavy Item", 500)
physical.set_weight(1000)
assert physical.calculate_shipping() == 500, f"Large weight shipping failed: {physical.calculate_shipping()}"
print("Large values test passed!")
def test_inheritance():
# 상속 관계 테스트
p = Product("Test", 100)
physical = PhysicalProduct("Test", 100)
digital = DigitalProduct("Test", 100)
assert isinstance(p, PrintableMixin), "Product should inherit from PrintableMixin"
assert isinstance(p, DiscountMixin), "Product should inherit from DiscountMixin"
assert isinstance(physical, Product), "PhysicalProduct should inherit from Product"
assert isinstance(physical, ShippableMixin), "PhysicalProduct should inherit from ShippableMixin"
assert isinstance(physical, PrintableMixin), "PhysicalProduct should inherit from PrintableMixin through Product"
assert isinstance(physical, DiscountMixin), "PhysicalProduct should inherit from DiscountMixin through Product"
assert isinstance(digital, Product), "DigitalProduct should inherit from Product"
assert isinstance(digital, PrintableMixin), "DigitalProduct should inherit from PrintableMixin through Product"
assert isinstance(digital, DiscountMixin), "DigitalProduct should inherit from DiscountMixin through Product"
print("Inheritance test passed!")
def test_method_overriding():
# Test method overriding behavior
p = Product("Regular Product", 100)
digital = DigitalProduct("Digital Product", 100)
# 같은 가격, 같은 할인율, 다른 결과
assert p.apply_discount(20) == 80, f"Regular discount calculation failed: {p.apply_discount(20)}"
assert digital.apply_discount(20) == 90, f"Digital fixed discount failed: {digital.apply_discount(20)}"
# 디지털 제품은 매개변수와 관계없이 항상 10% 할인을 적용해야 함
assert digital.apply_discount(0) == 90, "Digital product should apply 10% discount even with 0%"
assert digital.apply_discount(50) == 90, "Digital product should apply 10% discount even with 50%"
print("Method overriding test passed!")
def test_polymorphism():
# 다양한 제품 유형 목록으로 다형성 동작 테스트
products = [
Product("Regular", 100),
PhysicalProduct("Physical", 100),
DigitalProduct("Digital", 100)
]
# 물리 제품의 weight 설정
products[1].set_weight(10)
# apply_discount(20)에 대한 예상 결과
expected_discounts = [80, 80, 90]
for i, product in enumerate(products):
# 모두 print_details 메서드를 가져야 함
assert "Product:" in product.print_details(), f"Polymorphic print_details failed for {type(product)}"
# 모두 apply_discount 메서드를 가져야 하지만 구현은 다름
assert product.apply_discount(20) == expected_discounts[i], f"Polymorphic apply_discount failed for {type(product)}"
print("Polymorphism test passed!")
def test_attribute_access():
# Test attribute access patterns
p = Product("Test Product", 100)
assert p.name == "Test Product", "Name attribute not properly set in Product"
assert p.price == 100, "Price attribute not properly set in Product"
physical = PhysicalProduct("Physical Product", 200)
assert physical.name == "Physical Product", "Name attribute not properly set in PhysicalProduct"
assert physical.price == 200, "Price attribute not properly set in PhysicalProduct"
# set_weight가 호출되기 전에 weight 속성이 존재하지 않아야 함
try:
weight = physical.weight
assert False, "Weight attribute should not exist before set_weight is called"
except AttributeError:
pass
physical.set_weight(15)
assert physical.weight == 15, "Weight attribute not properly set in PhysicalProduct"
digital = DigitalProduct("Digital Product", 300)
assert digital.name == "Digital Product", "Name attribute not properly set in DigitalProduct"
assert digital.price == 300, "Price attribute not properly set in DigitalProduct"
print("Attribute access test passed!")
# 메인 테스트 실행기
test_case = input()
if test_case == "basic_test":
test_basic_functionality()
elif test_case == "edge_cases":
test_edge_cases()
elif test_case == "large_values":
test_large_values()
elif test_case == "inheritance":
test_inheritance()
elif test_case == "method_overriding":
test_method_overriding()
elif test_case == "polymorphism":
test_polymorphism()
elif test_case == "attribute_access":
test_attribute_access()이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
직접 연습해 보세요: 온라인 Python 컴파일러