【Python入门】10.面向对象编程

Python 面向对象编程(OOP)系统梳理

面向对象编程(Object-Oriented Programming, OOP)是一种以“对象”为核心的编程思想,核心目标是提高代码的复用性、维护性、扩展性。与“按步骤执行”的面向过程编程(POP)不同,OOP 更贴合现实世界的逻辑(如“人”“车”“动物”等实体),通过封装、继承、多态三大特性实现代码的高效组织。

本文将从基础概念→核心特性→进阶用法→实践原则逐步梳理,每个知识点配套可运行的代码示例,帮助你彻底掌握 Python OOP。

一、OOP 核心思想与核心特性

1. 什么是 OOP?

OOP 把现实世界中的实体抽象为对象,每个对象包含:

  • 属性:对象的状态(如人的年龄、姓名,车的颜色、型号);
  • 方法:对象的行为(如人会吃饭、走路,车会行驶、刹车)。

类(Class)是对象的模板,定义了某一类对象的共同属性和方法;对象是类的实例化结果(比如“人类”是类,“张三”是具体的对象)。

2. OOP vs 面向过程(POP)

对比维度面向过程(POP)面向对象(OOP)
核心单元函数/步骤类/对象
编程思路按步骤拆解任务,一步一步执行抽象实体为对象,通过对象交互完成任务
代码复用函数调用,复用性低继承、组合,复用性高
维护难度代码耦合度高,修改困难模块化设计,修改灵活
适用场景简单脚本(如计算工具)复杂项目(如网站、APP)

示例对比:实现“小狗吃饭”功能

  • 面向过程:用函数分步实现
    def feed_dog(dog_name, food):
        print(f"{dog_name}{food}")
    
    # 调用函数(步骤化)
    feed_dog("旺财", "骨头")  # 输出:旺财 吃 骨头
    
  • 面向对象:抽象为Dog类和对象
    class Dog:
        def __init__(self, name):
            self.name = name  # 属性:名字
    
        def eat(self, food):  # 方法:吃饭
            print(f"{self.name}{food}")
    
    # 实例化对象,通过对象调用方法
    wangcai = Dog("旺财")
    wangcai.eat("骨头")  # 输出:旺财 吃 骨头
    

3. OOP 三大核心特性

  • 封装:隐藏对象内部细节,只暴露安全的访问接口(如隐藏属性,通过方法操作);
  • 继承:子类复用父类的属性和方法,同时可扩展自身功能;
  • 多态:同一接口(方法名)在不同对象上有不同实现,提高代码灵活性。

二、基础:类与对象

类是对象的“设计图”,对象是类的“具体产品”。这是 OOP 的最基础概念,必须先掌握。

1. 定义类:class关键字

语法:

class 类名(父类列表):  # 父类列表可选,默认继承 object(Python 3 中)
    # 类属性(所有对象共享)
    类属性名 =# 初始化方法(创建对象时自动调用,初始化实例属性)
    def __init__(self, 参数1, 参数2, ...):
        self.实例属性名1 = 参数1  # self 代表当前实例对象
        self.实例属性名2 = 参数2

    # 实例方法(需要通过对象调用,第一个参数必须是 self)
    def 实例方法名(self, 参数...):
        方法体

    # 类方法(通过类调用,第一个参数是 cls,代表类本身)
    @classmethod
    def 类方法名(cls, 参数...):
        方法体

    # 静态方法(无默认参数,与类和实例无关,仅逻辑上归属类)
    @staticmethod
    def 静态方法名(参数...):
        方法体

2. 关键概念解释

  • self:实例方法的第一个参数,指代当前实例对象(创建对象后,调用方法时无需手动传递self,Python 自动绑定);
  • 实例属性:每个对象独有的属性(如每个Dogname),通过self.属性名定义;
  • 类属性:所有对象共享的属性(如所有Dogspecies = "犬科"),直接在类体内定义;
  • 实例方法:依赖实例的方法(需要访问实例属性);
  • 类方法:依赖类的方法(访问类属性),用@classmethod装饰;
  • 静态方法:与类和实例均无关的工具方法,用@staticmethod装饰。

