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

Python应用进阶DAY7--面向对象编程基本特性和super函数

前面相关文章:

Python核心基础DAY1--Python的基础变量类型之字符串和数字类型

Python核心基础DAY2--复合类型之序列类型、映射类型和集合类型

Python核心基础DAY3--Python表达式及常见语句

Python核心基础DAY4--Python函数和内置函数总结

Python应用进阶DAY5--Python装饰器函数总结

Python应用进阶DAY6--类和对象的基本概念及属性和方法的常见分类和使用场景


目录

面向对象编程

一、OOP基本特性

(一)封装

私有属性(__ 前缀)

 属性装饰器(@property)

(二)继承/派生

 继承的实现

方法覆盖(Override) 

【练习】 

​编辑多态

方法重写

二、super函数

super()的基本使用

super().__init__()

方法解析顺序(MRO)

五、总结 


面向对象编程

一、OOP基本特性

OOP的四大基本特性是封装继承多态抽象

(一)封装

隐藏实现细节:将对象的属性和方法包装在类内部,外部无需关心具体实现。

提供公共接口:通过方法(如 getter/setter)控制对属性的访问,增强安全性和可维护性。

API 是封装的"对外窗口"—— 它隐藏了复杂的内部细节,只留下安全、易用、稳定的交互方式,是面向对象编程中实现"高内聚、低耦合"的手段:

  1. 封装是将对象的属性和方法包装一起,对外隐藏实现细节,只提供必要的接口给外部访问。
  2. 在Python中,通过__init__方法初始化属性,并通过方法来操作这些属性。
私有属性(__ 前缀)

在 Python 中,使用双下划线 __ 开头的属性或方法被视为私有成员,外部无法直接访问:

class Dog:def __init__(self, name, age):self.__name = name  # 私有属性self.age = age      # 公有属性def get_name(self):     # 公有方法:访问私有属性return self.__name
dog = Dog('泰迪', 20)
print(dog.get_name())       # 正确:通过公有方法访问
print(dog.__name)           # 错误:无法直接访问私有属性

伪私有机制:Python 的私有属性并非真正不可访问,实际上会被解释器重命名为 _类名__属性名(如 _Dog__name),但强烈不建议直接使用这种方式访问。

单下划线 _:约定俗成的"保护属性",表示建议外部不要直接访问,但解释器不会强制限制。

 属性装饰器(@property)

属性装饰器用于将类的方法转换为只读属性,使得方法可以像属性一样访问,而不需用 () 调用:

【基本用法(使用 @property 使方法变为只读属性)】 

class Person:def __init__(self, name):self._name = name  # 单下划线:约定为保护属性**@property**def name(self):        # 方法变为属性return self._name
p = Person("Alice")
print(p.name)              # 无需括号,像属性一样访问
# p.name = "Bob"           # 报错:只读属性不可修改

如果希望属性可以修改,需要使用 @property 结合 @name.setter:

【可读可写(通过 @属性名.setter 装饰器添加写权限)】 

class Person:def __init__(self, name):self._name = name@propertydef name(self):return self._name**@name.setter**def name(self, new_name):  # 方法名必须与@property装饰的方法名一致if isinstance(new_name, str) and new_name.strip():self._name = new_nameelse:raise ValueError("名字必须是非空字符串")
p = Person("Alice")
p.name = "Bob"              # 可修改,会触发setter方法

如果希望属性支持删除,可使用 @name.deleter:

【可读可删(通过 @属性名.deleter 装饰器添加删除权限)】 

class Person:def __init__(self, name):self._name = name@property     # 使用 @property 装饰器定义name属性的getter方法def name(self):# 当访问 p.name 时会自动调用这个方法return self._name    **@name.deleter**# 使用 @属性名.deleter 装饰器定义 name 属性的 deleter 方法def name(self):          # 删除属性时触发# 当执行 del p.name 时会调用这个方法print("删除 name 属性")del self._name       # 真正删除底层的 _name 属性
p = Person("Alice")
# 使用 del 语句删除p对象的name属性,这会触发@name.deleter定义的方法
del p.name       # 触发 deleter 方法,输出提示信息并删除 _name 属性

 @property 适用于需要动态计算的属性:

