← 返回 Python 7天入门专题

Day 5 · 面向对象编程

类与对象 · 属性方法 · 继承 · 多态 · 封装 · 魔术方法 | 建议时长:3-4小时
第一性原理:函数是"动作的封装"。但现实世界中,数据和操作数据的动作是绑定在一起的——学生有姓名成绩(数据),还要能考试毕业(动作)。面向对象编程(OOP)就是把"数据+动作"打包成一个整体——类。类是蓝图,对象是按蓝图造出来的实体。

1. 类与对象 — 从蓝图到实体

类 (Class)

蓝图/模板/设计图

定义了有哪些属性和行为,但本身不占数据

例:class Dog — 定义"狗"这个概念

对象 (Object)

实例/实体/具体的东西

按类创建的具体数据,每个对象独立

例:Dog("旺财") — 一只具体的狗

# 定义类
class Dog:
    """狗类"""

    # __init__ 是构造方法,创建对象时自动调用
    def __init__(self, name, age):
        self.name = name    # 实例属性
        self.age = age

    # 实例方法
    def bark(self):
        return f"{self.name}说:汪汪!"

    def info(self):
        return f"名字:{self.name}, 年龄:{self.age}岁"

# 创建对象(实例化)
dog1 = Dog("旺财", 3)
dog2 = Dog("小黑", 5)

# 调用方法
print(dog1.bark())   # 旺财说:汪汪!
print(dog2.info())   # 名字:小黑, 年龄:5岁

# 访问属性
print(dog1.name)     # 旺财
self 的本质:self 就是"当前对象自己"。调用 dog1.bark() 时,Python自动把dog1传给self。所以方法定义必须写self,但调用时不传。

1.1 类属性 vs 实例属性

class Dog:
    # 类属性 — 所有对象共享
    species = "犬科动物"
    count = 0

    def __init__(self, name):
        self.name = name    # 实例属性 — 每个对象独有
        Dog.count += 1   # 每创建一个对象,计数+1

dog1 = Dog("旺财")
dog2 = Dog("小黑")

print(Dog.species)     # 犬科动物 (类属性,通过类访问)
print(dog1.species)     # 犬科动物 (也能通过对象访问)
print(dog1.name)        # 旺财 (实例属性)
print(Dog.count)       # 2 (创建了2个对象)

2. 封装 — 保护内部数据

第一性原理:有些数据不希望被外部随意修改(比如账户余额不能直接改成0)。封装就是"隐藏内部实现,只暴露安全的接口"。
class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self.__balance = 0    # 私有属性:两个下划线开头
        self.deposit(balance)  # 通过方法初始化

    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("存款金额必须大于0")
        self.__balance += amount
        return self.__balance

    def withdraw(self, amount):
        if amount > self.__balance:
            raise ValueError("余额不足")
        self.__balance -= amount
        return self.__balance

    def get_balance(self):
        return self.__balance

account = BankAccount("张三", 1000)
account.deposit(500)
print(account.get_balance())    # 1500

# account.__balance = 0  ← 不能直接改!(实际上是名称重整)
# account.__balance     ← 不能直接访问!
# 只能通过deposit/withdraw来操作,保证安全
Python的"私有"是约定不是强制:用 __ 开头的属性实际上会被"名称重整"为 _类名__属性名,所以理论上还是能访问 account._BankAccount__balance。但请尊重约定,不要从外部直接访问。

2.1 @property — 用方法伪装成属性

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

    @property
    def area(self):
        """面积 — 像属性一样访问,但每次动态计算"""
        import math
        return math.pi * self.radius ** 2

    @property
    def perimeter(self):
        """周长"""
        import math
        return 2 * math.pi * self.radius

c = Circle(5)
print(c.area)        # 78.54... (不加括号!)
print(c.perimeter)   # 31.42...

3. 继承 — 代码复用的核武器

第一性原理:"狗是动物"、"猫是动物"——它们都有动物的共性(吃、睡),又各有特性。继承就是子类获得父类的所有属性和方法,再添加自己的特性。避免重复代码。
# 父类(基类)
class Animal:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def eat(self):
        print(f"{self.name}在吃东西")

    def sleep(self):
        print(f"{self.name}在睡觉")

# 子类 — 继承Animal
class Dog(Animal):
    def __init__(self, name, age, breed):
        super().__init__(name, age)  # 调用父类的__init__
        self.breed = breed       # 子类新增属性

    def bark(self):              # 子类新增方法
        print(f"{self.name}:汪汪!")

    def info(self):              # 子类可以覆盖父类方法
        return f"{self.breed}犬{name}, {self.age}岁"