3. 示例:完整的类与对象使用

class Dog:
    # 类属性(所有狗共享)
    species = "犬科"

    # 初始化方法:创建对象时自动调用,初始化实例属性
    def __init__(self, name, age):
        self.name = name  # 实例属性:名字
        self.age = age    # 实例属性:年龄

    # 实例方法:需要访问实例属性(self.name)
    def bark(self):
        print(f"{self.name} 汪汪叫!")

    # 实例方法:带参数的实例方法
    def eat(self, food):
        print(f"{self.name} 吃了 {food}")

    # 类方法:访问类属性(cls.species)
    @classmethod
    def show_species(cls):
        print(f"所有狗都属于 {cls.species}")

    # 静态方法:无依赖,仅逻辑归属
    @staticmethod
    def is_adult(age):
        return age >= 1

# 1. 创建对象(实例化)
wangcai = Dog("旺财", 2)
xiaobai = Dog("小白", 0.5)

# 2. 访问实例属性和方法
print(wangcai.name)  # 输出:旺财
wangcai.bark()       # 输出:旺财 汪汪叫!
xiaobai.eat("狗粮")  # 输出:小白 吃了 狗粮

# 3. 访问类属性和类方法
print(Dog.species)       # 输出:犬科
Dog.show_species()       # 输出:所有狗都属于 犬科
print(wangcai.species)   # 输出:犬科(实例也可访问类属性)

# 4. 调用静态方法
print(Dog.is_adult(2))   # 输出:True
print(xiaobai.is_adult(0.5))  # 输出:False

三、核心特性一:封装(数据安全与隐藏)

1. 封装的定义

封装是指隐藏对象的内部属性和实现细节,只通过类提供的“接口”(方法)来访问和修改属性,避免外部直接操作导致数据混乱。

核心目的:

  • 数据安全:限制属性的合法范围(如年龄不能为负数);
  • 降低耦合:外部无需关心内部实现,只需调用接口。

2. Python 如何实现封装?

Python 没有严格的“私有属性”关键字(如 Java 的private),而是通过命名规范实现:

  • 私有属性/方法:以__属性名(双下划线)开头,Python 会自动对其进行“名称修饰”(改为_类名__属性名),外部无法直接访问;
  • 保护属性/方法:以_属性名(单下划线)开头,约定为“仅供内部使用”,外部可访问但不建议修改(语法上不限制)。

3. 访问私有属性的两种方式

方式 1:提供 getter/setter 方法(通用方式)
class Person:
    def __init__(self, name, age):
        self.name = name
        self.__age = age  # 私有属性:外部无法直接访问

    # getter 方法:获取私有属性
    def get_age(self):
        return self.__age

    # setter 方法:修改私有属性(可添加校验逻辑)
    def set_age(self, new_age):
        if new_age < 0 or new_age > 120:
            print("年龄不合法!")
            return
        self.__age = new_age

# 使用
p = Person("张三", 20)
print(p.name)  # 输出:张三
# print(p.__age)  # 报错:AttributeError(外部无法直接访问私有属性)

# 通过 getter 获取
print(p.get_age())  # 输出:20

# 通过 setter 修改(合法)
p.set_age(25)
print(p.get_age())  # 输出:25

# 非法修改被拦截
p.set_age(150)  # 输出:年龄不合法!
方式 2:property装饰器(Python 推荐方式)

property可以将方法伪装成属性,直接通过对象.属性名访问,更简洁优雅。

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

    # getter:用 @property 装饰,方法名即为属性名
    @property
    def age(self):
        return self.__age

    # setter:用 @属性名.setter 装饰,用于修改属性
    @age.setter
    def age(self, new_age):
        if new_age < 0 or new_age > 120:
            print("年龄不合法!")
            return
        self.__age = new_age

# 使用:直接像属性一样访问和修改
p = Person("张三", 20)
print(p.age)  # 输出:20(调用 getter 方法)
p.age = 25    # 调用 setter 方法
print(p.age)  # 输出:25
p.age = 150   # 输出:年龄不合法!

