Bootstrap

Python入门(10)--面向对象进阶

Python面向对象进阶 🚀

1. 继承与多态 🔄

1.1 继承基础

class Animal:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def speak(self):
        pass
    
    def describe(self):
        return f"{self.name} is {self.age} years old"

class Dog(Animal):
    def __init__(self, name, age, breed):
        super().__init__(name, age)  # 调用父类的初始化方法
        self.breed = breed
    
    def speak(self):
        return f"{self.name} says Woof!"
    
    def describe(self):
        # 扩展父类的方法
        return f"{super().describe()} and is a {self.breed}"

class Cat(Animal):
    def __init__(self, name, age, indoor=True):
        super().__init__(name, age)
        self.indoor = indoor
    
    def speak(self):
        return f"{self.name} says Meow!"
    
    def describe(self):
        habitat = "indoor" if self.indoor else "outdoor"
        return f"{super().describe()} and is an {habitat} cat"

# 使用示例
dog = Dog("Buddy", 3, "Golden Retriever")
cat = Cat("Whiskers", 2, True)

print(dog.describe())  # 输出: Buddy is 3 years old and is a Golden Retriever
print(cat.describe())  # 输出: Whiskers is 2 years old and is an indoor cat

继承的关键概念:

  1. super()的使用:调用父类方法
  2. 方法重写与扩展
  3. 构造函数的继承与扩展
  4. 属性的继承与新增

1.2 多态

让我们通过一个更实际的例子来展示多态的威力:

class Document:
    def __init__(self, content):
        self.content = content
    
    def format(self):
        pass

class PlainText(Document):
    def format(self):
        return self.content

class HTMLDocument(Document):
    def format(self):
        return f"<html><body>{self.content}</body></html>"

class MarkdownDocument(Document):
    def format(self):
        return f"# {self.content}"

def process_documents(documents):
    """多态函数 - 统一处理不同类型的文档"""
    for doc in documents:
        print(f"处理 {doc.__class__.__name__}:")
        print(doc.format())
        print("---")

# 实际应用
documents = [
    PlainText("Hello World"),
    HTMLDocument("Welcome"),
    MarkdownDocument("Title")
]

process_documents(documents)

1.3 多重继承

让我们通过一个更实用的场景来展示多重继承:

class Persistable:
    """提供持久化功能的混入类"""
    def save(self, filename):
        with open(filename, 'w') as f:
            f.write(str(self.__dict__))
    
    def load(self, filename):
        with open(filename, 'r') as f:
            data = eval(f.read())
            self.__dict__.update(data)

class Loggable:
    """提供日志功能的混入类"""
    def log(self, message):
        print(f"[{self.__class__.__name__}] {message}")

class Configuration(Persistable, Loggable):
    """应用配置类"""
    def __init__(self, host="localhost", port=8000):
        self.host = host
        self.port = port
    
    def update_config(self, **kwargs):
        self.log("Updating configuration...")
        self.__dict__.update(kwargs)
        self.log("Configuration updated")

# 使用示例
config = Configuration()
config.update_config(host="127.0.0.1", port=5000)
config.save("config.txt")  # 保存配置
config.load("config.txt")  # 加载配置

2. 封装与访问控制 🔒

让我们通过一个更完整的例子来展示Python的封装机制:

class Employee:
    def __init__(self, name, salary):
        self._name = name            # 受保护的属性
        self.__salary = salary       # 私有属性
        self.__bonus = 0             # 私有属性
    
    @property
    def name(self):
        """只读属性"""
        return self._name
    
    @property
    def total_salary(self):
        """计算总薪资(基本工资 + 奖金)"""
        return self.__salary + self.__bonus
    
    @property
    def bonus(self):
        """奖金查询"""
        return self.__bonus
    
    @bonus.setter
    def bonus(self, value):
        """设置奖金,带验证"""
        if not isinstance(value, (int, float)):
            raise TypeError("奖金必须是数字")
        if value < 0:
            raise ValueError("奖金不能为负数")
        self.__bonus = value
    
    def _calculate_tax(self):
        """受保护的方法 - 计算税收"""
        return self.total_salary * 0.2
    
    def get_salary_info(self):
        """公开方法 - 获取薪资信息"""
        return {
            'name': self._name,
            'total_salary': self.total_salary,
            'tax': self._calculate_tax(),
            'net_salary': self.total_salary - self._calculate_tax()
        }

# 使用示例
emp = Employee("John Doe", 5000)

# 属性访问
print(emp.name)         # 使用@property
emp.bonus = 1000        # 使用@bonus.setter
print(emp.total_salary) # 使用@property计算总薪资

