Python入门第9课:面向对象编程(OOP)从零开始,类、对象与三大特性

Python入门第9课:面向对象编程(OOP)从零开始,类、对象与三大特性

作者: 蛋皮
标签: Python, 面向对象编程, OOP, 类, 对象, 封装, 继承, 多态, 编程基础

欢迎回到Python入门系列!在上一课中,我们学习了如何通过模块与包来组织代码,让程序结构更清晰。今天,我们将进入一个更高级、更强大的编程范式——面向对象编程 (Object-Oriented Programming, OOP)。这是现代软件开发的基石之一,掌握它将彻底改变你设计和构建程序的方式。

想象一下,你不是在编写一系列零散的函数和变量,而是在创造一个个能够“思考”和“行动”的智能实体。这些实体拥有自己的数据行为,可以相互协作。这就是面向对象编程的魅力!


什么是面向对象编程 (OOP)?

传统的过程式编程(我们之前主要使用的)是将程序视为一系列按顺序执行的步骤(函数)。而面向对象编程则是将程序视为一组对象 (Objects) 的集合。每个对象都是现实世界中某个事物的软件模型

  • 对象 (Object): 程序中的一个具体实例,比如一只“猫”、一辆“汽车”、一个“用户账户”。对象拥有状态 (State)行为 (Behavior)
  • 类 (Class): 是创建对象的蓝图 (Blueprint)模板 (Template)。它定义了对象应该具有哪些状态(属性)和行为(方法)。

核心思想: 万物皆对象。我们通过定义类来描述一类事物的共同特征,然后根据类创建具体的对象来使用。


核心概念:类 (Class) 与 对象 (Object)

1. 定义一个类

使用 class 关键字来定义一个类。类名通常采用大驼峰命名法 (PascalCase)

class Dog:
    """
    这是一个Dog类,代表一只狗。
    """
    # 类属性 (Class Attribute): 所有Dog实例共享的属性
    species = "Canis lupus familiaris"

    # 构造方法 (Constructor): 在创建对象时自动调用
    def __init__(self, name, age):
        # 实例属性 (Instance Attribute): 每个对象独有的属性
        self.name = name  # self 指向当前创建的对象实例
        self.age = age

    # 实例方法 (Instance Method): 对象可以执行的行为
    def bark(self):
        print(f"{self.name} says: Woof! Woof!")

    def get_info(self):
        return f"名字: {self.name}, 年龄: {self.age}岁, 物种: {Dog.species}"

    # 另一个实例方法
    def have_birthday(self):
        self.age += 1
        print(f"祝 {self.name} 生日快乐!现在 {self.name} {self.age} 岁了!")

代码解析:

  • class Dog:: 定义一个名为 Dog 的类。
  • species = "...": 类属性。这个属性属于 Dog 类本身,所有 Dog 的实例都共享这个值。
  • def __init__(self, name, age):: 构造方法。名字固定为 __init__。当创建 Dog 的新实例时,这个方法会被自动调用。self 是一个特殊的参数,它代表即将被创建的这个具体对象nameage 是我们希望为每个狗实例设置的参数。
  • self.name = name: 在构造方法中,使用 self.属性名 = 值 的方式为当前对象(self)设置实例属性。每个 Dog 对象都有自己的 nameage
  • def bark(self):: 实例方法。方法的第一个参数必须是 self,这样方法内部才能访问该对象的属性和其他方法。bark 方法定义了狗可以“汪汪叫”的行为。

2. 创建对象 (实例化)

使用类名加上括号 () 来创建对象,这个过程称为实例化 (Instantiation)

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

# 访问对象的属性
print(dog1.name)  # 输出: 旺财
print(dog2.age)   # 输出: 5

# 访问类属性 (可以通过类名或实例访问)
print(Dog.species)    # 推荐方式: 通过类名访问
print(dog1.species)   # 也可以通过实例访问

# 调用对象的方法
dog1.bark()  # 输出: 旺财 says: Woof! Woof!
dog2.bark()  # 输出: 小黑 says: Woof! Woof!