4. 封装的优势

  • 控制属性的访问权限,避免非法值;
  • 当内部实现修改时(如年龄校验逻辑变化),外部调用代码无需修改;
  • 隐藏复杂逻辑,外部只需关注“怎么用”,无需关注“怎么实现”。

四、核心特性二:继承(代码复用与扩展)

1. 继承的定义

继承是指子类(派生类)自动复用父类(基类)的属性和方法,同时子类可添加新属性/方法或重写父类方法,实现功能扩展。

核心目的:代码复用,避免重复编写相同逻辑。

2. 继承的语法

# 单继承:子类只继承一个父类
class 子类名(父类名):
    子类属性和方法

# 多继承:子类继承多个父类(Python 独有)
class 子类名(父类1, 父类2, ...):
    子类属性和方法

3. 单继承示例

# 父类:Animal(动物)
class Animal:
    def __init__(self, name):
        self.name = name

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

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

# 子类:Dog(狗)继承 Animal
class Dog(Animal):
    # 子类添加新方法
    def bark(self):
        print(f"{self.name} 汪汪叫")

# 子类:Cat(猫)继承 Animal
class Cat(Animal):
    # 重写父类方法(覆盖父类逻辑)
    def eat(self):
        print(f"{self.name} 在吃小鱼干")  # 猫的吃饭逻辑与父类不同

# 使用子类
dog = Dog("旺财")
dog.eat()   # 复用父类方法:输出 旺财 在吃东西
dog.sleep() # 复用父类方法:输出 旺财 在睡觉
dog.bark()  # 子类新方法:输出 旺财 汪汪叫

cat = Cat("小白")
cat.eat()   # 调用重写后的方法:输出 小白 在吃小鱼干
cat.sleep() # 复用父类方法:输出 小白 在睡觉

4. 多继承与 MRO(方法解析顺序)

Python 支持多继承,但可能出现“方法冲突”(多个父类有同名方法),此时需遵循MRO(Method Resolution Order) 规则:

  • MRO 是子类查找父类方法的顺序,通过子类名.__mro__子类名.mro()查看;
  • 默认按“广度优先”原则(Python 3 特性),避免钻石继承(菱形继承)的歧义。
多继承示例
# 父类1
class A:
    def show(self):
        print("A 的 show 方法")

# 父类2
class B(A):
    def show(self):
        print("B 的 show 方法")

# 父类3
class C(A):
    def show(self):
        print("C 的 show 方法")

# 子类:继承 B 和 C
class D(B, C):
    pass

# 查看 MRO
print(D.__mro__)  # 输出:(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

# 调用 show 方法:按 MRO 顺序查找,先找到 B 的 show
d = D()
d.show()  # 输出:B 的 show 方法

5. super()函数:调用父类方法

子类重写父类方法后,若需保留父类逻辑,可通过super()函数调用父类方法。

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

class Dog(Animal):
    def __init__(self, name, breed):
        # 调用父类的 __init__ 方法,初始化 name 属性
        super().__init__(name)
        self.breed = breed  # 子类新增属性:品种

    def eat(self):
        # 先执行父类的 eat 逻辑,再添加子类逻辑
        super().eat()
        print(f"{self.name}{self.breed})吃得很香")

dog = Dog("旺财", "中华田园犬")
dog.eat()
# 输出:
# 旺财 在吃东西
# 旺财(中华田园犬)吃得很香

6. 继承的注意事项

  • 避免多层继承(建议不超过 3 层),否则代码可读性变差;
  • 多继承尽量简单,避免复杂的钻石继承(A→B、A→C、D→B/C);
  • 子类应遵循“里氏替换原则”:子类可替换父类,且不影响程序功能。

五、核心特性三:多态(灵活调用与接口统一)

1. 多态的定义

多态是指同一方法名在不同对象上有不同实现,调用时无需关心对象类型,只需统一调用接口,即可触发对应对象的逻辑。

核心目的:接口统一,代码灵活,降低不同类之间的耦合。

2. Python 的动态多态

与 Java/C# 不同,Python 是动态类型语言,无需显式声明接口或继承抽象类,多态的实现完全依赖“方法重写”和“鸭子类型”。

