Python基础学习(十一)面向对象编程(进阶)

时间:2024-11-04 15:42:28

代码获取:https://github.com/qingxuly/hsp_python_course
完结版:Python基础学习(完结版)

面向对象编程(进阶)

面向对象编程三大特征

  • 面向对象编程有三大特征:封装、继承、多态。

面向对象编程—封装

封装介绍
  • 封装(encapsulation)就是把抽象出的数据[属性]和对数据的操作[方法]封装在一起,数据被保护在内部。
  • 程序只有通过被授权的操作,才能对数据进行访问。
  • 封装经典案例:电视机的操作就是典型封装。
封装的理解和好处
  • 隐藏实现细节:方法(绘制柱状图)<——调用(传入参数…)。
  • 可以对数据进行验证(比如:age: 1~120、password: 长度要求),保证安全合理。
  • 可以保护数据隐私(比如salary),要求授权才可以访问。
私有成员
  • 公共的变量和方法介绍。
    • 默认情况下,类中的变量和方法都是公有的,它们的名称前都没有下划线。
    • 公共的变量和方法,在类的外部、类的内部,都可以正常访问。
  • 如何将属性/方法进行私有化。
    • 类中的变量或方法以双下划线__开头命名,则该变量或方法为私有的,私有的变量或方法,只能在本类内部使用,类的外部无法使用。
  • 如何访问私有的属性/方法:提供公共的方法,用于对私有成员的操作。
快速入门
# 创建职员类(Clerk),属性:name, job, salary
# 1、不能随便查看职员 Clerk 的职位和工资等隐私,比如职员 ("tiger", "python 工程师", 20000)
# 2、提供公共方法,可以对职员和工资进行操作

class Clerk:
    # 公共属性
    name = None
    # 私有属性
    __job = None
    __salary = None

    # 构造方法
    def __init__(self, name, job, salary):
        self.name = name
        self.__job = job
        self.__salary = salary

    # 提供公共的方法,对私有属性操作(根据实际的业务编写即可)
    def set_job(self, job):
        self.__job = job

    def get_job(self):
        return self.__job

    # 私有方法
    def __hi(self):
        print("hi()")

    # 提供公共方法,操作私有方法
    def f1(self):
        self.__hi()


clerk = Clerk("tiger", "python 工程师", 20000)
print(clerk.name)

# print(clerk.__job)  # AttributeError: 'Clerk' object has no attribute '__ job'
print(clerk.get_job())
clerk.set_job("Java 工程师")
print(clerk.get_job())

# 私有方法不能在类的外部直接调用
# clerk.hi()  # AttributeError: 'Clerk' object has no attribute 'hi'

# 通过公共方法,调用私有方法
clerk.f1()
注意事项和细节
  • python语言的动态特性,会出现伪私有属性的情况
class Clerk:
    # 公共属性
    name = None
    # 私有属性
    __job = None
    __salary = None

    # 构造方法
    def __init__(self, name, job, salary):
        self.name = name
        self.__job = job
        self.__salary = salary

    # 提供公共的方法,对私有属性操作
    def get_job(self):
        return self.__job


clerk = Clerk("apple", "python 工程师", 20000)

# 如果这样使用,因为 python 语言的动态特性,会动态的创建属性 __job,但是这个属性和我们在类中定义的私有属性 __ job 不是同一个变量,我们在类中定义的__job 私有属性完整的名字是 _Clerk__job,而并不是
# 可以通过 debug 的“Evaluate Expression”来观察。
clerk.__job = "Go 工程师"
print(f " job = {clerk.__job}")  # job = Go 工程师

# 获取真正的私有属性__job
print(f "{clerk.get_job()}")
  • 练习
# Account 类要求具有属性:姓名(长度为 2-4 位)、余额(必须 > 20)、密码(必须是六位),如果不满足,则给出提示
# 通过 set_xx 的方法给 Account 的属性赋值
# 编写 query_info() 接收姓名和密码,如果姓名和密码正确,返回该账号信息