# 调用其他方法
info1 = dog1.get_info()
print(info1)  # 输出: 名字: 旺财, 年龄: 3岁, 物种: Canis lupus familiaris

dog1.have_birthday()  # 输出: 祝 旺财 生日快乐!现在 旺财 4 岁了!
print(dog1.get_info()) # 再次获取信息,年龄已更新

关键点:

  • dog1dog2Dog 类的两个不同的实例
  • 它们有各自的 nameage(实例属性)。
  • 它们共享 species(类属性)。
  • 它们都可以调用 bark(), get_info(), have_birthday() 等方法。

OOP的三大特性

面向对象编程有三大核心特性:封装 (Encapsulation)继承 (Inheritance)多态 (Polymorphism)。它们是OOP强大和灵活的根源。

1. 封装 (Encapsulation)

定义: 将对象的数据(属性)操作数据的代码(方法) 集成在一个单元(即类)中,并尽可能隐藏对象的内部实现细节。外部代码通过对象提供的接口(方法) 来与对象交互,而不是直接操作其内部数据。

目的: 提高代码的安全性、可维护性和复用性。隐藏复杂性,暴露简单接口。

在Python中如何实现封装?

Python没有像Java那样的 private 关键字,但它通过命名约定来实现封装:

  • 公有 (Public): 属性和方法名没有下划线前缀。可以从类外部自由访问。
    class MyClass:
        def __init__(self):
            self.public_attr = "我是公开的"  # 公有属性
    
        def public_method(self):           # 公有方法
            return "我是公开的方法"
    
  • 受保护 (Protected): 使用单个下划线 _ 作为前缀(如 _attr, _method)。这是一种约定,表示该属性或方法是“内部使用”的,不建议在类外部直接访问,但Python不会阻止你。
    class MyClass:
        def __init__(self):
            self._internal_data = "内部数据,不建议直接访问"
    
        def _helper_function(self):  # 辅助函数
            return "内部辅助"
    
  • 私有 (Private): 使用双下划线 __ 作为前缀(如 __attr, __method)。Python会对其进行名称改写 (Name Mangling),使其在类外部更难访问(但不是绝对不可能),强制通过类的方法来操作。
    class BankAccount:
        def __init__(self, owner, initial_balance):
            self.owner = owner
            self.__balance = initial_balance  # 私有属性
    
        def deposit(self, amount):
            if amount > 0:
                self.__balance += amount
                print(f"存款 {amount} 元成功。")
            else:
                print("存款金额必须大于0。")
    
        def withdraw(self, amount):
            if 0 < amount <= self.__balance:
                self.__balance -= amount
                print(f"取款 {amount} 元成功。")
            else:
                print("余额不足或取款金额无效。")
    
        def get_balance(self):  # 公有方法,用于安全地获取余额
            return self.__balance
    
    # 使用
    account = BankAccount("张三", 1000)
    account.deposit(500)  # 正确方式
    account.withdraw(200) # 正确方式
    print(f"当前余额: {account.get_balance()}") # 通过公有方法获取
    
    # 尝试直接访问私有属性 (不推荐,且会报错)
    # print(account.__balance) # AttributeError!
    # 实际上,Python将其改写为 _BankAccount__balance
    # print(account._BankAccount__balance) # 可以访问,但破坏了封装原则,不要这样做!
    

封装的好处:BankAccount 例子中,我们通过 depositwithdraw 方法控制了对 __balance 的修改,确保了业务逻辑(如不能取负数、不能透支)被强制执行,保护了数据的完整性。

2. 继承 (Inheritance)

定义: 一个类(子类/派生类)可以继承另一个类(父类/基类)的属性和方法。子类在拥有父类所有功能的基础上,可以扩展新的属性和方法,或者重写 (Override) 父类的方法以实现不同的行为。

目的: 实现代码复用,建立类之间的层次关系,体现“is-a”关系(如“狗”是“动物”的一种)。

语法: class 子类名(父类名):