class Cat(Animal):
    def meow(self):
        print(f"{self.name}:喵~")

# 使用
dog = Dog("旺财", 3, "金毛")
cat = Cat("咪咪", 2)

dog.eat()     # 旺财在吃东西 (继承自Animal)
dog.sleep()   # 旺财在睡觉 (继承自Animal)
dog.bark()    # 旺财:汪汪! (Dog特有)
cat.meow()    # 咪咪:喵~ (Cat特有)

print(dog.info())   # 金毛犬旺财, 3岁

3.1 super() — 调用父类方法

class Vehicle:
    def __init__(self, brand):
        self.brand = brand
        print("Vehicle初始化")

class Car(Vehicle):
    def __init__(self, brand, model):
        super().__init__(brand)   # 先初始化父类
        self.model = model
        print("Car初始化")

car = Car("Toyota", "Camry")
# Vehicle初始化
# Car初始化

3.2 多重继承

class Flyable:
    def fly(self):
        print("飞行中...")

class Swimmable:
    def swim(self):
        print("游泳中...")

# 多重继承:同时获得两个父类的能力
class Duck(Flyable, Swimmable):
    def quack(self):
        print("嘎嘎!")

duck = Duck()
duck.fly()     # 飞行中...
duck.swim()    # 游泳中...
duck.quack()   # 嘎嘎!

4. 多态 — 同一接口,不同行为

第一性原理:不同对象调用同一个方法名,表现出不同的行为。比如"叫"——狗汪汪、猫喵喵、鸭嘎嘎。方法名相同,行为不同,这就是多态。
class Dog:
    def speak(self):
        return "汪汪"

class Cat:
    def speak(self):
        return "喵喵"

class Duck:
    def speak(self):
        return "嘎嘎"

# 多态:同一个函数,处理不同类型
def animal_sound(animal):
    print(animal.speak())  # 不关心是什么动物,只要有speak方法

animals = [Dog(), Cat(), Duck()]
for a in animals:
    animal_sound(a)
# 汪汪
# 喵喵
# 嘎嘎
Python的多态是"鸭子类型"(Duck Typing):"如果它走起来像鸭子、叫起来像鸭子,那它就是鸭子。" Python不检查类型,只检查方法是否存在。任何有 speak() 方法的对象都能传给 animal_sound()。

5. 魔术方法 — 让自定义类像内置类型一样用

方法触发时机用途
__init__创建对象时初始化
__str__print(对象) / str(对象)用户友好的显示
__repr__repr(对象) / 直接显示开发者友好的显示
__len__len(对象)支持长度
__eq__对象 == 对象支持相等比较
__lt__对象 < 对象支持大小比较
__add__对象 + 对象支持加法
__getitem__对象[key]支持索引
__iter__for x in 对象支持迭代
class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score

    def __str__(self):
        # print(student) 时显示
        return f"{self.name}({self.score}分)"

    def __repr__(self):
        # 直接输入变量名时显示
        return f"Student('{self.name}', {self.score})"

    def __eq__(self, other):
        # student1 == student2 时调用
        return self.score == other.score

    def __lt__(self, other):
        # student1 < student2 时调用(按分数比较)
        return self.score < other.score

    def __add__(self, other):
        # student1 + student2 → 合并分数
        return self.score + other.score

s1 = Student("张三", 85)
s2 = Student("李四", 92)

print(s1)             # 张三(85分) — __str__
print(s1 == s2)       # False — __eq__
print(s1 < s2)        # True — __lt__
print(s1 + s2)        # 177 — __add__

# 因为定义了__lt__,所以可以排序
students = [Student("王五", 78), Student("赵六", 95), s1, s2]
students.sort()
for s in students:
    print(s)  # 按分数从低到高

6. 类方法与静态方法

class MathHelper:
    pi = 3.14159

    # 实例方法 — 操作实例(self)
    def instance_method(self):
        return "我是实例方法"

    # 类方法 — 操作类(cls)
    @classmethod
    def circle_area(cls, radius):
        return cls.pi * radius ** 2  # 用cls访问类属性

    # 静态方法 — 不需要self也不需要cls
    @staticmethod
    def is_positive(n):
        return n > 0

# 类方法通过类调用
print(MathHelper.circle_area(5))   # 78.54...

# 静态方法通过类调用
print(MathHelper.is_positive(10))  # True

6.1 用类方法做工厂函数

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    @classmethod
    def from_string(cls, date_str):
        """从字符串 '2026-01-15' 创建Date对象"""
        year, month, day = map(int, date_str.split("-"))
        return cls(year, month, day)  # cls就是Date

    @classmethod
    def today(cls):
        """创建今天的Date对象"""
        import datetime
        t = datetime.date.today()
        return cls(t.year, t.month, t.day)