"" "
    思路分析:
    类名:Account
    私有属性:姓名(长度为 2-4 位)、余额(必须 > 20)、密码(必须是六位)
    构造器:无
    方法:set_xx(self, 属性名) 进行赋值,并且对各个接收到的数据进行校验
    方法:query_info(self, name, pwd) 而且需要验证,才返回响应信息
"" "


class Account:
    __name = None
    __balance = None
    __pwd = None

    def set_name(self, name):
        if 2 <= len(name) <= 4:
            self.__name = name
        else:
            print("名字的长度不在 2-4 位之间")

    def set_balance(self, balance):
        if balance > 20:
            self.__balance = balance
        else:
            print("余额(必须 > 20)")

    def set_pwd(self, pwd):
        if len(pwd) == 6:
            self.__pwd = pwd
        else:
            print("密码(必须是六位)")

    def query_info(self, name, pwd):
        if name == self.__name and pwd == self.__pwd:
            return f "账户信息:{self.__name} {self.__ balance}"
        else:
            return "请输入正确的名字和密码"


# 测试
account = Account()
account.set_name("tim")
account.set_pwd("000000")
account.set_balance(100)
print(account.query_info("tim", "000000"))

面向对象编程—继承

基本介绍
  • 继承基本介绍

    • 基础可以解决代码复用,让我们的编程更加靠近人类思维。
    • 当多个类存在相同的属性(成员变量)和方法时,可以从这些类中抽象出方法,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法。
  • 示意图

image-20240807183802865

  • 基本语法
class DerivedClassName(BaseClassName):
    <statement-1>
    ...
    <statement-N>
    
# 派生类就会自动拥有基类定义的属性和方法
# 基类习惯上也叫父类
# 派生类习惯上也叫子类
快速入门
# 编写父类
class Student:
    name = None
    age = None
    __score = None

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def show_info(self):
        print(f " name ={self.name}, age ={self.age}, score ={self.__score}")

    def set_score(self, score):
        self.__score = score


# 小学生类,继承 Student
class Pupil(Student):
    def testing(self):
        print("小学生在考小学数学...")


# 大学生类,继承 Student
class Graduate(Student):
    def testing(self):
        print("大学生在考高等数学...")


# 测试
student1 = Pupil("apple", 10)
student1.testing()
student1.set_score(70)
student1.show_info()

student2 = Graduate("grape", 22)
student2.testing()
student2.set_score(80)
student2.show_info()
  • 继承给编程带来的便利
    • 代码的复用性提高了。
    • 代码的扩展性和维护性提高了。
继承的注意事项和细节
  • 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问,但是私有属性和方法在子类不能在子类直接访问,要通过父类提供公共的方法去访问。
# 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问,但是私有属性和方法在子类不能在子类直接访问,要通过父类提供公共的方法去访问。
class Base:
    # 公共属性
    n1 = 100
    # 私有属性
    __n2 = 200

    def __init__(self):
        print("Base 构造方法")

    def hi(self):
        print("hi() 公共方法")

    def __hello(self):
        print("__hello() 私有方法 ")

    # 提供公共方法,访问私有属性和私有方法
    def test(self):
        print("属性:", self.n1, self.__n2)
        self.__hello()


class Sub(Base):

    def __init__(self):
        print("Sub 构造方法")

    def say_ok(self):
        print("say_ok() ", self.n1)
        self.hi()

        # print(self.__n2)  # AttributeError: 'Sub' object has no attribute '_Sub__n2'
        # self.__hello()  # AttributeError: 'Sub' object has no attribute '_Sub__n2'


# 测试
sub = Sub()
sub.say_ok()

# 调用子类继承父类的公共方法,去实现访问父类的私有成员的效果
sub.test()
  • Python语言中,object是所有其它类的基类。Ctrl + H可以查看一个类的继承关系。

  • Python支持多继承。

class A:
    n1 = 100

    def sing(self):
        print("A sing()...", self.n1)


class B:
    n2 = 200

    def dance(self):
        print("B dance()...", self.n2)


class C(A, B):
    # Python pass 是空语句,是为了保持程序结构的完整;pass 不做任何事情,一般用作站位语句。
    pass


