面向对象特性:继承
概念:
子类默认继承父类的所以属性和方法,与此同时子类也可以重写父类属性和方法
现实例子:
生活中继承,一般指的是子女继承父辈的财产
继承的基本语法:
基本语法:
#父类B: Class B(object): pass #子类A Class A(B): pass注意事项:
在python中,所有类默认继承object类,object是顶级类或者基类;其他子类叫做派生类。
解释:自动拥有B中的所有公共属性和方法私有属性和方法不支持继承
实例:
与继承相关的几个概念
继承:一个类从另一个已有的类获得其成员的相关特性,就叫作继承!
派生:从一个已有的类产生一个新的类,称为派生! 很显然,继承和派生其实就是从不同的方向来描述的相同的概念而已,本质上是一样的!
父类:也叫作基类,就是指已有被继承的类!
子类:也叫作派生类或扩展类
扩展:在子类中增加一些自己特有的特性,就叫作扩展,没有扩展,继承也就没有意义了!
(继承后必须要有扩展)
单继承:一个类只能继承自一个其他的类,不能继承多个类,单继承也是大多数面向对象语言的特性!
多继承:一个类同时继承了多个父类,(C++、Python 等语言都支持多继承)
python中的单继承
单继承:一个类只能继承自一个其他的类,不能继承多个类,单继承也是大多数面向对象语言的特性!
基本语法:
#父类B: Class B(object): pass #子类A Class A(B): pass案例:比如汽车可以分为两种(汽油车,电动车)
#1.定义一个共性类(车类) class Car(object): def run(self): print("汽车开动") #2.定义汽油车 class Q_Car(Car): pass #3.定义电动车 class D_Car(Car): pass qcar = Q_Car() qcar.run()单继承特性(多层继承):传递性
在python继承中,如A类继承了B类,B类又继承了C类。则根据继承的传递性,则A类也会自动继承C类中所有属性和方法(公共)
class A(object): def func(self): print('我是A类中的相关方法func') class B(A): pass class C(A): pass a = A() a.func()输出结果:
多继承
概念:
Python 语言是少数支持多继承的一门编程语言,所谓的多继承就是允许一个类同时继承自多个类的特性。
基本语法:
class B(object): pass class C(object): pass class A(B,C): pass a = A() a.B中的所有属性和方法 a.A中的所有属性和方法案例:汽油车、电动车 => 混合动力汽车(汽车+电动)
#定义一个类为汽油车 class QCar(object): def run_qcar(self): print("QCar") #定义一个类为电动车 class DCar(object): def run_Dcar(self): print("DCar") #定义一个汽车 class Car(QCar, DCar): pass test = Car() test.run_qcar() test.run_Dcar()输出结果:
注意:虽然多继承允许我们同时继承自多个类,但是实际开发中,应尽量避免使用多继承,因为如果两个类中出现了相同的属性和方法就会产生命名冲突。
子类扩展:重写父类属性和方法
扩展特性:继承让子类继承父类的所有公共属性和方法,但是如果仅仅是为了继承公共属性和方法,继承就没有实际的意义了,应该是在继承以后,子类应该有一些自己的属性和方法。
什么是重写? 重写也叫作覆盖,就是当子类成员与父类成员名字相同的时候,从父类继承下来的成员会重新定义!
此时,通过子类实例化出来的对象访问相关成员的时候,真正起作用的是子类中定义的成员!
class Father(object):
属性
方法
class Son(Father):
父类属性和方法
自己的属性和方法(如果子类中的属性和方法与父类中的属性或者方法同名,则子类中的属性或者方法回对父类中同名的属性或者方法进行覆盖(重写))
上面单继承例子中 Animal 的子类 Cat 和 Dog 继承了父类的属性和方法,但是我们狗类 Dog 有自己的叫声 ' 汪汪叫 ',猫类 Cat 有自己的叫声 ' 喵喵叫 ',这时我们需要对父类的 call () 方法进行重构。如下:
#定义一个动物父类 class Animal: def eat(self): print("我想eat") def call(self): print("我想call") class Dog(Animal): def call(self): print("dog想call") class Cat(Animal): #继承Animal类并重写父类中的call方法 def call(self): print("cat想call") #定义一个狗对象 dog = Dog() dog.eat() dog.call() #定义一个猫对象 cat = Cat() cat.eat() cat.call()输出结果:
思考:重写父类中的 call 方法以后,此时父类中的 call 方法还在不在?
答:还在,只不过是在其子类中找不到了。==类方法的调用顺序,当我们在子类中重构父类的方法后,Cat 子类的实例先会在自己的类 Cat 中查找该方法,当找不到该方法时才会去父类 Animal 中查找对应的方法。
super()调用父类属性和方法
super():调用父类属性或方法,完整写法:super(当前类名, self).属性或方法(),在 Python3 以后版本中,调用父类的属性和方法我们只需要使用super().属性或super().方法名()就可以完成调用了。
案例:Car 汽车类、QCar 汽油车、DCar 电动车
class Car(object): def __init__(self,brand,model,color): self.brand = brand self.model = model self.color = color def run(self): print('i can run') class DCar(Car): def run(self): print('i can run with DCar') class QCar(Car): def __init__(self, brand, model, color,battery): #强制调用父类中的__init__初始话方法,用于实现对brand/color/model属性进行初始化 super().__init__(brand,model,color) self.battery = battery def run(self): print('i can run with QCar') # 实例化对象 qcar = QCar('宝马','s','粉色',70) print(qcar.brand) print(qcar.model) print(qcar.color) qcar.run()输出结果:
MRO属性或者MRO方法:方法解析顺序
MOO(Method Resolution Order):方法解析顺序,我们可以通过类名.__mro__或者类名.mro()
获得“类的层次结构”,方法解析顺序也是按照这个“类的层次结构”寻找到。
class Car(object): def __init__(self,brand,model,color): self.brand = brand self.model = model self.color = color def run(self): print('i can run') class DCar(Car): def run(self): print('i can run with DCar') class QCar(Car): def __init__(self, brand, model, color,battery): #强制调用父类中的__init__初始话方法,用于实现对brand/color/model属性进行初始化 super().__init__(brand,model,color) self.battery = battery def run(self): print('i can run with QCar') print(QCar.__mro__) print(QCar.mro())输出结果:
类属性的静态方法(注解:@staticmethod)
在开发时,如果需要在类中封装一个方法,这个方法:
① 既不需要访问实例属性或者调用实例方法
② 也不需要访问类属性或者调用类方法
这个时候,可以把这个方法封装成一个静态方法
#开发一款游戏 class Game(object): #开始游戏,打印游戏功能菜单 这个功能既不需要调用对象属性也不要调用对象方法 #也不需要调用类属性也不需要调用类方法 @staticmethod def menu(): print('1.开始游戏') print('2.游戏暂停') print('3.退出游戏') #开始游戏,打印菜单 #调用方式 Game.menu() #实例化对象 game = Game() game.menu()输出结果:
面向对象特性:多态
概念:
多态是同一类事务具有的多种形态。不同的对象调用同一个接口(方法),表现出不同的状态,
成为多态
定义:
多态是一种使用对象的方式,子类重写父类方法,调用不同子类对象的相同父类方法,可以产生不同的执行结果。不同对象 => 使用相同方法 => 产生不同的执行结果。
① 多态依赖继承(不是必须的)
② 子类方法必须要重写父类方法
首先定义一个父类,其可能拥有多个子类对象。当我们调用一个公共方法(接口)时,传递的对象不同,则返回的结果不同。
好处:调用灵活,有了多态,更容易编写出通用的代码,做出通用的编程,以适应需求的不断变化!
多态的原理图
公共接口service就是多态的体现,随着传入水果对象的不同,能返回不同的结果
多态代码的实现
多态:可以基于继承也可以不基于继承
''' 多态特性:一种公共方法,随着传入参数(对象)不同,则同一个方法可以返回不同的结果 ''' class Fruit(object): def makejuice(self): print('i can make juice') #定义Apple类,继承Fruit类,并重写父类中的makejuice方法 class Apple(Fruit): def makejuice(self): print('i can make apple juice') #定义Banana类,继承Fruit类,并重写父类中的makejuice方法 class Banana(Fruit): def makejuice(self): print('i can make banana juice') #定义一个公共的方法,用于调用makejuice功能 def service(obj): obj.makejuice() #传入一个苹果对象 service(Apple()) #传入一个香蕉对象 service(Banana())输出结果:
编写面向对象代码中常见问题
问题1:在定义类时,其没有遵循类的命名规则
答:在 Python 中,类理论上是不区分大小写的。但是要遵循一定的命名规范:首字母必须是字母或下划线,其中可以包含字母、数字和下划线,而且要求其命名方式采用大驼峰。
电动汽车:ElectricCar
父类:Father
子类:Son
问题2:父类一定要继承object?
答:无论是否书写继承object,自定义类都会默认隐式继承 object,全部都是新式类,两种写法效果完全一致。
问题3:打印属性和方法时,不是只能使用print()输出,也可以使用return。
在实际打印对象信息,还建议使用__str__
return:
class Person: def get_info(self): return f"Name: {self.name}, Age: {self.age}" p = Person("Bob", 30) info = p.get_info() # 获取返回值__str__:
class Person: def __str__(self): return f"Person(name={self.name}, age={self.age})" p = Person("Charlie", 35) print(p) # 自动调用__str__函数的闭包
变量在内存中的存储形式
前言:全局变量和局部变量
全局变量的访问范围:
1.在全局作用域中可以正常访问全局变量
2.在局部作用域中也可以正常访问全局变量
局部变量的访问范围:
1.在局部作用域中可以正常访问局部变量
2.在全局作用域中无法正常访问局部变量,因为函数执行完毕后,其内部的变量会随之消失
扩展:
为什么函数执行完毕,其内部的变量会随着消失
答:计算机的垃圾回收机制
#函数外部的区域 => 全局作用域 #定义一个变量 a = 10 def func(): #函数内部的区域 => 局部作用域 b = 20 #尝试访问全局变量a print('在局部内尝试访问全局变量a') print(a) print( '局部变量的访问范围 => 在局部作用域中可以访问局部变量,但是在全局作用域中无法访问局部变量,其内部的变量会自动被内存所回收') print(b) func() print('全局变量的访问范围 ==> 在全局作用域中可以访问全局变量') print(a) #全局变量的访问范围 ==> 在全局作用域中可以访问全局变量 #局部变量的访问范围 => 在局部作用域中可以访问局部变量,但是在全局作用域中无法访问局部变量,其内部的变量会自动被内存所回收输出结果:
数据的闭包--计算机的垃圾回收机制
全局变量与局部变量在栈内存和数据区:
- 全局变量存储在数据区(Data Segment),包括初始化数据段(.data)和未初始化数据段(.bss)。它们的生命周期从程序启动到程序结束,作用域为整个程序。
- 局部变量存储在栈内存(Stack Segment),生命周期仅限于函数调用期间,作用域仅限于函数内部。
闭包与垃圾回收机制
闭包的本质闭包是函数与其引用环境的组合。当内部函数引用外部函数的局部变量时,即使外部函数执行完毕,这些变量仍被保留。闭包通过延长变量的生命周期,突破局部变量的作用域限制。
垃圾回收关键点
- 未被引用的对象会被标记为可回收。
- 闭包引用的变量不会被释放,即使外部函数已结束。
- 全局变量始终不被回收,直到程序终止。
典型闭包示例
def outer(): x = 10 def inner(): print(x) # 引用外部变量形成闭包 return inner closure = outer() closure() # 输出10,证明x未被销毁内存管理对比
栈内存特性
- 自动管理,高效但容量有限
- 后进先出(LIFO)结构
- 存储函数调用上下文和局部变量
数据区特性
- 静态分配,生命周期固定
- 存储全局/静态变量和常量
- 内存占用持续到程序结束
变量生命周期管理
延长生命周期的方法使用闭包可主动控制变量生命周期。闭包函数保持对外部变量的引用,阻止垃圾回收机制释放这些变量。
内存泄漏风险不当使用闭包可能导致内存泄漏。当闭包长期持有大对象引用时,这些对象无法被及时回收。应避免在闭包中缓存不必要的数据。
问题:我们也没有办法把函数内部的布局变量保留
在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数的内部函数成为闭包