【计算属性:用于动态计算的属性,无需存储在实例中】

class Circle:def __init__(self, radius):self._radius = radius**@property**def area(self):          # 每次访问时计算return 3.14 * self._radius ** 2
c = Circle(10)
print(c.area)              # 输出:314.0(无需括号)

子类可以重写父类的 @property 方法,实现自定义逻辑:

【@property 在继承中的应用】 

class Animal:def __init__(self, name):self._name = name**@property**def name(self):return f"Animal: {self._name}"
class Dog(Animal):**@property**def name(self):          # 重写父类的propertyreturn f"Dog: {self._name}"
dog = Dog("Buddy")
print(dog.name)            # 输出:Dog: Buddy
场景实现方式示例
只读属性@propertyp.name(无括号)
可读可写属性@property + @setterp.name = "Bob"
可读可删属性@property + @deleterdel p.name
计算属性@property(动态计算)c.area(每次计算)
私有属性保护__ 前缀 + 公共方法self.__name 通过get_name()访问
  1. 不要滥用 @property,仅在需要计算或验证时使用。
  2. 避免在 @property 方法中执行耗时操作,因为它们会在每次访问时调用。

(二)继承/派生

Python中所有的类最终都继承自内置的object

继承/派生

  1. 继承是从已有的类中派生出新的类,新类具有原类的数据属性和行为,并能扩展新的能力。
  2. 派生类就是从一个已有类中衍生出新类,在新的类上可以添加新的属性和行为

继承/派生的作用

  1. 用继承派生机制,可以将一些共有功能加在基类中。实现代码的共享。
  2. 在不改变基类的代码的基础上改变原有类的功能

继承/派生名词

  1. 基类(base class)/超类(super class)/父类(father class)
  2. 派生类(derived class)/子类(child class)
 继承的实现

单继承语法

class DerivedClass(BaseClass):  # 单继承:从一个基类派生# 子类的属性和方法

【多继承语法】 

class DerivedClass(Base1, Base2, ...):  # 多继承:从多个基类派生# 子类的属性和方法
class Animal:  # 基类def speak(self):print("Animal is speaking")
class Dog(Animal):  # 子类,继承自Animaldef speak(self):  # 覆盖基类方法print("Woof!")
class Cat(Animal):  # 另一个子类def speak(self):  # 覆盖基类方法print("Meow!")
dog = Dog()
dog.speak()  # 输出:Woof!(调用子类方法)
cat = Cat()
cat.speak()  # 输出:Meow!(调用子类方法)
方法覆盖(Override) 

实现和父类同名,但功能不同的方法

  1. 在子类中实现与基类同名的方法,称为方法覆盖
  2. 覆盖后,子类实例调用该方法时,优先执行子类的实现。
class A:  # 基类def work(self):print("A.work 被调用!")
class B(A):  # 子类,继承自Adef work(self):  # 覆盖基类的work方法print("B.work 被调用!!!")
b = B()
b.work()  # 输出:B.work 被调用!!!(优先执行子类方法)
a = A()
a.work()  # 输出:A.work 被调用!(基类实例调用基类方法)
【练习】 