# 定义一个更通用的父类
class Animal:
    def __init__(self, name, species):
        self.name = name
        self.species = species

    def make_sound(self):
        print(f"{self.name} 发出了一种声音。")

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

# Dog类继承Animal类
class Dog(Animal):
    def __init__(self, name, age, breed="未知品种"):
        # 调用父类的构造方法,初始化从父类继承来的属性
        super().__init__(name, "Canis lupus familiaris")
        # Dog类特有的属性
        self.age = age
        self.breed = breed

    # 重写 (Override) 父类的 make_sound 方法
    def make_sound(self):
        print(f"{self.name} 汪汪叫: Woof! Woof!")

    # 扩展:Dog类特有的方法
    def fetch(self):
        print(f"{self.name} 正在捡球!")

# Cat类也继承Animal类
class Cat(Animal):
    def __init__(self, name, age, color="未知颜色"):
        super().__init__(name, "Felis catus")
        self.age = age
        self.color = color

    # 重写 make_sound 方法
    def make_sound(self):
        print(f"{self.name} 喵喵叫: Meow!")

    # Cat类特有的方法
    def climb_tree(self):
        print(f"{self.name} 正在爬树!")

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

# 调用继承的方法
dog.sleep()  # 调用父类Animal的sleep方法
cat.sleep()

# 调用重写的方法
dog.make_sound()  # 输出: 旺财 汪汪叫: Woof! Woof!
cat.make_sound()  # 输出: 咪咪 喵喵叫: Meow!

# 调用子类特有的方法
dog.fetch()     # 输出: 旺财 正在捡球!
cat.climb_tree() # 输出: 咪咪 正在爬树!

# 访问继承的和特有的属性
print(f"{dog.name}{dog.breed} 品种。")
print(f"{cat.name}{cat.color} 色的。")

关键点:

  • super().__init__(...): 这是调用父类构造方法的标准方式,确保父类的初始化代码被执行。
  • 方法重写 (Method Overriding): 子类定义一个与父类同名的方法,当调用该方法时,子类的方法会“覆盖”父类的方法。
  • 方法扩展: 子类可以添加父类没有的新方法。

3. 多态 (Polymorphism)

定义: 多态字面意思是“多种形态”。在OOP中,它指的是同一个接口(方法名)在不同的对象上可以有不同的实现方式。多态通常与继承和方法重写一起使用。

目的: 提高代码的灵活性和可扩展性。允许我们编写能处理多种类型对象的通用代码。

核心思想: “一个接口,多种实现”

# 继续使用上面的 Animal, Dog, Cat 类

# 定义一个函数,它接受任何 Animal 类型的对象
def animal_sound(animal):
    """
    这个函数不关心传入的是哪种具体的动物,
    它只知道所有动物都有 make_sound 方法。
    """
    animal.make_sound()  # 调用 make_sound 方法

# 创建不同类型的动物对象
animals = [Dog("旺财", 3), Cat("咪咪", 2), Animal("未知生物", "未知")]

# 对列表中的每个动物调用 animal_sound 函数
for animal in animals:
    animal_sound(animal)
    # 输出:
    # 旺财 汪汪叫: Woof! Woof!
    # 咪咪 喵喵叫: Meow!
    # 未知生物 发出了一种声音。

print("---")

# 另一个例子:一个能和动物玩耍的函数
def play_with_animal(animal):
    print(f"正在和 {animal.name} 玩耍...")
    animal_sound(animal)  # 先让动物叫一声
    # 尝试调用特定行为,但需要检查类型或使用 hasattr
    if isinstance(animal, Dog):
        animal.fetch()
    elif isinstance(animal, Cat):
        animal.climb_tree()
    else:
        print(f"{animal.name} 不知道玩什么。")

# 测试
for animal in animals:
    play_with_animal(animal)
    print()

多态的体现:

  1. animal_sound 函数的参数 animal 可以是 AnimalDogCat 类型的任何对象。
  2. 当我们调用 animal.make_sound() 时,Python会根据 animal 实际指向的对象类型,自动调用相应类中定义的 make_sound 方法。
  3. 这就是运行时多态 (Runtime Polymorphism)动态绑定 (Dynamic Binding)。代码在运行时才决定具体调用哪个方法。

