015_Python 面向对象编程(OOP)学习笔记

一、核心概念

面向对象编程(OOP)是一种编程范式,核心思想是将程序抽象为 “对象” 的集合,每个对象包含属性(数据)和方法(行为)。Python 作为多范式语言,原生支持 OOP,且语法简洁灵活。

OOP 三大特性:

  • 封装:隐藏对象内部细节,仅暴露必要接口;
  • 继承:子类复用父类的属性和方法,实现代码复用;
  • 多态:不同对象对同一方法做出不同响应,提高代码灵活性。

二、类与对象的基础

1. 类的定义与实例化

类是对象的 “模板”,对象是类的 “实例”。

python

运行

# 定义类
class Person:
    # 类属性(所有实例共享)
    species = "Human"
    
    # 初始化方法(构造函数)
    def __init__(self, name, age):
        # 实例属性(每个实例独有)
        self.name = name
        self.age = age
    
    # 实例方法
    def introduce(self):
        return f"我是{self.name},今年{self.age}岁"

# 实例化对象
p1 = Person("Alice", 25)
p2 = Person("Bob", 30)

# 访问属性和方法
print(p1.name)          # Alice
print(p1.introduce())   # 我是Alice,今年25岁
print(Person.species)   # Human(类属性)

2. 封装:隐藏与访问控制

Python 通过命名约定和语法机制实现封装,核心是区分 “外部可访问” 和 “内部私有” 属性 / 方法:

(1)单下划线属性(_attr):约定俗成的 “受保护属性”

  • 语法规则:无特殊改写,仅通过命名约定标识;
  • 访问限制:无语法层面限制,外部可直接访问;
  • 设计意图:提示开发者 “这是内部属性,子类可继承 / 修改,外部尽量避免直接访问”。

python

运行

class Parent:
    def __init__(self):
        self._protected = "受保护属性"  # 单下划线:受保护

class Child(Parent):
    def __init__(self):
        super().__init__()
        self._protected = "子类覆盖受保护属性"  # 可直接覆盖

c = Child()
print(c._protected)  # 子类覆盖受保护属性(语法上允许访问)

(2)双下划线属性(__attr):语法级 “私有属性”(名称改写)

  • 语法规则:Python 自动将__attr改写为_类名__attr(名称改写机制);
  • 访问限制:直接访问__attr会报错,需通过改写后的名称访问(不推荐);
  • 设计意图:避免子类意外覆盖父类私有属性,确保类内部逻辑不被破坏。

python

运行

class Parent:
    def __init__(self):
        self.__private = "父类私有属性"  # 双下划线:私有,触发名称改写

class Child(Parent):
    def __init__(self):
        super().__init__()
        self.__private = "子类私有属性"  # 改写为_Child__private,不覆盖父类

c = Child()
# print(c.__private)  # 报错:AttributeError
print(c._Parent__private)  # 父类私有属性(改写后的名称,超级规操作)
print(c._Child__private)   # 子类私有属性(改写后的名称)
print(c.__dict__)  # {'_Parent__private': '父类私有属性', '_Child__private': '子类私有属性'}

(3)封装的实现方式

  • 基础方式:通过get_xxx()/set_xxx()方法控制属性访问,包含逻辑校验;
  • 优雅方式:使用property装饰器,将方法伪装为属性访问,兼顾控制与简洁。

python

运行

class Student:
    def __init__(self, name, score):
        self.name = name
        self.__score = score  # 私有属性
    
    # getter方法:控制读取逻辑
    def get_score(self):
        return self.__score
    
    # setter方法:控制写入逻辑(数据验证)
    def set_score(self, new_score):
        if 0 <= new_score <= 100:
            self.__score = new_score
        else:
            raise ValueError("分数无效")