class Bicycle:def run(self, km):print(f"自行车骑行了 {km} 公里")
class EBicycle(Bicycle):def __init__(self, volume=0):  # 初始化电量,默认0self.volume = volumedef fill_charge(self, vol):  # 充电方法self.volume += volprint(f"已充电 {vol} 度,当前电量: {self.volume} 度")def run(self, km):  # 覆盖父类的run方法# 计算电池可行驶的公里数(每10km消耗1度电)e_km = min(km, self.volume * 10)if e_km > 0:self.volume -= e_km / 10  # 消耗电量print(f"电动车使用电池骑行了 {e_km} 公里,剩余电量: {self.volume} 度") # 计算剩余需要用脚蹬的公里数if km > e_km:foot_km = km - e_kmsuper().run(foot_km)  # 调用父类的run方法def fill_charge(self, vol):print("电动自行车充电", vol, "度")self.volume += vol
b = EBicycle(5)  # 新买的电动车内有5度电
b.run(10)  # 电动骑行了10km 还剩 4度电
b.run(100)  # 电动骑行了 40 km ,还剩 0 度电, 用脚登骑行了60km
b.fill_charge(10)  # 电动自行车充电 10 度
b.run(50)  # 骑行了50公里剩余 5度电
多态
  1. 核心思想:同一接口,多种实现。即同一个方法在不同对象上表现出不同的行为
  2. 通过多态,可以使得不同类型的对象以相同的接口表现出不同的行为。
  3. 多态的实现通常通过继承和方法重写来实现。

【基于继承的多态】

class Animal:  # 基类def speak(self):print("Animal is speaking")
class Dog(Animal):  # 子类,继承自Animaldef speak(self):  # 重写父类的speak方法print("Woof!")
class Cat(Animal):  # 另一个子类def speak(self):  # 重写父类的speak方法print("Meow!")
def animal_speak(animal):  # 统一接口函数animal.speak()  # 调用对象的speak方法,具体实现由对象类型决定
# 创建实例
dog = Dog()
cat = Cat()
# 同一接口,不同行为
animal_speak(dog)  # 输出:Woof!
animal_speak(cat)  # 输出:Meow!

animal_speak函数接收一个animal参数,不关心具体类型,只要对象有speak方法即可调用

动态绑定:Python 在运行时根据对象的实际类型决定调用哪个方法,而非编译时

【鸭子类型示例】

class Car:def speak(self):  # 与Animal类无关,但有相同方法名print("Vroom!")
def animal_speak(animal):  # 同一接口函数animal.speak()
car = Car()
animal_speak(car)  # 输出:Vroom!(无需继承,只要有speak方法即可)
方法重写

在不改变父类代码的前提下,让子类拥有不同的行为,实现多态性 

  1. 如果父类方法的功能不能满足需求,可以在子类重写父类的方法
  2. 方法签名必须相同:方法名、参数列表、返回值类型需与父类一致。
  3. 访问权限不能更严格:子类方法的访问权限需大于等于父类。

对象转字符串重写 

str 方法

def __str__(self)
**作用:**定义对象的 “非正式” 字符串表示,用于 print() 和 str()。
**默认行为:**若未定义 __str__,则返回repr(obj)函数结果代替

【repr 方法】 

**作用:**定义对象的 “官方” 字符串表示,用于调试和 repr()。
**要求:**返回的字符串应尽可能接近创建对象的代码。
class MyNumber:def __init__(self, value):self.data = valuedef __str__(self):return "%s" % self.data  # 返回数据的字符串形式
n1 = MyNumber("一只猫")
print(str(n1))  # 输出: 一只猫

内建函数重写 

内建函数魔术方法作用
abs(obj)__abs__返回对象的绝对值
len(obj)__len__返回对象的长度
reversed(obj)__reversed__返回反转后的迭代器
round(obj)__round__返回四舍五入的值
class MyList:def __init__(self, iterable=()):self.data = list(iterable)def __len__(self):print("__len__ 被调用")return len(self.data)  # 返回内部列表的长度def __abs__(self):return MyList([abs(x) for x in self.data])  # 返回元素绝对值的新列表
myl = MyList([1, -2, 3, -4])
print(len(myl))  # 输出: __len__ 被调用 \n 4
print(abs(myl).data)  # 输出: [1, 2, 3, 4]

运算符重载 — 让自定义类的对象支持+,-,* 等运算符

运算符重载方法的参数已经有固定的含义,不建议改变原有的意义

方法名运算符和表达式说明
__add__(self, rhs)self + rhs加法
__sub__(self, rhs)self - rhs减法
__mul__(self, rhs)self * rhs乘法
__truediv__(self, rhs)self / rhs除法
__floordiv__(self, rhs)self // rhs地板除
__mod__(self, rhs)self % rhs取模(求余)
__pow__(self, rhs)self ** rhs

