ミックスイン
CoddyのPythonジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 38/64。
Mixinは、クラスに追加の機能を「mix in」するために使用される特殊な多重継承です。Mixin自体は完全なクラスではなく、特定のメソッドを提供します。
以下はシンプルな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形式のstringに変換します- このmixinは、それを継承するすべてのclassに、このJSONシリアライズfunctionalityを「mixes in」します
それでは、実際に動かしてみましょう:
class User(JSONSerializableMixin):
def __init__(self, name, email):
self.name = name
self.email = emailこのミックスインは、それを継承する任意のクラスに JSON 機能を追加します:
user = User("Alice", "alice@example.com")
print(user.to_json())出力:
{"name": "Alice", "email": "alice@example.com"}重要なポイント:User class には、直接定義していないにもかかわらず、to_json() method が備わっています。mixin がその functionality を「mixed in」したのです!
異なる機能のために複数のミックスインを作成します。
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 は self.__dict__ にアクセスして、mixin を使用するクラスに関係なく、オブジェクトの attributes を操作します。これが mixin の力です。mixin は、どのクラスの attributes とも連携する再利用可能な functionality を提供します。
1つのクラスで複数のmixinを組み合わせます:
class Product(JSONSerializableMixin, PrintableMixin, ComparableMixin):
def __init__(self, name, price):
self.name = name
self.price = price
product1 = Product("Laptop", 999)
product2 = Product("Laptop", 999)すべてのmixinの機能を使用します:
print(product1.to_json()) # JSONSerializableMixin から
product1.pretty_print() # PrintableMixin から
print(product1 == product2) # ComparableMixin から出力:
{"name": "Laptop", "price": 999}
name: Laptop
price: 999
Truemixin の主な特徴:
- 単独でインスタンス化することを意図していない
- 特定の再利用可能な機能を提供する
- 通常、
__init__メソッドを持たない - 名前は「Mixin」または「able」で終わることが多い
- 多重継承と組み合わせられる
- 柔軟に扱えるよう、
self.__dict__やその他の一般的なオブジェクト機能と連携する
重要なポイント: Mixins は、複雑な inheritance ツリーを作成せずに、different な class の階層間で functionality を共有する方法を提供します。これにより、シリアライズ、比較、出力などの特定の機能を、それらを必要とする任意の class に「mix in」できます。これによってコードの再利用が促進され、class は主要な責務に集中できます。
チャレンジ
中級このチャレンジでは、mixinと継承を使用してシンプルなeコマースシステムを実装します。
以下のファイルに必要なクラスを実装してください(各ファイルのTODOコメントに従ってください)。
printablemixin.py- formatted output functionalityを備えたPrintableMixinを作成しますdiscountmixin.py- 価格引き下げの計算を行うDiscountMixinを実装しますshippablemixin.py- 重量とshipping costの機能を持つShippableMixinを構築しますproduct.py- 適切なmixin継承を備えた基底Productクラスを開発しますphysicalproduct.py- Productを拡張するPhysicalProductクラスを作成しますdigitalproduct.py- 特別な割引動作を持つDigitalProductクラスを実装します
自分で試してみよう
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():
# ゼロや負の値などのエッジケースをテストする
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)}"
# Digital product はパラメータに関係なく常に 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オンラインコンパイラ