# 或使用property装饰器(更优雅)
class Student:
    def __init__(self, name, score):
        self.name = name
        self.__score = score
    
    @property
    def score(self):  # 相当于getter,通过s.score访问
        return self.__score
    
    @score.setter
    def score(self, new_score):  # 相当于setter,通过s.score = ...赋值
        if 0 <= new_score <= 100:
            self.__score = new_score
        else:
            raise ValueError("分数无效")

s = Student("Alice", 85)
print(s.score)    # 85(看似属性访问,实则调用getter)
s.score = 90      # 调用setter,验证数据
# s.score = 101   # 报错:ValueError

三、继承:代码复用与扩展

1. 单继承

子类继承父类的属性和方法,可重写或扩展父类逻辑。

python

运行

class Teacher(Person):  # 继承Person类
    def __init__(self, name, age, subject):
        # 调用父类构造方法
        super().__init__(name, age)
        self.subject = subject
    
    # 重写父类方法
    def introduce(self):
        return f"我是{self.name},教{self.subject},今年{self.age}岁"

t = Teacher("David", 35, "数学")
print(t.introduce())  # 我是David,教数学,今年35岁

2. 多继承与 MRO

Python 支持多继承,通过MRO(方法解析顺序) 解决冲突:

  • 使用类名.mro()或类名.__mro__查看 MRO 顺序;
  • MRO 遵循 “子类优先、声明顺序优先、无重复” 原则(C3 线性化算法)。

python

运行

class A:
    def func(self): print("A.func")

class B(A):
    def func(self): print("B.func")

class C(A):
    def func(self): print("C.func")

class D(B, C):  # 多继承,先继承B,再继承C
    pass

print(D.mro())  # [D, B, C, A, object]
d = D()
d.func()        # B.func(按MRO顺序调用)

3.super()的使用

super()基于 MRO 动态调用父类方法,而非固定指向某一父类:

  • 单继承中:super()指向直接父类;
  • 多继承中:super()指向 MRO 中当前类的下一个类。

python

运行

class D(B, C):
    def func(self):
        super().func()  # 调用MRO中下一个类(B)的func
        super(B, self).func()  # 调用B之后的类(C)的func

d = D()
d.func()  # 输出:B.funcC.func

4. 属性查找机制

访问obj.attr时,Python 的查找顺序:

  1. 实例obj的命名空间(obj.__dict__);
  2. 实例所属类的命名空间(obj.__class__.__dict__);
  3. 按 MRO 顺序查找父类的命名空间。
  • 普通属性:重名时 “先找到的覆盖后找到的”;
  • 双下划线属性:因名称改写成为不同属性,避免覆盖,实现父子类私有属性共存。

四、多态:灵活的行为扩展

多态指 “同一方法在不同对象上表现不同行为”,Python 通过动态类型天然支持:

python

运行

class Cat:
    def make_sound(self):
        print("喵喵喵")

class Dog:
    def make_sound(self):
        print("汪汪汪")

# 统一接口
def animal_sound(animal):
    animal.make_sound()

animal_sound(Cat())  # 喵喵喵
animal_sound(Dog())  # 汪汪汪

五、魔术方法(特殊方法)

魔术方法以__开头和结尾,用于实现对象的特殊行为(如运算符重载、字符串表明等)。

方法

作用

示例

__init__

初始化对象

def __init__(self, x):

__str__

自定义对象的字符串表明

def __str__(self): return f”Obj({self.x})”

__repr__

自定义对象的官方字符串表明(调试用)

def __repr__(self): return f”Obj({self.x})”

__add__

重载+运算符

def __add__(self, other): return self.x + other.x

__iter__

让对象可迭代

def __iter__(self): return iter(self.data)

示例:

python

运行

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)
    
    def __str__(self):
        return f"Vector({self.x}, {self.y})"

v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2)  # Vector(4, 6)

六、抽象类与接口

1. 抽象类(abc 模块)

抽象类包含抽象方法(仅声明,无实现),不能直接实例化,子类必须实现抽象方法:

python

运行

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):  # 抽象方法
        pass

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius
    
    def area(self):  # 必须实现抽象方法
        return 3.14 * self.radius **2