多态示例:统一接口调用
# 父类(可选,无父类也可实现多态)
class Animal:
    def make_sound(self):
        pass  # 空实现,子类重写

# 子类1:Dog
class Dog(Animal):
    def make_sound(self):
        print("汪汪汪")

# 子类2:Cat
class Cat(Animal):
    def make_sound(self):
        print("喵喵喵")

# 子类3:Duck(无需继承 Animal,只要有 make_sound 方法即可)
class Duck:
    def make_sound(self):
        print("嘎嘎嘎")

# 统一接口:接收任意有 make_sound 方法的对象
def animal_sound(animal):
    animal.make_sound()

# 调用:不同对象传入同一函数,触发不同行为
dog = Dog()
cat = Cat()
duck = Duck()

animal_sound(dog)  # 输出:汪汪汪
animal_sound(cat)  # 输出:喵喵喵
animal_sound(duck) # 输出:嘎嘎嘎

3. 鸭子类型(Duck Typing)

Python 多态的核心是“鸭子类型”:不关注对象的类型,只关注对象的行为

通俗理解:“如果一个东西走路像鸭子、叫起来像鸭子,那它就是鸭子”。

在上面的示例中,Duck类没有继承Animal,但因为它有make_sound方法,所以可以被animal_sound函数接收,这就是鸭子类型的体现。

4. 多态的应用场景

  • 统一接口调用:如上面的animal_sound函数,无需为每个类写单独的调用逻辑;
  • 扩展新功能:新增类(如Chicken)时,无需修改原有animal_sound函数,直接传入即可;
  • 框架设计:如 Python 内置函数len(),可接收字符串、列表、元组等,本质是多态(这些类型都实现了__len__方法)。

六、进阶特性:让类更强大

1. 魔法方法(特殊方法)

Python 中以__xxx__(双下划线)命名的方法称为“魔法方法”,它们是类的内置方法,在特定场景下自动调用,可自定义类的行为。