【二元运算符重载方法格式】 

def __xxx__(self, other):....

【算术运算符重载示例】 

class MyNumber:"""此类用于定义一个自定义的类,用于演示运算符重载"""# 构造函数 __init__:用于初始化 MyNumber 类的实例def __init__(self, value):# 将传入的值赋给实例属性 self.data,表示该对象存储的数据self.data = value# 定义 __str__ 方法:用于返回对象的字符串表示,便于打印def __str__(self):# 返回格式为 "MyNumber(值)" 的字符串return "MyNumber(%d)" % self.data# 定义 __add__ 方法:实现加法运算符重载def __add__(self, rhs):"""加号运算符重载"""    # 当使用 + 运算符时,此方法会被调用print("__add__ is called")  # 提示信息,表示调用了加法方法# 创建一个新的 MyNumber 对象,其值为两个对象的 data 相加return MyNumber(self.data + rhs.data)# 定义 __sub__ 方法:实现减法运算符重载def __sub__(self, rhs):"""减号运算符重载"""    # 当使用 - 运算符时,此方法会被调用print("__sub__ is called")  # 提示信息,表示调用了减法方法# 创建一个新的 MyNumber 对象,其值为两个对象的 data 相减return MyNumber(self.data - rhs.data)
# 创建两个 MyNumber 实例对象 n1 和 n2,分别传入值 100 和 200
n1 = MyNumber(100)
n2 = MyNumber(200)
print(n1 + n2)  # 用+将n1和n2相加,自动调用__add__方法,返回一个新的MyNumber对象
n = n1 - n2  # 用-将n1和n2相减,会自动调用__sub__方法,返回一个新的MyNumber对象赋给n
print(n.data)  # 打印新对象n的data属性(即 100 - 200 = -100)


二、super函数

super() 函数是用于调用父类(超类)的一个方法

  1. 无参数形式:在子类方法中使用 super().add()super().add()super().add() 调用父类中已被覆盖的方法
  2. 带参数形式:使用 super(Child,obj).myMethod()super(Child, obj).myMethod()super(Child,obj).myMethod() 用于子类对象调用父类已被覆盖的方法
super()的基本使用

无参数形式调用父类被覆盖的方法

class A:def add(self, x):print(f"A.add: {x + 1}")
class B(A):def add(self, x):print("B.add: 子类方法")super().add(x)  # 调用父类的add方法
b = B()
b.add(2)  

【带参数形式】 

class Parent:  # 定义父类def myMethod(self):print('调用父类方法')
class Child(Parent):        # 定义子类def myMethod(self):print('调用子类方法')
c = Child()                 # 子类实例
c.myMethod()                # 子类调用重写方法
super(Child, c).myMethod()  # 用子类对象调用父类已被覆盖的方法
super().__init__()

通过super().init()调用父类构造函数,以确保父类的构造函数被正确调用和初始化。

  1. 避免代码重复:父类的初始化逻辑只需写一次。
  2. 多继承安全:确保所有父类的初始化方法按 MRO 顺序被调用。
  3. 正确初始化:确保父类的初始化逻辑(如设置属性、分配资源等)被执行。

【初始化父类属性】 

class Parent:def __init__(self):print("Parent 初始化")self.parent_attr = "父类属性"
class Child(Parent):def __init__(self):super().__init__()  # 调用父类的初始化方法print("Child 初始化")self.child_attr = "子类属性"
child = Child()
print(child.parent_attr)  # 输出: 父类属性

方法解析顺序(MRO)

MRO(Method Resolution Order)方法解析顺序,决定了当一个类继承自多个父类时,如何确定方法和属性的查找顺序。

