Mixin
本课属于 Coddy Python 学习旅程的“面向对象编程”单元。第 38 课,共 64 课。
混入类是一种特殊的多重继承,用于向类中“混入”额外的功能。它们提供特定的方法,但本身并不是完整的类。
下面是一个简单混入类的示例:
class JSONSerializableMixin:
def to_json(self):
import json
return json.dumps(self.__dict__)我们来分解一下这个混入类的作用:
self.__dict__,这个特殊属性包含对象的所有属性及其值组成的字典json.dumps(),这个函数会将 Python 字典转换为 JSON 格式的字符串- 这个混入类会将 JSON 序列化功能混入所有继承它的类中
现在来看看它的实际效果:
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 类拥有 to_json() 方法,但没有直接定义它。这个混入类“混入”了这项功能!
为不同的功能创建多个混入类:
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__每个混入都会访问 self.__dict__ 来处理对象的属性,无论哪个类使用这个混入。这就是混入的强大之处:它们提供可复用的功能,适用于任何类的属性。
在一个类中组合多个 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)使用所有混入功能:
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__或其他常见的对象特性来保持灵活
重点:Mixin 提供了一种在不同类层次结构之间共享功能的方式,无需创建复杂的继承树。你可以将序列化、比较或打印等特定功能“混入”任何需要它们的类中。这样可以促进代码复用,并让类专注于自己的主要职责。
挑战
中等在这个挑战中,你将使用混入和继承实现一个简单的电子商务系统。
在以下文件中实现所需的类(遵循每个文件中的 TODO 注释):
printablemixin.py:创建 PrintableMixin,实现格式化输出功能discountmixin.py:实现 DiscountMixin,用于计算价格折扣shippablemixin.py:构建 ShippableMixin,实现重量和运费功能product.py:开发基类 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():
# 测试所有类的基本功能
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():
# 测试方法重写的行为
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)
]
# 设置实体产品的重量
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():
# 测试属性访问方式
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 前,重量属性不应存在
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 在线编译器