面向对象编程(OOP)是Python核心编程思想,核心四大特性:封装、继承、多态、抽象(本文重点讲解考试/面试/实操高频的前三者)。
本文专为课堂演示设计,所有代码可直接复制运行,同时搭配错误案例,让学生通过「正确运行看效果、报错调试懂规则」彻底吃透OOP核心语法与底层逻辑。
本文核心知识点全覆盖:
-
属性分类:按归属(类属性、实例属性、静态属性)、按访问权限(公共、保护、私有)
-
方法分类:实例方法、类方法、静态方法、私有/保护/公共方法
-
核心语法:构造函数(__init__)、析构函数
-
三大特性:封装、继承、多态(全套正反例)
一、Python 类与对象基础
对象是「具体的事物」,类是「事物的模板」。所有OOP的属性、方法都依托类和对象实现。
1.1 最简类与对象示例(正例)
# 定义类
class Student:
pass
# 创建对象(实例化)
stu1 = Student()
stu2 = Student()
print(stu1)
print(stu2)
运行结果:输出两个不同的内存地址,代表两个独立对象。
核心结论:同一个类可以实例化出无数个独立对象。
二、属性全套详解(分类+正反例+报错解析)
属性是类/对象的变量,分为两大分类维度:按归属划分、按访问权限划分。
2.1 按归属划分:类属性、实例属性、静态属性
2.1.1 实例属性(属于对象,每个对象独有)
定义在构造函数 __init__ 中,通过 self.属性名 定义,每个对象的实例属性相互独立、互不干扰。
正例代码(正常运行)
class Student:
# 构造函数:初始化实例属性
def __init__(self, name, age):
self.name = name # 实例属性
self.age = age # 实例属性
# 创建两个独立对象
stu1 = Student("张三", 18)
stu2 = Student("李四", 20)
# 各自属性独立
print(stu1.name, stu1.age) # 张三 18
print(stu2.name, stu2.age) # 李四 20
# 修改单个对象实例属性,不影响其他对象
stu1.age = 19
print(stu1.age) # 19
print(stu2.age) # 20
反例代码(报错:对象无该实例属性)
class Student:
def __init__(self, name):
self.name = name
stu1 = Student("张三")
print(stu1.age) # 报错
报错信息:AttributeError: 'Student' object has no attribute 'age'
报错原因:age 未在构造函数中定义为实例属性,对象无法调用未初始化的实例属性。
2.1.2 类属性(属于类,所有对象共享)
定义在类内部、方法外部,不属于任何对象,所有实例对象共享同一份类属性,修改类属性会影响所有未重写该属性的对象。
正例代码(正常运行)
class Student:
# 类属性:全局共享
school = "光明小学"
def __init__(self, name):
self.name = name
# 类属性可以通过 类名/对象名 调用
print(Student.school) # 类名调用:光明小学
stu1 = Student("张三")
stu2 = Student("李四")
print(stu1.school) # 对象调用:光明小学
print(stu2.school) # 对象调用:光明小学
# 修改类属性,所有对象同步变更
Student.school = "阳光小学"
print(stu1.school) # 阳光小学
print(stu2.school) # 阳光小学
易错反例(坑点:对象赋值类属性,不会修改全局类属性)
Python 是有 动态实例属性 机制的!
class Student:
school = "光明小学"
def __init__(self, name):
self.name = name
stu1 = Student("张三")
stu1.school = "私立小学" # 看似修改类属性,实则给stu1新增实例属性
print(Student.school) # 光明小学(类属性未变)
print(stu1.school) # 私立小学(优先调用自己的实例属性)
print(stu2.school) # 光明小学(其他对象不受影响)
核心总结:对象直接赋值会创建实例属性覆盖类属性,不会修改全局类属性;修改类属性必须用 类名.属性名。
2.1.3 静态属性(本质是类属性,极少单独使用)
Python无严格意义的静态属性,日常将「全局共享、无需实例化的类变量」统称为静态属性,用法和类属性一致。
2.2 按访问权限划分:公共、保护、私有属性
Python无严格的权限修饰符,通过命名规范区分访问权限,核心靠下划线区分:
-
公共属性:无下划线 name,任意位置访问
-
保护属性:单下划线 _name,仅类内部、子类访问(约定规范)
-
私有属性:双下划线 __name,仅当前类内部访问,外部无法直接访问
2.2.1 公共属性(正例)
class Student:
def __init__(self):
self.name = "张三" # 公共属性
stu = Student()
print(stu.name) # 外部可直接访问:张三
2.2.2 保护属性(规范约束)
单下划线只是编程约定,外部可强制访问,但不推荐。
class Student:
def __init__(self):
self._age = 18 # 保护属性
stu = Student()
print(stu._age) # 可强制访问(语法不报错,违背编码规范)
2.2.3 私有属性(严格权限,正反例)
双下划线私有属性,Python会做名称改写,外部无法直接访问。
正例:类内部正常访问
class Student:
def __init__(self):
self.__score = 99 # 私有属性
def get_score(self):
# 类内部可以正常访问私有属性
return self.__score
stu = Student()
print(stu.get_score()) # 99
反例:外部直接访问(报错)
class Student:
def __init__(self):
self.__score = 99
stu = Student()
print(stu.__score) # 外部直接访问报错
报错信息:AttributeError: 'Student' object has no attribute '__score'
拓展知识点(进阶):私有属性被改写为 _类名__属性名,可强制访问(工作中禁止使用)
print(stu._Student__score) # 99(强制破解,不推荐)
类属性 同理。
三、方法全套详解(分类+正反例+报错解析)
方法是类/对象的函数,分类维度和属性完全对应:按归属(实例、类、静态方法)、按权限(公共、保护、私有方法)。
3.1 按归属划分三大核心方法
3.1.1 实例方法(最常用)
定义:第一个参数必须是 self,只能通过对象调用,可以操作实例属性、类属性。
正例
class Student:
school = "光明小学"
def __init__(self, name):
self.name = name
# 实例方法
def show_info(self):
print(f"姓名:{self.name},学校:{self.school}")
stu = Student("张三")
stu.show_info() # 姓名:张三,学校:光明小学
反例:类直接调用实例方法(报错)
Student.show_info() # 报错,缺少self参数
报错信息:TypeError: show_info() missing 1 required positional argument: 'self'
3.1.2 类方法(@classmethod)
装饰器 @classmethod 修饰,第一个参数必须是 cls(代表类本身),可通过类/对象调用,只能操作类属性,无法操作实例属性。
正例
class Student:
school = "光明小学"
@classmethod
def change_school(cls, new_school):
cls.school = new_school
# 类名调用
Student.change_school("育才小学")
print(Student.school) # 育才小学
# 对象调用
stu = Student()
stu.change_school("实验小学")
print(Student.school) # 实验小学
反例:类方法调用实例属性(报错)
class Student:
def __init__(self):
self.age = 18
@classmethod
def get_age(cls):
print(cls.age) # 试图调用实例属性
Student.get_age() # 报错
报错原因:类方法无实例对象,无法访问实例属性。
3.1.3 静态方法(@staticmethod)
装饰器 @staticmethod 修饰,无默认参数,和类、对象无绑定,仅作为 类的 工具方法,无法操作 类属性、实例属性。
正例
class Student:
@staticmethod
def calc_sum(a, b):
return a + b
# 类、对象均可调用
print(Student.calc_sum(1,2)) # 3
stu = Student()
print(stu.calc_sum(3,4)) # 7
3.2 按权限划分:公共/保护/私有方法
命名规则和属性完全一致:无下划线(公共)、单下划线(保护)、双下划线(私有)。
私有方法正反例
class Student:
# 私有方法
def __check_score(self, score):
return score >= 60
# 公共方法调用私有方法
def get_result(self, score):
return self.__check_score(score)
stu = Student()
print(stu.get_result(80)) # True(正常调用)
print(stu.__check_score(80)) # 外部直接调用报错
四、构造函数与析构函数(核心必考点)
4.1 构造函数 __init__
实例化对象时自动触发,用于初始化对象属性,无需手动调用。
正例:带参构造函数
class Student:
def __init__(self, name, age):
print("对象创建成功,自动执行构造函数")
self.name = name
self.age = age
stu = Student("张三", 18) # 实例化自动执行
print(stu.name)
反例:传参数量不匹配(报错)
stu = Student("张三") # 少传一个参数,报错
4.2 析构函数 __del__
对象被销毁时自动触发,用于释放资源。
class Student:
def __del__(self):
print("对象被销毁,执行析构函数")
stu = Student()
del stu # 主动删除对象,触发析构函数
五、面向对象三大特性(封装、继承、多态)正反例精讲
5.1 封装(OOP核心)
定义:隐藏类的内部细节,对外提供统一访问接口,保护核心数据安全。
核心用法:私有属性 + 公开get/set方法
正例:标准封装写法
class Student:
def __init__(self, score):
self.__score = score # 私有属性,隐藏数据
# 公开接口:获取分数
def get_score(self):
return self.__score
# 公开接口:修改分数(增加数据校验)
def set_score(self, new_score):
if 0 <= new_score <= 100:
self.__score = new_score
else:
print("分数不合法,修改失败")
stu = Student(80)
print(stu.get_score()) # 80
stu.set_score(95)
print(stu.get_score()) # 95
stu.set_score(200) # 分数不合法,修改失败
封装优势:杜绝外部随意篡改数据,可通过接口增加校验逻辑,保障数据合法性。
5.2 继承(代码复用核心)
定义:子类继承父类的所有公共/保护属性和方法,实现代码复用,子类可拓展新功能。
语法:class 子类名(父类名):
5.2.1 单继承正例
# 父类
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def show_person(self):
print(f"姓名:{self.name},年龄:{self.age}")
# 子类继承父类
class Student(Person):
# 子类拓展自己的属性
def __init__(self, name, age, school):
# 调用父类构造函数
super().__init__(name, age)
self.school = school
# 子类拓展新方法
def show_student(self):
print(f"学校:{self.school}")
stu = Student("张三", 18, "光明小学")
stu.show_person() # 继承父类方法:姓名:张三,年龄:18
stu.show_student() # 子类自有方法:学校:光明小学
5.2.2 继承反例:私有属性无法被继承
class Person:
def __init__(self):
self.__money = 1000 # 私有属性
class Student(Person):
def get_money(self):
print(self.__money) # 子类无法访问父类私有属性
stu = Student()
stu.get_money() # 报错
核心结论:父类私有属性、私有方法无法被子类继承,保护、公共属性/方法可正常继承。
5.3 多态(接口统一,实现不同)
首先说明 Python 语言层面没有 像Java/go 那种独立 interface 关键字。
定义:父类定义统一接口,子类 重写 父类 方法,同一个方法,不同子类实现不同效果。
多态前提:继承 + 方法重写
Python 实现 多态的 方式 主要有以下 3种, 约束性依次递减
5.3.1 抽象类 (推荐)
Python 真正的抽象类 必须借助 标准库 abc(Abstract Base Classes):
abc = Abstract Base Classes(抽象基类),属于 Python 自带标准库,随 Python 本体一起安装;
版本支持:Python 3.4 及以上完全稳定可用,课堂教学、生产环境通用;
- 继承 ABC
- 方法加装饰器 @abstractmethod
满足后:- 抽象类不能实例化;
- 子类不重写全部抽象方法,实例化子类直接报错。
标准抽象类完整正反例(课堂可直接运行)
from abc import ABC, abstractmethod
# 标准抽象类
class Animal(ABC):
@abstractmethod
def speak(self):
pass
# 反例1:直接实例化抽象类 → 报错
# a = Animal()
# TypeError: Can't instantiate abstract class Animal with abstract method speak
# 反例2:子类不重写抽象方法,实例化报错
class Dog(Animal):
pass
# d = Dog()
# TypeError: Can't instantiate abstract class Dog with abstract method speak
# 正例:子类必须重写所有抽象方法才能正常使用
class Dog(Animal):
def speak(self):
print("汪汪汪")
d = Dog()
d.speak() # 汪汪汪
5.3.2 普通类
写 pass 的空方法只是人为约定,没有语法强制;
# 父类
class Animal:
def speak(self):
pass
# 子类1:重写方法
class Dog(Animal):
def speak(self):
print("汪汪汪")
# 子类2:重写方法
class Cat(Animal):
def speak(self):
print("喵喵喵")
# 多态函数:统一接口,适配所有子类
def animal_speak(animal: Animal):
animal.speak()
# 不同对象调用同一个方法,效果不同
animal_speak(Dog()) # 汪汪汪
animal_speak(Cat()) # 喵喵喵
5.3.3 鸭子类型
Python原生特色,无任何约束
class Dog:
def speak(self):
print("汪")
class Cat:
def speak(self):
print("喵")
# 只要有speak方法就能传,不需要继承任何父类
def make_sound(animal):
animal.speak()
make_sound(Dog())
make_sound(Cat())
六、知识拓展
@dataclass
一、基础概念
@dataclass 是 Python 3.7+ 标准库 dataclasses 提供的类装饰器,专门简化只存数据的实体类,自动生成魔法方法:
额外可选生成:__lt__/__le__/__gt__/__ge__(大小比较)、__hash__(哈希)
二、直观对比:不用 dataclass vs 使用 dataclass
1. 传统手写数据类(冗余样板代码)
class Student:
def __init__(self, name: str, age: int, score: float):
self.name = name
self.age = age
self.score = score
def __repr__(self):
return f"Student(name={self.name!r}, age={self.age!r}, score={self.score!r})"
def __eq__(self, other):
if not isinstance(other, Student):
return False
return (self.name == other.name and
self.age == other.age and
self.score == other.score)
# 测试
s1 = Student("小明", 18, 92.5)
s2 = Student("小明", 18, 92.5)
print(s1) # Student(name='小明', age=18, score=92.5)
print(s1 == s2) # True
缺点:字段一多,重复写大量模板代码,极易漏写、写错。
2. @dataclass 简化版(完全等价上面逻辑)
from dataclasses import dataclass
@dataclass
class Student:
name: str
age: int
score: float
# 测试代码完全不变
s1 = Student("小明", 18, 92.5)
s2 = Student("小明", 18, 92.5)
print(s1)
print(s1 == s2)
输出完全一致,自动生成了 __init__ / __repr__ / __eq__。
三、类属性 还是 实例属性?
name: str、age: int、score: float 只是「字段声明标记」,本身 不是 可用的 类属性;
最终实例化后 存放在对象里,是 标准实例属性。
分两层拆解:声明阶段 + 运行阶段,搭配正反代码演示。
一、第一步:类定义阶段(写 name: str 时发生了什么)
from dataclasses import dataclass
@dataclass
class Student:
name: str
age: int
score: float
# 打印类注解,证明标注存在于类上
print(Student.__annotations__)
# {'name': <class 'str'>, 'age': <class 'int'>, 'score': <class 'float'>}
# 直接访问 Student.name 会报错!证明它不是真正的类属性
# print(Student.name)
# AttributeError: type object 'Student' has no attribute 'name'
# dataclass自动生成的__init__逻辑
def __init__(self, name: str, age: int, score: float):
self.name = name
self.age = age
self.score = score
- name: str 只是告诉装饰器「这个类有3个数据字段」;
- 类本身不存在 Student.name、Student.age 这类可读写的类属性;
- 只有实例化执行 self.name = name 后,属性才绑定到对象身上。
二、第二步:实例化后,name/age/score 是实例属性
s1 = Student("张三", 18, 90.5)
s2 = Student("李四", 20, 88.0)
# 1. 属性存在于单个对象,互不干扰(实例属性特征)
print(s1.name) # 张三
print(s2.name) # 李四
# 修改一个对象,不影响另一个
s1.age = 19
print(s1.age) # 19
print(s2.age) # 20
# 2. 类依旧拿不到 name
# print(Student.name) # 报错
# 3. 查看对象的属性字典,确认存在于实例 __dict__
print(s1.__dict__)
# {'name': '张三', 'age': 19, 'score': 90.5}
核心区分:真正的类属性 vs dataclass字段声明
1)普通手写类属性(真实存在于类)
class Student:
school = "一中" # 真实类属性,类可直接访问
name: str
print(Student.school) # 一中(正常读取类属性)
# print(Student.name) # 报错,注解不是类属性
2)dataclass字段和类属性共存对比演示
from dataclasses import dataclass
@dataclass
class Student:
school = "光明中学" # 真实类属性,所有对象共享
name: str # 仅字段标注,实例属性载体
age: int
s1 = Student("小明", 17)
s2 = Student("小红", 18)
# 类属性:改类,全部对象同步变
Student.school = "实验中学"
print(s1.school, s2.school) # 实验中学 实验中学
# 实例属性:只改当前对象,互不影响
s1.name = "大明"
print(s1.name, s2.name) # 大明 小红