c = C()
# 继承的属性信息
print(f "属性信息:{c.n1} {c.n2}")
# 调用继承的方法
c.dance()
c.sing()
  • 在多重继承中,如果有同名的成员,遵守从左到右的继承优先级(即:写在左边的父类优先级高,写在右边的父类优先级低)。
class A:
    n1 = 100

    def sing(self):
        print("A sing()...", self.n1)


class B:
    n2 = 200

    def dance(self):
        print("B dance()...", self.n2)

    def sing(self):
        print("B sing()...", self.n2)


class C(A, B):
    # Python pass 是空语句,是为了保持程序结构的完整;pass 不做任何事情,一般用作站位语句。
    pass


c = C()
# 继承的属性信息
print(f "属性信息:{c.n1} {c.n2}")
# 调用继承的方法
c.dance()
c.sing()  # A sing()... 100
继承的练习题
class GrandPa:
    name = "大头爷爷"
    hobby = "旅游"


class Father(GrandPa):
    name = "大头爸爸"
    age = 39


class Son(Father):
    name = "大头儿子"


son = Son()
print(f "son.name is {son.name} and son.age is {son.age} and son.hobby is {son.hobby}")
class Computer:
    cpu = None
    memory = None
    disk = None

    def __init__(self, cpu, memory, disk):
        self.cpu = cpu
        self.memory = memory
        self.disk = disk

    def get_details(self):
        return f "CPU: {self.cpu}, Memory: {self.memory}, Disk: {self.disk}"


class PC(Computer):
    brand = None

    def __init__(self, cpu, memory, disk, brand):
        # 初始化子类的属性——方式 1
        # self.cpu = cpu
        # self.memory = memory
        # self.disk = disk
        # self.brand = brand

        # 初始化子类的属性——方式 2
        # 通过 super().xx 方式可以去调用父类方法,这里通过 super().__init__(cpu, memory, disk, brand) 去调用父类的构造器
        super().__init__(cpu, memory, disk)
        self.brand = brand

    def print_info(self):
        print(f "品牌:{self.brand}\t{self.get_details()}")


pc = PC("inter", 32, 1000, "戴尔")
pc.print_info()

调用父类成员

基本介绍&实例
  • 基本介绍
    • 如果子类和父类出现同名的成员,可以通过父类名super() 访问父类的成员。
  • 基本语法
    • 访问父类成员方式1
      • 访问成员变量:父类名.成员变量
      • 访问成员方法:父类名.成员方法(self)
    • 访问父类成员方式2
      • 访问成员变量:super().成员变量
      • 访问成员方法:super().成员方法()
  • 案例演示
class A:
    n1 = 100

    def run(self):
        print("A-run()...")


class B(A):
    n1 = 200

    def run(self):
        print("B-run()...")

    # 通过父类名去访问父类的成员
    def say(self):
        print(f "父类的 n1 ={A.n1} 本类的 n1 ={self.n1}")
        # 调用父类的 run
        A.run(self)

        # 调用本类的 run()
        self.run()

    # 通过 super()方式去访问父类对象
    def hi(self):
        print(f "父类的 n1 ={super().n1}")


b = B()
b.say()
b.hi()
注意事项和使用细节
  • 子类不能直接访问父类的私有成员
class A:
    n1 = 100
    __n2 = 600

    def run(self):
        print("A-run()...")

    def __jump(self):
        print("A-jump()...")


class B(A):
    # 子类不能直接访问父类的私有成员
    def say(self):
        # print(A.__n2)  # AttributeError: type object 'A' has no attribute '_B_ _n2'. Did you mean: '_A__n2'?
        # print(super().__n2)  # AttributeError: 'super' object has no attribute '_B__n2'

        # A.__jump(self)
        # super().jump()
        print("B-say()...")


b = B()
b.say()
  • 访问不限于直接父类,而是建立从子类向上级父类的查找关系 A->B->C…
class Base:
    n3 = 800

    def fly(self):
        print("Base-fly()...")