多态的好处: 我们的 animal_sound 函数非常通用。如果以后我们创建了 Bird 类并让它继承 Animal 且重写 make_sound 方法,这个函数无需修改就能正确处理 Bird 对象!这极大地增强了代码的可扩展性。


静态方法与类方法

除了实例方法,Python类还支持两种特殊的方法:

1. 静态方法 (@staticmethod)

  • 不需要 selfcls 参数。
  • 它本质上是一个属于类的普通函数,不会访问类或实例的任何数据。
  • 通常用于与类相关的工具函数。
  • @staticmethod 装饰器标记。
class MathUtils:
    @staticmethod
    def add(x, y):
        """一个与MathUtils类相关的加法工具函数"""
        return x + y

    @staticmethod
    def is_even(number):
        return number % 2 == 0

# 调用静态方法:可以直接通过类名调用,不需要创建实例
result = MathUtils.add(5, 3)
print(result)  # 8
print(MathUtils.is_even(4))  # True

2. 类方法 (@classmethod)

  • 第一个参数是 cls,代表类本身,而不是实例。
  • 可以访问类属性,但不能访问实例属性(因为没有 self)。
  • 常用于创建替代构造函数 (Alternative Constructors)
  • @classmethod 装饰器标记。
class Person:
    species = "Homo sapiens"

    def __init__(self, name, age):
        self.name = name
        self.age = age

    @classmethod
    def from_string(cls, person_str):
        """
        一个替代构造函数,可以从 "姓名-年龄" 格式的字符串创建Person实例。
        cls 指向 Person 类。
        """
        name, age_str = person_str.split('-')
        return cls(name, int(age_str))  # 等同于 Person(name, int(age_str))

    def introduce(self):
        return f"大家好,我是 {self.name},今年 {self.age} 岁,属于 {Person.species}。"

# 使用替代构造函数
person1 = Person("Alice", 25)
person2 = Person.from_string("Bob-30")  # 无需手动分割字符串

print(person1.introduce())
print(person2.introduce())

OOP最佳实践与总结

  1. 何时使用OOP? 当你的程序涉及多个具有相似属性和行为的实体,或者逻辑变得复杂时,OOP是很好的选择。简单的脚本可能不需要。
  2. 高内聚,低耦合: 类内部的属性和方法应该紧密相关(高内聚)。类与类之间的依赖应该尽量少(低耦合)。
  3. 单一职责原则 (SRP): 一个类应该只有一个引起它变化的原因。即,一个类只负责一项职责。
  4. 善用继承: 继承是强大的,但不要滥用。优先考虑“组合 (Composition)”而非“继承 (Inheritance)”。即,一个类包含另一个类的对象,而不是继承它。
  5. 清晰的命名: 类名、方法名、属性名要清晰、有意义。
  6. 编写文档: 使用文档字符串 ("""...""") 为类和方法编写说明。

总结

今天我们深入学习了面向对象编程的核心概念:

  • 类 (Class) 是蓝图,对象 (Object) 是根据蓝图创建的实例。
  • 封装 隐藏内部细节,通过接口与外界交互,保护数据安全。
  • 继承 实现代码复用,建立类的层次结构。
  • 多态 允许同一接口在不同对象上有不同实现,提高代码灵活性。

OOP是一种强大的思维方式,它帮助我们将复杂的世界映射到代码中,使程序更易于理解、维护和扩展。虽然一开始可能需要一些适应,但一旦掌握,你将能构建出更加优雅和健壮的软件。

练习建议:

  1. 创建一个 Car 类,包含品牌、型号、年份等属性,以及启动、加速、刹车等方法。
  2. 创建一个 Vehicle 父类,让 CarMotorcycle 继承它。
  3. 创建一个 BankAccount 类,包含存款、取款、查询余额等方法,并使用私有属性保护余额。
  4. 尝试为你的 Car 类添加一个静态方法(如计算油耗)和一个类方法(如从配置文件创建实例)。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值