欢迎光临
我们一直在努力

还有同学说Python面向对象难,来给你好看

面向对象编程(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 提供的类装饰器,专门简化只存数据的实体类,自动生成魔法方法:

  • __init__:构造初始化方法
  • __repr__:友好打印对象字符串
  • __eq__:对象等值比较(==)
    额外可选生成:__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

  • name: str 是类型注解标注,存储在类的 __annotations__ 字典中,属于类的元数据:
  • # 打印类注解,证明标注存在于类上
    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 装饰器读取 __annotations__,提取字段名,自动生成 __init__,生成的初始化代码等价于:
  • # 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) # 大明 小红

    赞(0)
    未经允许不得转载:171主机测评 » 还有同学说Python面向对象难,来给你好看
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址