跳转至

03 - 面向对象基础

学习时间: 45-60分钟 重要性: ⭐⭐⭐⭐ 理解代码组织方式


🎯 学习目标

  • 理解类和对象的概念
  • 掌握属性和方法
  • 理解继承和多态
  • 知道何时使用OOP

🏗️ 类和对象

基本概念

Python
# 类是模板,对象是根据模板创建的实例

class Dog:
    """一个简单的狗类"""

    # 类属性(所有实例共享)
    species = "Canis familiaris"

    def __init__(self, name, age):
        """构造函数:初始化对象"""
        self.name = name  # 实例属性
        self.age = age

    def bark(self):
        """实例方法"""
        return f"{self.name} says Woof!"

    def introduce(self):
        return f"I'm {self.name}, {self.age} years old"

# 创建对象(实例化)
dog1 = Dog("Buddy", 3)
dog2 = Dog("Max", 5)

# 访问属性
print(dog1.name)  # "Buddy"
print(dog2.age)   # 5

# 调用方法
print(dog1.bark())  # "Buddy says Woof!"
print(dog2.introduce())  # "I'm Max, 5 years old"

# 类属性是共享的
print(dog1.species)  # "Canis familiaris"
print(dog2.species)  # "Canis familiaris"

类 vs 实例属性

Python
class Student:
    school = "清华大学"  # 类属性

    def __init__(self, name):
        self.name = name  # 实例属性

# 类属性共享
s1 = Student("张三")
s2 = Student("李四")
print(s1.school)  # "清华大学"
print(s2.school)  # "清华大学"

# 修改类属性(影响所有实例)
Student.school = "北京大学"
print(s1.school)  # "北京大学"
print(s2.school)  # "北京大学"

# 实例属性独立
s1.name = "王五"
print(s1.name)  # "王五"
print(s2.name)  # "李四"(不受影响)

🔧 方法的类型

Python
class Calculator:
    def __init__(self, value):
        self.value = value

    # 实例方法(最常用)
    def add(self, x):
        """需要访问实例属性"""
        return self.value + x

    # @classmethod类方法:第一个参数cls代表类本身
    @classmethod
    def from_string(cls, str_value):
        """从字符串创建实例(工厂方法)"""
        value = int(str_value)
        return cls(value)

    # @staticmethod静态方法:不需要self/cls,通过类名直接调用
    @staticmethod
    def is_valid(x):
        """不需要访问实例或类,只是相关的工具函数"""
        return isinstance(x, (int, float))  # isinstance(obj, type)检查对象是否是指定类型(含子类)的实例

# 使用
calc = Calculator(10)
print(calc.add(5))  # 15

# 类方法:替代构造函数
calc2 = Calculator.from_string("20")
print(calc2.value)  # 20

# 静态方法:不需要实例
print(Calculator.is_valid(10))  # True
print(Calculator.is_valid("abc"))  # False

🔒 封装和访问控制

公共 vs 私有属性

Python
class BankAccount:
    def __init__(self, balance):
        self.balance = balance  # 公共属性
        self.__pin = "1234"     # 私有属性(名称改写)

    def deposit(self, amount):
        if amount > 0:
            self.balance += amount

    def __verify_pin(self, pin):
        """私有方法"""
        return self.__pin == pin

    def withdraw(self, amount, pin):
        if self.__verify_pin(pin):
            if amount <= self.balance:
                self.balance -= amount
                return True
        return False

account = BankAccount(1000)
account.deposit(500)
print(account.balance)  # 1500

# 私有属性在外部无法直接访问
# print(account.__pin)  # AttributeError!

# 但Python的"私有"是通过名称改写实现的
# 实际上可以通过 _BankAccount__pin 访问(不推荐)
print(account._BankAccount__pin)  # "1234"

属性装饰器(推荐)