# 获取完整信息
salary_info = emp.get_salary_info()
for key, value in salary_info.items():
    print(f"{key}: {value}")

# 以下操作会引发错误
# emp.name = "Jane"     # AttributeError: 不能修改只读属性
# emp.__salary = 6000   # AttributeError: 私有属性不能直接访问
# emp.bonus = -100      # ValueError: 奖金不能为负数

补充说明:

  1. 使用@property创建只读属性
  2. 使用@property@x.setter创建可读写属性
  3. 使用单下划线表示受保护成员
  4. 使用双下划线表示私有成员
  5. 通过公开方法提供对私有数据的安全访问

3. 类方法与静态方法 🛠️

3.1 类方法(@classmethod)

让我们通过一个更实用的工厂模式示例来展示类方法的应用:

from datetime import datetime, date

class Order:
    """订单类"""
    order_count = 0  # 类变量,用于追踪订单数量
    
    def __init__(self, customer_id: str, items: list, order_date: date):
        Order.order_count += 1
        self.order_id = f"ORD{Order.order_count:04d}"
        self.customer_id = customer_id
        self.items = items
        self.order_date = order_date
        self.status = "pending"
    
    @classmethod
    def create_from_dict(cls, order_data: dict):
        """从字典创建订单对象"""
        try:
            customer_id = order_data['customer_id']
            items = order_data['items']
            # 解析日期字符串
            date_str = order_data.get('date', datetime.now().strftime('%Y-%m-%d'))
            order_date = datetime.strptime(date_str, '%Y-%m-%d').date()
            return cls(customer_id, items, order_date)
        except KeyError as e:
            raise ValueError(f"缺少必要的订单信息: {e}")
    
    @classmethod
    def create_rush_order(cls, customer_id: str, items: list):
        """创建加急订单"""
        order = cls(customer_id, items, date.today())
        order.status = "rush"
        return order
    
    @classmethod
    def get_order_count(cls) -> int:
        """获取订单总数"""
        return cls.order_count
    
    def __str__(self):
        return f"Order {self.order_id}: {len(self.items)} items for {self.customer_id}"

# 使用示例
# 1. 常规创建
order1 = Order("CUST001", ["item1", "item2"], date.today())

# 2. 从字典创建
order_data = {
    "customer_id": "CUST002",
    "items": ["item3", "item4"],
    "date": "2024-03-15"
}
order2 = Order.create_from_dict(order_data)

# 3. 创建加急订单
rush_order = Order.create_rush_order("CUST003", ["urgent_item"])

print(f"Total orders: {Order.get_order_count()}")  # 输出订单总数

3.2 静态方法(@staticmethod)

通过一个更复杂的示例来展示静态方法的实际应用:

class DataValidator:
    """数据验证工具类"""
    
    @staticmethod
    def validate_email(email: str) -> bool:
        """验证邮箱格式"""
        import re
        pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
        return bool(re.match(pattern, email))
    
    @staticmethod
    def validate_phone(phone: str) -> bool:
        """验证电话号码格式"""
        import re
        # 支持多种格式:+86-123-4567-8901, 12345678901, 123-4567-8901
        patterns = [
            r'^\+\d{1,3}-\d{3}-\d{4}-\d{4}$',
            r'^\d{11}$',
            r'^\d{3}-\d{4}-\d{4}$'
        ]
        return any(bool(re.match(pattern, phone)) for pattern in patterns)
    
    @staticmethod
    def validate_date(date_str: str) -> bool:
        """验证日期格式和有效性"""
        try:
            datetime.strptime(date_str, '%Y-%m-%d')
            return True
        except ValueError:
            return False
    
    @staticmethod
    def sanitize_string(text: str) -> str:
        """清理并验证字符串"""
        # 移除首尾空白,替换多个空格为单个空格
        text = ' '.join(text.split())
        # 移除特殊字符
        text = ''.join(char for char in text if char.isprintable())
        return text

class UserProfile:
    """用户档案类 - 展示数据验证的应用"""
    def __init__(self, email: str, phone: str, birth_date: str):
        if not DataValidator.validate_email(email):
            raise ValueError("无效的邮箱地址")
        if not DataValidator.validate_phone(phone):
            raise ValueError("无效的电话号码")
        if not DataValidator.validate_date(birth_date):
            raise ValueError("无效的出生日期")
        
        self.email = email
        self.phone = phone
        self.birth_date = birth_date