d1 = Date(2026, 1, 15)                 # 传统方式
d2 = Date.from_string("2026-01-15")      # 工厂方法
d3 = Date.today()                      # 工厂方法

7. 完整示例:图书管理系统

class Book:
    """图书类"""
    def __init__(self, title, author, isbn):
        self.title = title
        self.author = author
        self.isbn = isbn
        self.is_borrowed = False

    def __str__(self):
        status = "已借出" if self.is_borrowed else "可借"
        return f"《{self.title}》-{self.author} [{status}]"


class Library:
    """图书馆类"""
    def __init__(self, name):
        self.name = name
        self.books = []

    def add_book(self, book):
        self.books.append(book)
        print(f"已添加: {book}")

    def borrow(self, title):
        for book in self.books:
            if book.title == title and not book.is_borrowed:
                book.is_borrowed = True
                print(f"借出成功: {book}")
                return book
        print(f"《{title}》不可借")
        return None

    def return_book(self, title):
        for book in self.books:
            if book.title == title and book.is_borrowed:
                book.is_borrowed = False
                print(f"归还成功: {book}")
                return
        print(f"《{title}》不在借出列表")

    def list_books(self):
        print(f"\n=== {self.name} 图书列表 ===")
        for book in self.books:
            print(book)


# 使用
lib = Library("城市图书馆")
lib.add_book(Book("Python入门", "Guido", "001"))
lib.add_book(Book("算法导论", "CLRS", "002"))

lib.list_books()
lib.borrow("Python入门")
lib.list_books()
lib.return_book("Python入门")

8. 练习题(独立完成)

练习1:矩形类

创建Rectangle类,有width和height属性,方法:area()计算面积、perimeter()计算周长、is_square()判断是否正方形。用@property装饰area和perimeter。

参考答案
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

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

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

    def is_square(self):
        return self.width == self.height

r = Rectangle(5, 3)
print(f"面积: {r.area}, 周长: {r.perimeter}, 正方形: {r.is_square()}")
# 面积: 15, 周长: 16, 正方形: False

s = Rectangle(4, 4)
print(f"面积: {s.area}, 正方形: {s.is_square()}")
# 面积: 16, 正方形: True

练习2:员工管理系统

创建Employee基类(name, salary),Manager子类(额外有bonus,总工资=salary+bonus),Developer子类(额外有programming_language)。都有get_info()方法,但输出不同。

参考答案
class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

    def get_info(self):
        return f"员工: {self.name}, 工资: {self.salary}"

class Manager(Employee):
    def __init__(self, name, salary, bonus):
        super().__init__(name, salary)
        self.bonus = bonus

    def get_info(self):
        total = self.salary + self.bonus
        return f"经理: {self.name}, 基本工资: {self.salary}, 奖金: {self.bonus}, 总计: {total}"

class Developer(Employee):
    def __init__(self, name, salary, language):
        super().__init__(name, salary)
        self.language = language

    def get_info(self):
        return f"开发: {self.name}, 工资: {self.salary}, 语言: {self.language}"

emps = [
    Manager("张三", 20000, 5000),
    Developer("李四", 15000, "Python"),
    Employee("王五", 8000),
]
for e in emps:
    print(e.get_info())

练习3:购物车

创建Product类(name, price)和ShoppingCart类。购物车有add()、remove()、total()方法。用__len__支持len(cart),用__str__支持print(cart)。

参考答案
class Product:
    def __init__(self, name, price):
        self.name = name
        self.price = price

    def __str__(self):
        return f"{self.name}(¥{self.price})"

class ShoppingCart:
    def __init__(self):
        self.items = []

    def add(self, product, qty=1):
        self.items.append({"product": product, "qty": qty})

    def remove(self, product_name):
        self.items = [i for i in self.items if i["product"].name != product_name]

    def total(self):
        return sum(i["product"].price * i["qty"] for i in self.items)

    def __len__(self):
        return len(self.items)

    def __str__(self):
        lines = [f"  {i['product']} x{i['qty']}" for i in self.items]
        return "购物车:\n" + "\n".join(lines) + f"\n总计: ¥{self.total()}"

cart = ShoppingCart()
cart.add(Product("苹果", 5), 3)
cart.add(Product("面包", 10), 2)
cart.add(Product("牛奶", 8), 1)
print(cart)
print(f"商品种类: {len(cart)}")
cart.remove("面包")
print(cart)

9. Day 5 小结

今天你学会了什么