Python
class Temperature:
    def __init__(self, celsius):
        self._celsius = celsius  # 约定:单下划线表示"内部使用"

    @property  # @property将方法变为属性访问,无需括号调用
    def celsius(self):
        """获取摄氏度"""
        return self._celsius

    @celsius.setter
    def celsius(self, value):
        """设置摄氏度(带验证)"""
        if value < -273.15:
            raise ValueError("Temperature below -273.15°C is not possible")
        self._celsius = value

    @property
    def fahrenheit(self):
        """自动计算华氏度"""
        return self._celsius * 9/5 + 32

    @fahrenheit.setter
    def fahrenheit(self, value):
        """通过华氏度设置"""
        self._celsius = (value - 32) * 5/9

# 使用
temp = Temperature(25)
print(temp.celsius)  # 25
print(temp.fahrenheit)  # 77.0

temp.celsius = 30
print(temp.fahrenheit)  # 86.0

temp.fahrenheit = 100
print(temp.celsius)  # 37.777...

# temp.celsius = -300  # ValueError!

🧬 继承

基本继承

Python
class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        pass

    def eat(self):
        return f"{self.name} is eating"

# 子类继承父类
class Dog(Animal):
    def speak(self):
        return f"{self.name} says Woof!"

class Cat(Animal):
    def speak(self):
        return f"{self.name} says Meow!"

# 使用
dog = Dog("Buddy")
cat = Cat("Whiskers")

print(dog.speak())  # "Buddy says Woof!"
print(cat.speak())  # "Whiskers says Meow!"
print(dog.eat())    # "Buddy is eating"(继承自Animal)

调用父类方法

Python
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def introduce(self):
        return f"I'm {self.name}, {self.age} years old"

class Student(Person):
    def __init__(self, name, age, student_id):
        # 调用父类构造函数
        super().__init__(name, age)
        self.student_id = student_id

    def introduce(self):
        # 调用父类方法,然后添加额外信息
        base_intro = super().introduce()
        return f"{base_intro}, ID: {self.student_id}"

# 使用
student = Student("张三", 20, "S1001")
print(student.introduce())
# "I'm 张三, 20 years old, ID: S1001"

方法重写与多态

Python
class Shape:
    def area(self):
        raise NotImplementedError("Subclass must implement area()")

    def perimeter(self):
        raise NotImplementedError("Subclass must implement perimeter()")

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

    def perimeter(self):
        return 2 * (self.width + self.height)

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        import math
        return math.pi * self.radius ** 2

    def perimeter(self):
        import math
        return 2 * math.pi * self.radius

# 多态:统一接口,不同实现
shapes = [Rectangle(10, 5), Circle(7)]

for shape in shapes:
    print(f"Area: {shape.area()}, Perimeter: {shape.perimeter()}")

🎯 何时使用OOP

适合使用类的场景

Python
# ✅ 适合:需要维护状态的复杂数据
class DataProcessor:
    def __init__(self, filepath):
        self.filepath = filepath
        self.data = None
        self.clean = False

    def load(self):
        self.data = pd.read_csv(self.filepath)

    def clean_data(self):
        # 复杂的清洗逻辑
        self.clean = True

    def save(self, output_path):
        if self.clean:
            self.data.to_csv(output_path)

# ✅ 适合:需要封装和验证
class BankAccount:
    def __init__(self, initial_balance):
        if initial_balance < 0:
            raise ValueError("Initial balance cannot be negative")
        self._balance = initial_balance

    @property
    def balance(self):
        return self._balance

    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("Deposit must be positive")
        self._balance += amount

不需要使用类的场景

Python
# ❌ 不适合:简单的数据处理
class MathOperations:
    def add(self, a, b):
        return a + b

    def subtract(self, a, b):
        return a - b

# ✅ 更好:直接使用函数
def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

# ❌ 不适合:只是存储数据的容器
class PersonData:
    def __init__(self, name, age, email):
        self.name = name
        self.age = age
        self.email = email

# ✅ 更好:使用dataclass或字典
from dataclasses import dataclass

@dataclass  # @dataclass自动生成__init__、__repr__等方法,简化类定义
class Person:
    name: str
    age: int
    email: str