# 使用示例
try:
    # 创建用户档案
    user = UserProfile(
        email="[email protected]",
        phone="123-4567-8901",
        birth_date="1990-01-01"
    )
    print("用户档案创建成功")
    
    # 验证和清理数据
    text = "   Hello   World!   \n\t@#$%   "
    cleaned = DataValidator.sanitize_string(text)
    print(f"清理后的文本: '{cleaned}'")
    
except ValueError as e:
    print(f"错误: {e}")

4. 抽象类与接口 🎯

让我们通过一个文件处理系统的例子来展示抽象类和接口的实际应用:

from abc import ABC, abstractmethod
from typing import Any, List, Dict
import json
import csv
import xml.etree.ElementTree as ET

class DataProcessor(ABC):
    """数据处理器抽象基类"""
    
    @abstractmethod
    def read(self, source: str) -> Any:
        """读取数据"""
        pass
    
    @abstractmethod
    def write(self, data: Any, destination: str) -> bool:
        """写入数据"""
        pass
    
    @abstractmethod
    def validate(self, data: Any) -> bool:
        """验证数据"""
        pass
    
    @property
    @abstractmethod
    def supported_extensions(self) -> List[str]:
        """支持的文件扩展名"""
        pass

class JSONProcessor(DataProcessor):
    """JSON数据处理器"""
    
    @property
    def supported_extensions(self) -> List[str]:
        return ['.json']
    
    def validate(self, data: Any) -> bool:
        if not isinstance(data, (dict, list)):
            return False
        try:
            json.dumps(data)  # 测试是否可序列化
            return True
        except (TypeError, ValueError):
            return False
    
    def read(self, source: str) -> Any:
        try:
            with open(source, 'r', encoding='utf-8') as f:
                return json.load(f)
        except json.JSONDecodeError as e:
            raise ValueError(f"JSON解析错误: {e}")
    
    def write(self, data: Any, destination: str) -> bool:
        if not self.validate(data):
            raise ValueError("无效的JSON数据")
        try:
            with open(destination, 'w', encoding='utf-8') as f:
                json.dump(data, f, indent=2, ensure_ascii=False)
            return True
        except Exception as e:
            print(f"写入错误: {e}")
            return False

class CSVProcessor(DataProcessor):
    """CSV数据处理器"""
    
    @property
    def supported_extensions(self) -> List[str]:
        return ['.csv']
    
    def validate(self, data: Any) -> bool:
        return isinstance(data, list) and all(isinstance(row, dict) for row in data)
    
    def read(self, source: str) -> List[Dict]:
        try:
            with open(source, 'r', encoding='utf-8') as f:
                return list(csv.DictReader(f))
        except Exception as e:
            raise ValueError(f"CSV读取错误: {e}")
    
    def write(self, data: List[Dict], destination: str) -> bool:
        if not self.validate(data):
            raise ValueError("无效的CSV数据格式")
        try:
            with open(destination, 'w', newline='', encoding='utf-8') as f:
                if not data:
                    return True
                writer = csv.DictWriter(f, fieldnames=data[0].keys())
                writer.writeheader()
                writer.writerows(data)
            return True
        except Exception as e:
            print(f"写入错误: {e}")
            return False

class DataProcessorFactory:
    """数据处理器工厂"""
    
    _processors: Dict[str, DataProcessor] = {
        '.json': JSONProcessor(),
        '.csv': CSVProcessor()
    }
    
    @classmethod
    def get_processor(cls, file_path: str) -> DataProcessor:
        """根据文件扩展名获取相应的处理器"""
        import os
        ext = os.path.splitext(file_path)[1].lower()
        processor = cls._processors.get(ext)
        if not processor:
            raise ValueError(f"不支持的文件类型: {ext}")
        return processor

# 使用示例
def convert_file(source: str, destination: str):
    """文件转换功能"""
    try:
        # 获取源文件和目标文件的处理器
        source_processor = DataProcessorFactory.get_processor(source)
        dest_processor = DataProcessorFactory.get_processor(destination)
        
        # 读取源文件
        data = source_processor.read(source)
        
        # 写入目标文件
        if dest_processor.write(data, destination):
            print(f"文件转换成功: {source} -> {destination}")
        else:
            print("文件转换失败")
            
    except Exception as e:
        print(f"转换错误: {e}")

# 测试文件转换
if __name__ == "__main__":
    # JSON到CSV的转换
    convert_file("data.json", "output.csv")
    
    # CSV到JSON的转换
    convert_file("data.csv", "output.json")

5. 实战案例:图形计算器 📐

让我们创建一个图形计算器,它能够处理不同类型的图形,计算它们的面积和周长:

from abc import ABC, abstractmethod
from typing import List
import math