常用魔法方法示例
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # 1. __str__:对象的“友好字符串表示”(print 时调用)
    def __str__(self):
        return f"Person(name='{self.name}', age={self.age})"

    # 2. __repr__:对象的“官方字符串表示”(交互式环境/调试时调用)
    def __repr__(self):
        return f"Person('{self.name}', {self.age})"

    # 3. __eq__:自定义对象的相等判断(== 时调用)
    def __eq__(self, other):
        # 只有当 other 是 Person 实例,且 name 和 age 都相同时,才认为相等
        return isinstance(other, Person) and self.name == other.name and self.age == other.age

    # 4. __len__:自定义对象的长度(len() 时调用)
    def __len__(self):
        return len(self.name)  # 这里定义“长度”为姓名的长度

    # 5. __add__:自定义对象的加法(+ 时调用)
    def __add__(self, other):
        if isinstance(other, Person):
            return Person(f"{self.name}&{other.name}", (self.age + other.age) // 2)
        return NotImplemented

# 使用魔法方法
p1 = Person("张三", 20)
p2 = Person("张三", 20)
p3 = Person("李四", 25)

print(p1)          # 调用 __str__:输出 Person(name='张三', age=20)
print(repr(p1))    # 调用 __repr__:输出 Person('张三', 20)
print(p1 == p2)    # 调用 __eq__:输出 True
print(p1 == p3)    # 输出 False
print(len(p1))     # 调用 __len__:输出 2(“张三”长度为 2)
p4 = p1 + p3       # 调用 __add__:输出 Person(name='张三&李四', age=22)
print(p4)
其他常用魔法方法
  • __del__:对象被销毁时调用(析构函数);
  • __getitem__:支持对象[key]访问(如字典、列表);
  • __setitem__:支持对象[key] = value赋值;
  • __delitem__:支持del 对象[key]删除。

2. 抽象类与接口(abc模块)

抽象类是包含抽象方法的类,抽象方法只有声明(无实现),子类必须重写该方法才能实例化。抽象类的核心作用是规范子类的接口

Python 中通过abc模块实现抽象类:

from abc import ABCMeta, abstractmethod

# 抽象类:继承 ABCMeta(Python 3.4+ 也可直接继承 abc.ABC)
class Shape(metaclass=ABCMeta):
    # 抽象方法:用 @abstractmethod 装饰,无实现
    @abstractmethod
    def area(self):
        pass

    @abstractmethod
    def perimeter(self):
        pass

# 子类1:Circle(圆),必须实现 area 和 perimeter
class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14 * self.radius **2

    def perimeter(self):
        return 2 * 3.14 * self.radius

# 子类2:Rectangle(矩形),必须实现 area 和 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)

# 使用
circle = Circle(5)
print(circle.area())      # 输出:78.5
print(circle.perimeter()) # 输出:31.4

rect = Rectangle(3, 4)
print(rect.area())        # 输出:12
print(rect.perimeter())   # 输出:14

# 错误:抽象类不能实例化
# shape = Shape()  # 报错:TypeError: Can't instantiate abstract class Shape with abstract methods area, perimeter

3. 组合与聚合(“has-a”关系)

当两个类不是“is-a”(如 Dog is a Animal)的继承关系,而是“has-a”(如 Car has a Engine)的包含关系时,应使用组合聚合,而非继承。

组合 vs 聚合
  • 组合:部分(如 Engine)依赖整体(如 Car),整体销毁时部分也销毁(发动机不能脱离汽车独立存在);
  • 聚合:部分(如 Student)独立于整体(如 Class),整体销毁时部分仍可存在(学生离开班级后依然是学生)。
组合示例:Car 包含 Engine
# 部分类:Engine(发动机)
class Engine:
    def start(self):
        print("发动机启动")

    def stop(self):
        print("发动机关闭")

# 整体类:Car(汽车),组合 Engine
class Car:
    def __init__(self, brand):
        self.brand = brand
        self.engine = Engine()  # 组合:Car 创建时自动创建 Engine,依赖 Car 存在

    def start(self):
        print(f"{self.brand} 汽车启动:")
        self.engine.start()

    def stop(self):
        print(f"{self.brand} 汽车关闭:")
        self.engine.stop()

# 使用
car = Car("特斯拉")
car.start()
# 输出:
# 特斯拉 汽车启动:
# 发动机启动
car.stop()
# 输出:
# 特斯拉 汽车关闭:
# 发动机关闭
组合 vs 继承的选择原则
  • 若为“is-a”关系:用继承(如 Dog is a Animal);
  • 若为“has-a”关系:用组合(如 Car has a Engine);
  • 优先使用组合:组合比继承更灵活,降低类之间的耦合(继承是强耦合,组合是弱耦合)。

七、OOP 设计原则(简要)

掌握以下原则,能写出更优雅、可维护的 OOP 代码:

  1. 单一职责原则:一个类只负责一件事(如Person类只管理个人信息,不负责日志打印);
  2. 开放封闭原则:对扩展开放(新增功能通过新增类/方法),对修改关闭(不修改原有代码);
  3. 里氏替换原则:子类可替换父类,且不破坏程序逻辑(如Dog可替换Animal的所有场景);
  4. 依赖倒置原则:依赖抽象(如Shape抽象类),不依赖具体实现(如Circle)。

八、总结与实践建议

1. OOP 核心优势

  • 代码复用:继承、组合减少重复代码;
  • 维护性高:封装隐藏细节,修改时只影响类内部;
  • 扩展性强:新增功能只需新增子类或组合类;
  • 可读性好:类和对象贴合现实逻辑,代码结构清晰。

2. 学习实践建议

  1. 基础练习:实现简单类(如StudentBook),练习属性、方法、封装;
  2. 进阶练习:实现小型项目(如学生管理系统、图书管理系统),综合运用继承、多态、组合;
  3. 阅读源码:查看 Python 内置类(如listdict)的实现,理解魔法方法和封装思想;
  4. 避坑指南
    • 区分实例属性和类属性(避免修改类属性影响所有实例);
    • 多继承尽量简单,优先用组合替代;
    • 私有属性通过property或 getter/setter 访问,不直接操作。

通过以上内容,你已掌握 Python OOP 的核心知识。OOP 的关键不在于记住语法,而在于学会用“对象”的思维抽象问题,多实践、多思考,才能真正灵活运用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值