# 或者直接用字典
person = {"name": "张三", "age": 25, "email": "test@qq.com"}

💡 实用技巧

数据类(dataclass)- Python 3.7+

Python
from dataclasses import dataclass

@dataclass
class Student:
    name: str
    age: int
    grades: list[int]

    def average_grade(self):
        return sum(self.grades) / len(self.grades) if self.grades else 0

# 自动生成__init__, __repr__, __eq__等方法
student = Student("张三", 20, [85, 90, 78])
print(student)  # Student(name='张三', age=20, grades=[85, 90, 78])
print(student.average_grade())  # 84.333...

魔术方法(特殊方法)

Python
class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    # __str__定义print()输出格式;__repr__定义调试/交互式显示格式
    def __repr__(self):
        return f"Vector({self.x}, {self.y})"

    def __str__(self):
        return f"({self.x}, {self.y})"

    # 相等性比较
    def __eq__(self, other):
        if not isinstance(other, Vector):
            return False
        return self.x == other.x and self.y == other.y

    # 运算符重载
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

    def __mul__(self, scalar):
        return Vector(self.x * scalar, self.y * scalar)

    # 长度
    def __len__(self):
        return int((self.x**2 + self.y**2) ** 0.5)

# 使用
v1 = Vector(3, 4)
v2 = Vector(1, 2)

print(v1)  # "(3, 4)"
print(repr(v1))  # "Vector(3, 4)"
print(v1 == v2)  # False
print(v1 + v2)  # Vector(4, 6)
print(v1 * 2)  # Vector(6, 8)
print(len(v1))  # 5

上下文管理器

Python
class FileManager:
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode
        self.file = None

    def __enter__(self):  # __enter__/__exit__实现上下文管理器协议,支持with语句
        self.file = open(self.filename, self.mode)
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):  # exc_type=异常类型, exc_val=异常值, exc_tb=回溯对象;无异常时均为None
        if self.file:
            self.file.close()
        # 可以处理异常
        if exc_type:
            print(f"An error occurred: {exc_val}")
        return True  # 返回True表示已处理异常不再向上传播;返回False/None则继续抛出

# 使用
with FileManager("test.txt", "w") as f:
    f.write("Hello")
    # 即使出错,文件也会自动关闭

📝 练习

练习1: 创建一个简单的类

Python
class Rectangle:
    """
    实现一个矩形类,包含:
    - 属性:width, height
    - 方法:area(), perimeter(), is_square()
    """
    # TODO: 实现这个类
    pass

# 测试
rect = Rectangle(5, 5)
print(rect.area())  # 应该输出 25
print(rect.is_square())  # 应该输出 True

练习2: 继承与多态

Python
class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

    def calculate_bonus(self):
        """计算奖金,默认为工资的10%"""
        return self.salary * 0.1

class Manager(Employee):
    """经理的奖金为工资的20%"""
    # TODO: 重写calculate_bonus方法
    pass

class Developer(Employee):
    """开发者的奖金为工资的15%"""
    # TODO: 重写calculate_bonus方法
    pass

# 测试
emp = Employee("张三", 5000)
mgr = Manager("李四", 8000)
dev = Developer("王五", 6000)

print(emp.calculate_bonus())  # 500
print(mgr.calculate_bonus())  # 1600
print(dev.calculate_bonus())  # 900

练习3: 使用属性装饰器

Python
class BankAccount:
    """
    实现银行账户类:
    - balance属性:只能通过deposit和withdraw修改
    - deposit方法:存款
    - withdraw方法:取款(不能透支)
    - 使用@property装饰器
    """
    # TODO: 实现这个类
    pass

🎯 自我检查

完成这个主题后,你应该:

  • 理解类和对象的概念
  • 知道实例方法、类方法、静态方法的区别
  • 能使用继承和方法重写
  • 理解封装和属性装饰器
  • 知道何时使用OOP,何时使用函数
  • 不查资料完成上面的练习

📚 延伸阅读


下一步: 04 - 文件操作与异常处理