class Shape(ABC):
    """抽象基类:形状"""
    
    @abstractmethod
    def area(self) -> float:
        """计算面积"""
        pass
    
    @abstractmethod
    def perimeter(self) -> float:
        """计算周长"""
        pass
    
    @abstractmethod
    def description(self) -> str:
        """返回形状描述"""
        pass

class Rectangle(Shape):
    """矩形类"""
    
    def __init__(self, width: float, height: float):
        if width <= 0 or height <= 0:
            raise ValueError("矩形的宽和高必须为正数")
        self._width = width
        self._height = height
    
    def area(self) -> float:
        return self._width * self._height
    
    def perimeter(self) -> float:
        return 2 * (self._width + self._height)
    
    def description(self) -> str:
        return f"矩形(宽={self._width}, 高={self._height})"

class Circle(Shape):
    """圆形类"""
    
    def __init__(self, radius: float):
        if radius <= 0:
            raise ValueError("圆的半径必须为正数")
        self._radius = radius
    
    def area(self) -> float:
        return math.pi * self._radius ** 2
    
    def perimeter(self) -> float:
        return 2 * math.pi * self._radius
    
    def description(self) -> str:
        return f"圆形(半径={self._radius})"

class Triangle(Shape):
    """三角形类"""
    
    def __init__(self, a: float, b: float, c: float):
        if not self._is_valid_triangle(a, b, c):
            raise ValueError("无效的三角形边长")
        self._sides = (a, b, c)
    
    @staticmethod
    def _is_valid_triangle(a: float, b: float, c: float) -> bool:
        """检查三条边是否能构成三角形"""
        return (a > 0 and b > 0 and c > 0 and
                a + b > c and b + c > a and a + c > b)
    
    def area(self) -> float:
        """使用海伦公式计算面积"""
        a, b, c = self._sides
        s = (a + b + c) / 2  # 半周长
        return math.sqrt(s * (s - a) * (s - b) * (s - c))
    
    def perimeter(self) -> float:
        return sum(self._sides)
    
    def description(self) -> str:
        return f"三角形(边长={', '.join(map(str, self._sides))})"

class GeometryCalculator:
    """几何计算器类"""
    
    def __init__(self):
        self._shapes: List[Shape] = []
    
    def add_shape(self, shape: Shape):
        """添加形状"""
        self._shapes.append(shape)
        print(f"已添加: {shape.description()}")
    
    def calculate_total_area(self) -> float:
        """计算所有形状的总面积"""
        return sum(shape.area() for shape in self._shapes)
    
    def calculate_total_perimeter(self) -> float:
        """计算所有形状的总周长"""
        return sum(shape.perimeter() for shape in self._shapes)
    
    def show_all_shapes(self):
        """显示所有形状的信息"""
        print("\n形状列表:")
        for i, shape in enumerate(self._shapes, 1):
            print(f"{i}. {shape.description()}")
            print(f"   面积: {shape.area():.2f}")
            print(f"   周长: {shape.perimeter():.2f}")

def main():
    # 创建计算器实例
    calculator = GeometryCalculator()
    
    # 添加一些形状
    try:
        calculator.add_shape(Rectangle(5, 3))
        calculator.add_shape(Circle(4))
        calculator.add_shape(Triangle(3, 4, 5))
        
        # 显示所有形状信息
        calculator.show_all_shapes()
        
        # 显示总计
        print(f"\n总面积: {calculator.calculate_total_area():.2f}")
        print(f"总周长: {calculator.calculate_total_perimeter():.2f}")
        
    except ValueError as e:
        print(f"错误: {e}")

if __name__ == "__main__":
    main()

实战案例特点:

  1. 抽象基类:使用Shape作为抽象基类,定义了必须实现的方法
  2. 继承与多态:各种具体图形类继承自Shape
  3. 封装:使用私有属性保护数据
  4. 类型提示:使用typing模块增加代码可读性
  5. 错误处理:包含适当的验证和异常处理
  6. 静态方法:用于辅助功能(如三角形的有效性检查)

扩展建议:

  1. 添加更多图形类型(如多边形、椭圆等)
  2. 实现图形的缩放和旋转
  3. 添加图形的绘制功能
  4. 实现文件保存和加载功能
  5. 添加图形组合功能
  6. 实现简单的GUI界面
  7. 添加单位转换功能

这个实战案例展示了面向对象编程的进阶概念,包括继承、多态、封装和抽象类的实际应用。它提供了一个可扩展的框架,可以根据需要添加更多功能。


如果你觉得这篇文章有帮助,欢迎点赞转发,也期待在评论区看到你的想法和建议!👇

咱们下一期见!

;