# s = Shape()  # 报错:无法实例化抽象类
c = Circle(5)
print(c.area())  # 78.5

2. 接口:鸭子类型

Python 无显式接口,通过 “鸭子类型” 实现:对象只要具备指定方法 / 属性,即视为符合 “接口”。

python

运行

# 无需继承,只要有area方法即视为“Shape接口”
class Square:
    def __init__(self, side):
        self.side = side
    
    def area(self):
        return self.side** 2

def print_area(shape):
    print(f"面积:{shape.area()}")

print_area(Square(4))  # 面积:16

七、常见注意事项

  1. self的含义:self代表实例本身,是实例方法的第一个参数(名称约定俗成,可自定义,但不推荐);
  2. 类属性与实例属性:类属性属于类,所有实例共享;实例属性属于单个实例,互不影响;
  3. 避免多继承的 “菱形问题”:通过 MRO 和super()解决,优先使用组合而非多继承;
  4. 封装的本质:Python 无真正私有属性,__attr的名称改写是 “约定层面的私有”,而非 “语法强制禁止访问”;
  5. 动态特性:Python 支持动态给对象添加属性 / 方法,灵活但需谨慎(避免破坏封装);
  6. 名称改写的目的:不是 “禁止访问”,而是避免子类意外覆盖父类私有属性,确保类内部逻辑安全。

八、FAQ:初学者常见误解与疑问

1.self到底是什么?可以省略吗?

self是实例方法的第一个参数,指向当前实例对象本身,Python 通过self让实例能访问自己的属性和方法。

  • 实例方法必须显式声明self(或其他名称,如this,但约定用self),否则调用时会报错(参数数量不匹配);
  • 类方法用cls取代self,指向类本身;静态方法无需self/cls。

python

运行

class Test:
    def func(self):  # 必须有self
        print(self)

t = Test()
t.func()  # <__main__.Test object at 0x...>(打印实例本身)

2. 类属性和实例属性有什么区别?为什么修改类属性会影响所有实例?

  • 类属性:定义在类中、方法外,属于类,所有实例共享(存在类的命名空间里);
  • 实例属性:定义在__init__等方法中,以self.xxx声明,属于单个实例(存在实例的命名空间里)。

修改类属性会影响所有未单独赋值的实例,而实例属性仅影响自己:

python

运行

class Person:
    count = 0  # 类属性

p1 = Person()
p2 = Person()
Person.count = 10  # 修改类属性
print(p1.count, p2.count)  # 10 10(所有实例共享)

p1.count = 20  # 给p1添加实例属性count,覆盖类属性
print(p1.count, p2.count)  # 20 10(p2仍用类属性)

3.super()只能调用直接父类吗?多继承时怎么工作?

super()不是 “指向直接父类”,而是根据 MRO 顺序调用当前类的下一个类

多继承时,super()会严格按照类名.mro()的顺序找下一个类,而非仅直接父类:

python

运行

class A: pass
class B(A): pass
class C(A): pass
class D(B, C):
    def func(self):
        super().func()  # 按MRO[D,B,C,A],调用B的func;若B没有,调用C的

print(D.mro())  # [D,B,C,A,object]

4. 为什么__attr不是真正的私有?名称改写的目的是什么?

Python 的__attr只是 “名称改写”(变成_类名__attr),并非真正禁止访问 —— 这是 Python “信任开发者” 的设计哲学(We are all consenting adults)。

名称改写的核心目的是避免子类意外覆盖父类的私有属性,而非 “防黑客”:

python

运行

class Parent:
    def __init__(self):
        self.__x = 1  # 改写为_Parent__x

class Child(Parent):
    def __init__(self):
        super().__init__()
        self.__x = 2  # 改写为_Child__x(不会覆盖父类的_Parent__x)

c = Child()
print(c._Parent__x, c._Child__x)  # 1 2(父子类的__x共存)