class A(Base):
    n1 = 100
    __n2 = 600

    def run(self):
        print("A-run()...")

    def __jump(self):
        print("A-jump()...")


class B(A):
    # 访问不限于直接父类,而是建立从子类向上级父类的查找关系 B-> A-> Base...
    def say(self):
        print(Base.n3, A.n3, super().n3)

        Base.fly(self)
        A.fly(self)
        super().fly()
        self.fly()


b = B()
b.say()
  • 建议使用super()的方式,因为如果使用父类名方式,一旦父类变化,类名统一需要修改,比较麻烦。
练习题
  • 分析下面的代码,看看输出什么内容?
class A:
    n1 = 300
    n2 = 500
    n3 = 600

    def fly(self):
        print("A-fly()...")


class B(A):
    n1 = 200
    n2 = 400

    def fly(self):
        print("B-fly()...")


class C(B):
    n1 = 100

    def fly(self):
        print("C-fly()...")

    def say(self):
        print(self.n1)  # 100
        print(self.n2)  # 400
        print(self.n3)  # 600
        print(super().n1)  # 200
        print(B.n1)  # 200
        print(C.n1)  # 100


c = C()
c.say()
  • 针对上面的程序,想在C的say()中,调用C的fly()和A的fly(),应该如何调用?
self.fly()  # C-fly()...
A.fly(self)  # A-fly()...

方法重写

基本介绍&实例
  • 基本介绍
    • 重写又称覆盖(override),即子类继承父类的属性和方法后,根据业务需要,再重新定义同名的属性和方法。
  • 案例演示
image-20240822123940420
练习题
class Person:
    name = None
    age = None

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def say(self):
        return f "名字:{self.name} 年龄:{self.age}"


class Student(Person):
    id = None
    score = None

    def __init__(self, id, score, name, age):
        # 调用父类的构造器完成继承父类的属性和属性的初始化
        super().__init__(name, age)
        # 子类的特有的属性,我们自己完成初始化
        self.id = id
        self.score = score

    def say(self):
        return f "id:{self.id} score:{self.score} {super().say()}"


person = Person("tom", 12)
print(person.say())

student = Student("tom", 14, "tom", 18)
print(student.say())

类型注解-type hint

基本介绍
  • 为什么需要类型注解
    • 随着项目越来越大,代码也就会越来越多,在这种情况下,如通过没有类型注解,很容易不记得某一个方法的参数类型是什么。
    • 一旦传入了错误类型的参数,python是解释性语言,只有运行时候才能发现问题,这对大项目来说是一个巨大的灾难。
# 对字符串进行遍历
# a: str 给形参 a 进行类型注解,标注 a 的类型是 str。
def fun1(a: str):
    for ele in a:
        print(ele)


# Ctrl + P 提示参数时,没有类型提示
# 如果类型传错了,就会出现异常
fun1(100)  # TypeError: 'int' object is not iterable
  • 类型注解作用和说明
    • 从Python3.5开始,引入了类型注解机制,作用和说明如下:
      • 类型提示,防止运行时出现参数类型、返回值类型、变量类型不符合。
      • 作为开发文档附加说明,方便使用者调用时传入和返回类型参数。
      • 加入后并不会影响程序的运行,不会报正式的错误,只有提示。
      • Pycharm支持类型注解,参数类型错误会黄色提示。
变量的类型注释
  • 基本语法:变量: 类型

  • 基础数据类型注解

n1: int = 10  # 对n1进行类型注解,标注n1的类型为int;如果给出的类型与标注的类型类型不一致,则Pycharm会给出黄色警告
n2: float = 10.1
is_pass: bool = True
name: str = "Tom"
  • 实例对象类型注解
class Cat:
    pass


cat: Cat = Cat()  # 对cat进行类型注解,标注cat类型是Cat
  • 容器类型注解
my_list: list = [1, 2, 3]  # 对my_list进行类型注解,标注my_list类型为list。
my_tuple: tuple = ("run", "sing", "fly")
my_set: set = {1, 2, 3}
my_dict: dict = {"name": "Tom", "age": 22}
  • 容器详细类