Python进阶02-面向对象高级

229 阅读19分钟

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

image-20210319145119665

(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()