5. 鸭子类型和抽象类有什么区别?该怎么选?

特性

鸭子类型

抽象类(abc 模块)

约束方式

运行时检查方法 / 属性是否存在(弱约束)

编译时强制子类实现抽象方法(强约束)

继承要求

无需继承任何类

必须继承ABC并实现抽象方法

适用场景

轻量级场景、快速开发、第三方对象适配

大型项目、团队协作、需要严格规范

选择原则:

  • 小项目 / 脚本:用鸭子类型(灵活、简洁);
  • 大型框架 / 团队协作:用抽象类(强制规范,避免遗漏方法)。

6. 多态在 Python 中为什么不需要像 Java 那样声明接口?

Python 是动态类型语言,变量无类型约束 —— 调用方法时只关心对象 “有没有这个方法”,而非 “是不是某个类型”(鸭子类型)。

因此 Python 无需显式接口,直接通过方法存在性实现多态,比静态语言更灵活。

7. 为什么要使用property装饰器?直接用get/set方法不行吗?

property的优势是让属性访问更自然(像普通属性一样用obj.attr,而非obj.get_attr()),同时保留get/set的逻辑控制:

python

运行

class Student:
    @property
    def score(self):
        return self.__score

s = Student()
s.score = 90  # 像普通属性赋值,实则调用setter;若没写setter,就是只读属性

直接用get/set方法也能实现,但语法繁琐(s.set_score(90)),property是更优雅的封装方式。

8. 什么是名称改写?由谁执行?如何触发?

名称改写(Name Mangling)是 Python 的一种语法机制:当类中定义以双下划线开头、且不以双下划线结尾的属性 / 方法时(如__attr),Python 解释器会自动将其重命名为_类名__属性名(如_ClassName__attr)。

  • 执行者:Python 解释器(编译 / 运行阶段自动处理);
  • 触发条件:属性 / 方法名以__开头且不以__结尾(注意:魔术方法如__init__不会触发,由于它以__结尾);
  • 目的:避免子类意外覆盖父类的私有属性 / 方法,确保类内部逻辑的独立性。

示例:

python

运行

class Test:
    def __init__(self):
        self.__x = 1  # 触发名称改写,变为_Test__x
        self.__y__ = 2  # 不触发(以__结尾,视为魔术方法)

t = Test()
print(t._Test__x)  # 1(改写后的名称可访问)
print(t.__y__)     # 2(未改写,直接访问)

9. 何为 MRO?具体规则是什么?

MRO(Method Resolution Order,方法解析顺序)是 Python 在多继承时,查找属性 / 方法的优先级顺序。它通过C3 线性化算法生成,核心规则如下:

  1. 子类优先于父类:子类的优先级高于任何父类;
  2. 声明顺序优先:多继承时,括号中先写的父类优先级更高;
  3. 深度优先调整:避免 “钻石继承”(子类继承两个父类,而两个父类继承同一基类)中的重复查找,确保基类最后被查找;
  4. 无重复、无环:最终的 MRO 列表中每个类仅出现一次,且无循环依赖。

可通过类名.mro()或类名.__mro__查看 MRO 顺序:

python

运行

class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass

print(D.mro())  # [D, B, C, A, object](符合子类优先、声明顺序优先)

C3 算法的核心目标是:保证继承的单调性(子类优先级始终高于父类)和局部优先级(声明顺序),避免多继承时的属性查找冲突。

九、总结

Python 的 OOP 兼具简洁性和灵活性:

  • 基础语法简单,易于上手;
  • 支持 OOP 三大特性,满足复杂项目需求;
  • 通过命名约定(_/__)和property实现封装,兼顾控制与易用性;
  • 结合动态类型和鸭子类型,无需严格的接口约束,适配快速开发场景;
  • 通过abc模块支持抽象类,兼顾大型项目的规范需求。
© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
早起吃蒸馒的头像 - 鹿快
评论 共1条

请登录后发表评论