当前位置: 首页 > news >正文

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 += 1print(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 familiarisdog1.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 = ownerself.__balance = initial_balance  # 私有属性def deposit(self, amount):if amount > 0:self.__balance += amountprint(f"存款 {amount} 元成功。")else:print("存款金额必须大于0。")def withdraw(self, amount):if 0 < amount <= self.__balance:self.__balance -= amountprint(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 = nameself.species = speciesdef 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 = ageself.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 = ageself.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)  # 先让动物叫一声# 尝试调用特定行为,但需要检查类型或使用 hasattrif 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:@staticmethoddef add(x, y):"""一个与MathUtils类相关的加法工具函数"""return x + y@staticmethoddef 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 = nameself.age = age@classmethoddef 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 类添加一个静态方法(如计算油耗)和一个类方法(如从配置文件创建实例)。
http://www.dtcms.com/a/337151.html

相关文章:

  • Leetcode 3650. Minimum Cost Path with Edge Reversals
  • Vue Router的常用API有哪些?
  • 05 定时器,延时器、递归、内置对象(Object 对象+Math 对象+Date 对象+String对象)
  • Less (CSS 预处理器)
  • 8.18网络编程——基于UDP的TFTP文件传输客户端
  • 后端通用基础代码
  • 电源电路介绍
  • OpenTelemetry、Jaeger 与 Zipkin:分布式链路追踪方案对比与实践
  • 窗口看门狗(WWDG)
  • 网络基础——协议认识
  • Linux权限的学习
  • 抽象类与接口的区别
  • 【C语言篇】操作符详解
  • Ubuntu下无法在huggingface下载指定模型的解决方法
  • Read Frog:一款开源AI浏览器语言学习扩展
  • 如何解决IDEA/Datagrip无法连接数据库的问题:解决方法为添加参数-Djava.net.preferIPv4Stack=true
  • Java原子类详解
  • 并发编程原理与实战(二十四)Java并发基石LockSupport park/unpark机制全解析
  • 车e估牵头正式启动乘用车金融价值评估师编制
  • AI出题人给出的Java后端面经(十八)(日更)
  • Java基础八股复习3 jvm-内存结构
  • 数据仓库理论
  • 具身智能2硬件架构(人形机器人)摘自Openloong社区
  • Vue3 中使用 Element Plus 完整指南
  • 博客项目 Spring + Redis + Mysql
  • 利用DeepSeek辅助WPS电子表格ET格式分析
  • 代码随想录算法训练营四十五天|图论part03
  • flask——4:请求与响应
  • 机器学习(决策树)
  • pytest的前置与后置