Python进阶02-面向对象高级
1、面向对象的三大特性
- 面向对象的三大特性:封装、继承、多态
- ① 封装:将属性和方法书写到类的里面的操作即为封装,封装可以为属性和方法添加私有权限。
- ② 继承:子类默认继承父类的所有属性和方法,与此同时子类也可以重写父类属性和方法。
- ③ 多态:多态是同一类事物具有的多种形态。不同的对象调用同一个接口(方法),表现出不同的状态,称为多态。
2、Python中的封装
(1)私有属性和私有方法
- 在Python中,可以为实例属性和方法设置私有权限,即设置某个实例属性或实例方法不继承给子类。
- 设置私有属性和私有方法的方式非常简单:在属性名和方法名 前面 加上两个下划线 "__" 即可。
(2)设置私有属性
- 私有属性不能在类的外部被直接访问。
- 类中的私有属性不能被其子类继承。
'''
在Python代码中,我们在定义类的时候,只需要在定义属性时为其添加两个__,其就变成了私有属性!
class 类名(object):
# 定义属性
def __init__(self, name, age):
self.name = 公有属性
self.__age = 私有属性
# 定义方法
'''
class Girl(object):
# 定义属性
def __init__(self, name):
self.name = name
self.__age = 18
g1 = Girl('小美')
print(g1.name)
print(g1.__age)# 报错,提示Girl对象没有__age属性
(3)获取与设置私有属性值
- 在Python中,一般定义函数名' get_xx '用来获取私有属性,定义' set_xx '用来修改私有属性值。
'''
私有属性有何意义?
答:明确区分内外,控制外部对内部数据访问,起到保护数据以及过滤异常数据目的!
'''
class Girl(object):
# 定义属性
def __init__(self, name):
self.name = name
self.__age = 18
# 给__age私有属性添加一个访问"接口"
def get_age(self):
# 获取属性之前
# 1、需要验证用户是否具有查看属性的权限
# 2、如果有,则返回私有属性;如果没有权限,则直接禁止访问
return self.__age
# 给__age私有属性添加一个设置"接口"
def set_age(self, age):
# 在设置属性之前
# 1、首先对age进行判断,判断是否是一个合理数据
if not isinstance(age, int):
print('很抱歉,您设置的age参数非整数类型')
return
if age <= 0 or age > 250:
print('很抱歉,您设置的age参数范围不合理')
return
# 2、如果是合理的数据,则允许设置;反之,则直接禁止
self.__age = age
g1 = Girl('小美')
print(g1.name)
g1.set_age(20)
print(g1.get_age())
(4)设置私有方法
- 私有方法的定义方式与私有属性基本一致,在方法名的前面添加两个下划线
__方法名()
'''
如何定义私有方法?
class 类名():
# 定义属性
# 定义方法
def __方法名称(self):
私有方法
私有方法有何意义呢?答:私有方法并不是用于保护数据,简化程序的复杂度。
银行 => ATM取款 => ① 插卡 ② 用户验证 ③ 输入取款金额 ④ 取款 ⑤ 打印账单
'''
class ATM(object):
# 1、定义一个插卡方法
def __card(self):
print('插卡')
# 2、定义一个用户验证
def __auth(self):
print('用户验证')
# 3、定义一个方法
def __input(self):
print('输入取款金额')
# 4、取款
def __take_money(self):
print('取款')
# 5、打印账单
def __print_bill(self):
print('打印账单')
# 定义一个统一的"接口",专门用于实现取款操作
def withdraw(self):
self.__card()
self.__auth()
self.__input()
self.__take_money()
self.__print_bill()
# 实例化ATM类,进行取款操作
atm = ATM()
atm.withdraw()
(5)封装的意义
-
封装私有属性意义:明确的区分内外,控制外部对对隐藏的属性的操作行为
-
封装私有方法意义:降低程序的复杂度
3、Python中的继承
(1)继承是什么
- 生活中的继承,一般指的是子女继承父辈的财产。
- 类是用来描述现实世界中同一组事务的共有特性的抽象模型,但是类也有上下级和范围之分,比如:生物 => 动物 => 哺乳动物 => 灵长型动物 => 人类 => 黄种人
- 从哲学上说,就是共性与个性之间的关系,比如:白马和马!所以,我们在OOP代码中,也一样要体现出类与类之间的共性与个性关系,这里就需要通过类的继承来体现。简单来说,如果一个类A使用了另一个类B的成员(属性和方法),我们就可以说A类继承了B类,同时这也体现了OOP中代码重用的特性!
(2)继承的基本语法
- 假设A类要继承B类中的所有属性和方法(私有属性和私有方法除外)
- 在Python中,所有类默认继承object类,object类是顶级类或基类;其他子类叫做派生类。
class B(object):
pass
class A(B):
pass
a = A()
a.B中的所有公共属性
a.B中的所有公共方法
- 案例:人类和学生
'''
在Python代码中,为了实现代码重用 => 体现共性与个性的问题,可以通过继承来实现重用关系
如果A类继承了B中的所有公共属性和公共方法,基本语法:
class B(object):
# 公共属性
# 公共方法
class A(B):
A这个类实例化的对象会自动拥有B类的中所有公共属性和公共方法
'''
class Person(object):
# 定义公共方法
def eat(self):
print('i can eat food!')
# 定义公共方法
def run(self):
print('i can run!')
class Student(Person):
pass
# 实例化对象
stu = Student()
stu.eat()
stu.run()
(3)几个相关概念
-
继承:一个类从另一个已有的类获得其成员的相关特性,就叫作继承!
-
派生:从一个已有的类产生一个新的类,称为派生!
-
很显然,继承和派生其实就是从不同的方向来描述的相同的概念而已,本质上是一样的!
-
父类:也叫作基类,就是指已有被继承的类!
-
子类:也叫作派生类或扩展类
-
扩展:在子类中增加一些自己特有的特性,就叫作扩展,没有扩展,继承也就没有意义了!
-
单继承:一个类只能继承自一个其他的类,不能继承多个类,单继承也是大多数面向对象语言的特性!
-
多继承:一个类同时继承了多个父类, (C++、Python等语言都支持多继承)
(4)单继承
- 单继承:一个类只能继承自一个其他的类,不能继承多个类。这个类会有具有父类的属性和方法。
class Animal(object):
def eat(self):
print('吃...')
def sleep(self):
print('睡...')
def call(self):
print('叫...')
class Dog(Animal):
pass
class Cat(Animal):
pass
(5)单继承传递性
- 在Python继承中,如A类继承了B类,B类又继承了C类。则根据继承的传递性,则A类也会自动继承C类中所有属性和方法(公共)
'''
多层继承也是单继承的一种延伸,简单来说:A => B => C,所以A自动继承了C中的所有公共属性和公共方法
'''
class C(object):
def func1(self):
print('C类中的func1方法')
class B(C):
def func2(self):
print('B类中的func2方法')
class A(B):
pass
# 实例化A类产生一个a对象
a = A()
a.func2()
a.func1()
(6)多继承
- 多继承:一个类同时继承了多个父类,并且同时具有所有父类的属性和方法
'''
Python语言是少数支持多继承的一门编程语言,所谓的多继承就是允许一个类同时继承自多个类的特性。
class C(object):
pass
class B(object):
pass
class A(B, C):
pass
'''
# 汽油车类
class GasolineCar(object):
def run_with_gasoline(self):
print('i can run with gasoline!')
# 电动车类
class ElectricCar(object):
def run_with_electric(self):
print('i can run with electric!')
# 混动汽车
class HybridCar(ElectricCar, GasolineCar):
pass
benz = HybridCar()
benz.run_with_gasoline()
benz.run_with_electric()
(7)多继承的覆盖问题
- 在 Python 中,类的定义可以包括一个基类列表,这些基类将按照它们在类定义中出现的顺序被继承。如果多个基类又相同的属性和方法,会继承排在前面基类的属性和方法
# 汽油车类
class GasolineCar(object):
def run(self):
print('i can run with gasoline!')
# 电动车类
class ElectricCar(object):
def run(self):
print('i can run with electric!')
# 混动汽车
class HybridCar(ElectricCar, GasolineCar):
pass
# 到底HybridCar优先继承哪个类中的所有公共属性和公共方法呢?
benz = HybridCar()
benz.run() # i can run with electric!
(8)子类重写父类属性方法
- 重写也叫作覆盖,就是当子类成员与父类成员名字相同的时候,从父类继承下来的成员会重新定义!
- 此时,通过子类实例化出来的对象访问相关成员的时候,真正起作用的是子类中定义的成员!
- 上面单继承例子中 Animal 的子类 Cat和Dog 继承了父类的属性和方法,但是我们狗类Dog 有自己的叫声'汪汪叫',猫类 Cat 有自己的叫声 '喵喵叫' ,这时我们需要对父类的 call() 方法进行重构。如下:
class Animal(object):
# 定义属性name和age
def __init__(self, name, age):
self.name = name
self.age = age
def eat(self):
print('吃...')
def sleep(self):
print('睡...')
def call(self):
print('叫...')
class Dog(Animal):
def call(self):
print('汪汪叫...')
class Cat(Animal):
def call(self):
print('喵喵叫...')
# Dog实例对象
dog = Dog('汪汪',10)
dog.eat()
dog.call()
# Cat实例对象
cat = Cat('喵喵',8)
cat.eat()
cat.call()
-
思考一个问题:此时父类中的call方法还在不在?
- 答:还在,只不过是在其子类中找不到了。类方法的调用顺序,当我们在子类中重构父类的方法后,Cat子类的实例先会在自己的类 Cat 中查找该方法,当找不到该方法时才会去父类 Animal 中查找对应的方法。
-
输出结果
吃...
汪汪叫...
吃...
喵喵叫...
(9)子类调用父类属性和方法
- super():调用父类属性或方法
- 完整写法:super(当前类名, self).属性或方法()
'''
在Python代码中,如果在继承重写的基础上,我们还需要强制调用父类中的属性或方法,可以考虑使用super()
基本语法:
super().属性
super().方法()
'''
class Animal():
def __init__(self, name, age):
self.name = name
self.age = age
def eat(self):
print('吃...')
def sleep(self):
print('睡...')
def call(self):
print('叫...')
class Dog(Animal):
def __init__(self, name, age, sex):
super().__init__(name, age)
self.sex = sex
def __str__(self):
return f'{self.name},今年{self.age}岁了,我会汪汪叫...'
class Cat(Animal):
def __init__(self, name, age, sex):
super().__init__(name, age)
self.sex = sex
def __str__(self):
return f'{self.name},今年{self.age}岁了,我会喵喵叫...'
dog = Dog('阿呆',18,'男')
dog.eat()
print(dog)
cat = Cat('阿瓜',20,'女')
cat.eat()
print(cat)
- 输出
吃...
阿呆,今年18岁了,我会汪汪叫...
吃...
阿瓜,今年20岁了,我会喵喵叫...
4、Python中的多态
(1)多态是什么
- 多态指的是一类事物有多种形态。
- 定义:多态是一种使用对象的方式,子类重写父类方法,调用不同子类对象的相同父类方法,可以产生不同的执行结果① 多态依赖继承② 子类方法必须要重写父类方法
- 好处:调用灵活,有了多态,更容易编写出通用的代码,做出通用的编程,以适应需求的不断变化!
(2)多态的实现步骤
- 定义父类,并提供公共方法
- 定义子类,并重写父类公共方法
- 传递子类对象给调用者,可以看到不同子类执行效果不同
'''
什么是多态?简单来说就是一种事物,随着传入对象的不同,可以返回多种形态。
① 多态依赖继承
② 子类还需要重写父类中的方法
'''
class Fruit(object):
# 定义一个公共方法
def makejuice(self):
pass
class Apple(Fruit):
# 重写父类中的公共方法
def makejuice(self):
print('i can make apple juice!')
class Orange(Fruit):
# 重写父类中的公共方法
def makejuice(self):
print('i can make orange juice!')
class Banana(Fruit):
# 重写父类中的公共方法
def makejuice(self):
print('i can make banana juice!')
# 定义一个公共接口 => 多态特性 => 要求传入一个对象作为参数
def service(obj):
obj.makejuice()
apple = Apple()
orange = Orange()
banana = Banana()
service(apple)
service(orange)
service(banana)
(3)多态实现
- 类具有继承关系,并且子类类型可以向上转型看做父类类型,如果我们从 Animal 派生出 Cat和 Dog,并都写了一个 call() 方法,如下示例:
'''
多态:同一个方法,随着传入对象的不同,返回不同的结果(多态)
① 有继承
② 重写父类中的公共方法
'''
class Dog():
# 定义一个公共方法
def work(self):
pass
class ArmyDog(Dog):
# 重写父类中的work方法
def work(self):
print('追击敌人...')
class DrugDog(Dog):
# 重写父类中的work方法
def work(self):
print('追查毒品...')
class Person(object):
# 定义一个多态接口 => 函数、方法
def work_with_dog(self, obj):
obj.work()
# 实例化Person类生成一个对象
police = Person()
police.work_with_dog(ArmyDog())
5、面向对象的其他特性
(1)类属性
- 类属性就是类中定义的属性,它被该类的所有实例对象所共有。通常用来记录与这类相关的特征,类属性不会用于记录 具体对象的特征。
- 案例:定义一个工具类, 每件工具都有自己的名称,需求:知道使用这个类,创建了多少个工具对象?
'''
在面向对象中,属性一般可以分为两种形式:① 对象属性(成员属性) ② 类属性
在Python代码中,一切皆对象。类也是一种特殊的对象,所以我们也可以为类来创建一个属性 => 类属性
既然类也可以拥有属性,类属性与对象属性有何区别呢?
对象属性(成员属性)是由这个类产生的对象所拥有的属性。
类属性:并不是由实例化对象拥有的,而是这个类所拥有的属性,可以理解为是所有对象共同拥有的属性,但是其不属于某一个对象。
应用场景:比如我们想知道由这个类到底生成了多少个对象,如何定义一个属性来进行统计呢?
强调一下:类属性并不是某个对象所拥有的,而是所有对象所共同组成的一个特征,我们就可以把其定义为类属性
定义类属性:
class 类名称():
属性 = 属性值
类属性如何访问呢?既可以在类的里面也可以在类的外面
类名.类属性(推荐)
对象.类属性(也可以,但是不推荐)
'''
class Person(object):
# 定义一个类属性:用于统计到底由这个类产生了多少个对象
count = 0
# 通过__init__魔术方法定义的都是对象属性
def __init__(self, name, age):
self.name = name
self.age = age
# 对类属性进行累加计数
Person.count += 1
p1 = Person('小明', 23)
p2 = Person('小美', 18)
print(f'由Person类一共创建了{Person.count}个对象!')
(2)类方法
- 类方法就是针对类定义的方法,在类方法中可以直接访问类属性或者调用其他类方法。
- 基本语法:
@classmethod
def 类方法名称(cls):
pass
-
类方法需要用修饰器" @classmethod "来标识,告诉解释器这是一个类方法类方法的第一个参数应该是" cls "
- ① 有哪一个类调用的方法,方法内的" cls "就是哪一个类的引用
- ② 这个参数和示例方法的第一个参数是"self"类似
- ③ 提示使用其他名称也可以,不过习惯使用" cls " 通过类名.调用类方法,调用方法时,不需要传递" cls "参数
-
在方法内部
- ① 可以通过"cls."访问类的属性
- ② 也可以通过 "cls." 调用其他的类方法
-
案例:
- 定义一个工具类
- 每件工具都有自己的名称
- 需求 :在类封装一个" getCount"的类方法,输出当前这个类创建的对象个数
'''
类方法:在Python代码中,类属性与对象属性一样,都强调封装特性,不建议直接在类的外部直接对类属性进行操作。
如果想在类的外部获取类属性的信息,必须使用类方法来实现。
类方法 => 基本语法:
class 类名称():
类属性 = 属性值
@classmethod => 装饰器
def 类方法(cls):
因为这个方法属于类方法,所以cls代表这个类本身
调用类方法:
类名称.类方法() => 强烈推荐使用第一种
对象.类方法()
案例:统计Tool工具类到底生成了多少个工具类对象
'''
class Tool(object):
# 定义一个类属性
count = 0
# 定义对象属性
def __init__(self, name):
self.name = name
# 对类属性进行操作,进行+1
Tool.count += 1
# 定义一个类方法 => 专门用于操作类属性
@classmethod
def getCount(cls):
return f'您一共实例化了{cls.count}个对象!'
# 实例化对象
t1 = Tool('斧头')
t2 = Tool('榔头')
t3 = Tool('锤子')
# 调用类方法,输出一共创建了多少个对象
print(Tool.getCount())
(3)静态方法
-
在开发时,如果需要在类中封装一个方法,这个方法:
- ① 既 不需要访问实例属性或者调用实例方法
- ② 也 不需要访问类属性或者调用类方法这个时候
- ③ 可以把这个方法封装成一个静态方法
-
基本语法:
@staticmethod
def 静态方法名():
pass
- 静态方法需要用修饰器 "@staticmethod" 来标识,告诉解释器这是一个静态方法。通过类名.调用 静态方法
class Game:
@staticmethod
def menu():
print('------')
print('开始[1]')
print('暂停[2]')
print('退出[3]')
Game.menu()
- 案例:打印学生管理系统菜单
'''
什么样的方法可以封装为静态方法?
答:既不需要调用自身属性(对象属性、类属性),也不需要调用自身方法(对象方法、类方法),本身就是一个独立功能。
定义:
class 类名称():
@staticmethod
def 静态方法():
由于静态方法本身就是一个独立功能,既不需要调用自身属性也不需要调用自身方法,所以其没有参数
'''
class StudentManager(object):
# 打印系统功能菜单
@staticmethod
def menu():
print('-' * 40)
print('欢迎使用学生管理系统V1.0')
print('【1】添加学员信息')
print('【2】删除学员信息')
print('【3】修改学员信息')
print('【4】查询学员信息')
print('-' * 40)
# 调用静态方法 => 类名.静态方法() 或者 对象.静态方法()
StudentManager.menu()
6、综合案例-学生管理系统
(1)需求分析
-
① 系统要求:学员数据存储在文件中
-
② 系统功能:
- 添加学员
- 删除学员
- 修改学员信息
- 查询学员信息
- 显示所有学员信息
- 保存学员信息
- 退出系统
(2)对象分析
-
学员对象(姓名、年龄、电话) => 自身属性,打印学生时,还需要输出学生信息 => _str_()
-
学生管理系统对象(可以实现对学生进行增、删、改、查)
-
注意事项
-
① 为了方便维护代码,一般一个角色一个程序文件
-
② 项目要有主程序入口,习惯为
main.py
-
(3)项目创建
-
创建类文件 => studentManager.py
-
创建项目入口文件 => main.py
(4)代码实现
- student.py类文件实现
# 定义一个Student类
class Student():
# 定义魔术方法,用于初始化属性信息
def __init__(self, name, age, mobile):
self.name = name
self.age = age
self.mobile = mobile
# 定义魔术方法,用于打印输出学员信息
def __str__(self):
return f'{self.name}, {self.age}, {self.mobile}'
- studentManager.py类文件实现
from student import Student
class StudentManager(object):
# 定义一个__init__魔术方法,用于初始化数据
def __init__(self):
# 初始化一个student_list属性,用于将来保存所有学员对象信息
self.student_list = []
# 定义load_student()方法
def load_student(self):
pass
def add_student(self):
# 提示用户输入学员信息
name = input('请输入学员的姓名:')
age = int(input('请输入学员的年龄:'))
mobile = input('请输入学员的电话:')
# 使用Student类实例化对象
student = Student(name, age, mobile)
# 调用student_list属性,追加student对象信息
self.student_list.append(student)
print('学员信息已添加成功')
def del_student(self):
# 提示用户输入要删除的学员姓名
name = input('请输入要删除的学员姓名:')
# 对student_list属性(本质列表)进行遍历
for i in self.student_list:
if i.name == name:
# 找到了要删除的学员,删除
self.student_list.remove(i)
print(f'学员{name}信息删除成功')
break
else:
print('您要删除的学员不存在...')
def mod_student(self):
# 提示用户输入要修改的学员姓名
name = input('请输入要修改的学员姓名:')
# 对student_list属性进行遍历,判断要修改的学员姓名是否存在
for i in self.student_list:
if i.name == name:
i.name = input('请输入修改后的学员姓名:')
i.age = int(input('请输入修改后的学员年龄:'))
i.mobile = input('请输入修改后的学员电话:')
print(f'学员信息修改成功,修改后信息如下 => 学员姓名:{i.name},学员年龄:{i.age},学员电话:{i.mobile}')
break
else:
print('您要修改的学员信息不存在...')
def show_student(self):
# 提示用户输入要查询的学员姓名
name = input('请输入要查询的学员姓名:')
# 对student_list属性进行遍历
for i in self.student_list:
if i.name == name:
print(i)
break
else:
print('您要查找的学员信息不存在...')
def show_all(self):
# 直接对列表进行遍历
for i in self.student_list:
print(i)
# 把self.student_list转换为字符串保存到student.data文件中
def save_student(self):
# 打开文件
f = open('student.data', 'w', encoding='utf-8')
# 把列表中的对象转换为字典
new_list = [i.__dict__ for i in self.student_list]
# 文件读写(写入)
f.write(str(new_list))
# 关闭文件
f.close()
# 提示用户数据已经保存成功了
print('学员信息保存成功')
# 定义load_student()方法
def load_student(self):
# 捕获异常
try:
f = open('student.data', 'r', encoding='utf-8')
except:
f = open('student.data', 'w', encoding='utf-8')
else:
# 如果文件存在,没有异常,则执行else语句
content = f.read()
if not content:
content = '[]'
# 把字符串转换为原数据类型[{}, {}, {}]
data = eval(content)
# 把列表中的所有字典 => 转换为对象
self.student_list = [Student(i['name'], i['age'], i['mobile']) for i in data]
finally:
f.close()
# 定义静态show_help()方法
@staticmethod
def show_help():
print('-' * 40)
print('通讯录管理系统V2.0')
print('1.添加学员信息')
print('2.删除学员信息')
print('3.修改学员信息')
print('4.查询学员信息')
print('5.遍历所有学员信息')
print('6.保存学员信息')
print('7.退出系统')
print('-' * 40)
# 定义一个run()方法,专门用于实现对管理系统中各个功能调用
def run(self):
# 1、调用一个学员加载方法,用于加载文件中的所有学员信息,加载完成后,把得到的所有学员信息保存在student_list属性中
self.load_student()
# 2、显示帮助信息,提示用户输入要实现的功能编号
while True:
# 显示帮助信息
self.show_help()
# 提示用户输入要操作功能编号
user_num = int(input('请输入要操作功能的编号:'))
if user_num == 1:
self.add_student()
elif user_num == 2:
self.del_student()
elif user_num == 3:
self.mod_student()
elif user_num == 4:
self.show_student()
elif user_num == 5:
self.show_all()
elif user_num == 6:
self.save_student()
elif user_num == 7:
print('感谢您使用通讯录管理系统V2.0,欢迎下次使用!')
break
else:
print('信息输入错误,请重新输入...')
- main.py入口文件实现
# 从studentManager模块中导入StudentManager类功能
from studentManager import StudentManager
# 定义入口代码
if __name__ == '__main__':
student_manager = StudentManager()
student_manager.run()