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 是封装的"对外窗口"—— 它隐藏了复杂的内部细节,只留下安全、易用、稳定的交互方式,是面向对象编程中实现"高内聚、低耦合"的手段:
- 封装是将对象的属性和方法包装一起,对外隐藏实现细节,只提供必要的接口给外部访问。
- 在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
场景 | 实现方式 | 示例 |
---|---|---|
只读属性 | @property | p.name(无括号) |
可读可写属性 | @property + @setter | p.name = "Bob" |
可读可删属性 | @property + @deleter | del p.name |
计算属性 | @property (动态计算) | c.area(每次计算) |
私有属性保护 | __ 前缀 + 公共方法 | self.__name 通过get_name()访问 |
- 不要滥用
@property
,仅在需要计算或验证时使用。 - 避免在
@property
方法中执行耗时操作,因为它们会在每次访问时调用。
(二)继承/派生
Python中所有的类最终都继承自内置的object类
继承/派生
- 继承是从已有的类中派生出新的类,新类具有原类的数据属性和行为,并能扩展新的能力。
- 派生类就是从一个已有类中衍生出新类,在新的类上可以添加新的属性和行为
继承/派生的作用
- 用继承派生机制,可以将一些共有功能加在基类中。实现代码的共享。
- 在不改变基类的代码的基础上改变原有类的功能
继承/派生名词
- 基类(base class)/超类(super class)/父类(father class)
- 派生类(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)
实现和父类同名,但功能不同的方法
- 在子类中实现与基类同名的方法,称为方法覆盖。
- 覆盖后,子类实例调用该方法时,优先执行子类的实现。
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度电
多态
- 核心思想:同一接口,多种实现。即同一个方法在不同对象上表现出不同的行为
- 通过多态,可以使得不同类型的对象以相同的接口表现出不同的行为。
- 多态的实现通常通过继承和方法重写来实现。
【基于继承的多态】
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方法即可)
方法重写
在不改变父类代码的前提下,让子类拥有不同的行为,实现多态性
- 如果父类方法的功能不能满足需求,可以在子类重写父类的方法
- 方法签名必须相同:方法名、参数列表、返回值类型需与父类一致。
- 访问权限不能更严格:子类方法的访问权限需大于等于父类。
对象转字符串重写
【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() 函数是用于调用父类(超类)的一个方法
- 无参数形式:在子类方法中使用 super().add()super().add()super().add() 调用父类中已被覆盖的方法
- 带参数形式:使用 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()调用父类构造函数,以确保父类的构造函数被正确调用和初始化。
- 避免代码重复:父类的初始化逻辑只需写一次。
- 多继承安全:确保所有父类的初始化方法按 MRO 顺序被调用。
- 正确初始化:确保父类的初始化逻辑(如设置属性、分配资源等)被执行。
【初始化父类属性】
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 线性化算法确保了以下几点:
- 子类优先于父类:子类的方法或属性会优先于父类被查找。
- 父类的顺序保持不变:在定义类时,父类的顺序会影响 MRO。在多重继承中,按照继承列表的顺序从左到右查找。
- 单调性:如一个类出现在另一个类的MRO中,那么它的父类也应出现在这个类的MRO中
MRO 的计算方法
- 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面向对象编程的核心思想和实践方法。