MRO 的计算规则 — Python 使用 C3 线性化算法计算 MRO。C3 线性化算法确保了以下几点:

  1. 子类优先于父类:子类的方法或属性会优先于父类被查找。
  2. 父类的顺序保持不变:在定义类时,父类的顺序会影响 MRO。在多重继承中,按照继承列表的顺序从左到右查找。
  3. 单调性:如一个类出现在另一个类的MRO中,那么它的父类也应出现在这个类的MRO中

MRO 的计算方法 

  1. MRO 的计算方法可以通过 __mro__ 属性或 mro() 方法来查看 
class A:def method(self):print("A.method")
class B(A):def method(self):print("B.method")
class C(A):def method(self):print("C.method")
class D(B, C):pass
# 查看 D 类的 MRO
print(D.__mro__)

 D的MRO顺序是 D -> B -> C -> A -> object,当调用D类的方法时,Python 会按照这个顺序查找方法。 

class A:def method(self):print("A.method")
class B(A):def method(self):print("B.method")
class C(A):def method(self):print("C.method")
class D(B, C):def method(self):print("D.method")
class E(C, B):def method(self):print("E.method")
class F(D, E):pass
# 查看 F 类的 MRO
print(F.__mro__)

 

问题:D的MRO顺序是 D -> B -> C -> A -> object,而E的MRO顺序是 E -> C -> B -> A -> object。这导致F无法创建一致的 MRO,因为B和C的顺序在D和E中不一致。

在这种情况下,Python无法创建一个一致的方法解析顺序(MRO),解决办法:类D和E继承B、C的顺序一致,即: 

class D(B, C):def method(self):print("D.method")
class E(B, C):def method(self):print("E.method")

五、总结 

本文系统讲解了Python面向对象编程(OOP)的四大特性。封装部分详述了私有属性机制、属性装饰器@property的使用场景;继承部分涵盖单继承、方法覆盖、super函数调用和多继承的MRO规则;多态部分通过方法重写和运算符重载实现;最后介绍了抽象特性。全文通过丰富的代码示例演示了OOP核心概念在Python中的具体实现方式,包括属性控制、方法重写、继承链调用等关键技术要点,帮助读者掌握Python面向对象编程的核心思想和实践方法。

http://www.lryc.cn/news/591063.html

相关文章:

  • 电子电路中的电压符号命名约定
  • FreeSWITCH配置文件解析(6) mod_format_cdr 话单中字段解析
  • 浅谈自动化设计最常用的三款软件catia,eplan,autocad
  • 云服务器如何设置防火墙和安全组规则?
  • Linux内核网络栈深度剖析:inet_connection_sock.c的服务器端套接字管理
  • 【算法训练营Day13】二叉树part3
  • 华为P30/pro (ELE-AL00) 鸿蒙4.2降级 EMUI 9
  • 服务器数据恢复—raid5磁盘阵列崩溃如何恢复数据?
  • 集群聊天服务器各个类进行详解
  • Cookie 与 Session概述
  • 【神经网络在MATLAB中是如何实现的?】
  • 构建可扩展的测试体系,从设计、优化到持续维护
  • 2D视觉系统标定流程与关键要求
  • VSCODE调教
  • 《前端基础核心知识笔记:HTML、CSS、JavaScript 及 BOM/DOM》
  • yolov8-pos/yolov11-pos 训练
  • 6、docker network
  • UE5 lumen
  • Linux搭建LAMP环境(CentOS 7 与 Ubuntu 双系统教程)
  • FastAdmin系统框架通用操作平滑迁移到新服务器的详细步骤-优雅草卓伊凡
  • lua(xlua)基础知识点记录二
  • STM32上移植Lua解析器
  • Android15系统实现刷机防呆功能
  • 【JVM】深入理解 JVM 类加载器
  • MySQL如何解决事务并发的幻读问题
  • JVM 内存分配与垃圾回收策略
  • macOS 字体管理全攻略:如何查看已安装字体及常见字体格式区
  • 网络编程7.17
  • JAVA中的Collection集合及ArrayList,LinkedLIst,HashSet,TreeSet和其它实现类的常用方法
  • MyBatis延迟加载(Lazy Loading)之“关联查询”深度解析与实践