python语言基础(五)--面向对象和三大特征

一、面向对象

1、概述

  • 是一种编程思想,先把复杂的事情简单化 ,然后把程序员从执行者的角色变成指挥者的角色更符合人们的思考习惯

  • 强调以对象 为基础,完成各种操作,是基于面向过程的。

拓展:python同时具有面向过程和面向对象思想。

例如:平时生活中,我们说的车就是一种类,而我家楼下停的那辆牌照为XXX的车就是对象。

一般来说,生活中的方方面面都是对象,我们可以说万物皆对象。

2、三大特征简述

(一)、继承

概述

简单来说就是子类可以继承父类的某些成员,类似于现实中的子承父业。

格式

python 复制代码
class 子类名(父类名):
    pass

好处

提高代码的复用性,提高代码的可维护性

弊端

耦合性增加

(二)、封装

概述

简单来说就是隐藏对象的相关属性和实现的细节,仅仅对外提供公共的访问方法

好处

提高代码的安全性

提高代码的复用性

弊端

代码量增加

(三)、多态

指的是同一个事物在不同条件下表现出的不同形态或者状态。在python中可以理解为函数输入不同的参数,返回不同的值和功能。

3、入门代码

相关概述

属性:名词,用来描述对象的外在特征,比如姓名,年龄等等

行为:动词,用来表示对象能做什么事情,比如学习,看书等等

类:抽象的概念,可以理解为一大类的东西,一种统称

对象:类的具体实现,有事物存在

格式

python 复制代码
class 类名(父类名):
    
    def __init__(self):
    	self.属性名字 = 数值
       
    def get...(self):
        具体的动作
        
if __name__ == '__main__':
    tmp = 列名()
    print(tmp.属性名字)
    
	tmp.get...()

例如

python 复制代码
# 需求: 定义1个汽车类, 然后具有跑的功能, 并调用.
# 1. 定义汽车类.
class Car():
    # 属性.
    def __init__(self):
        self.color = '红色'
    # 行为, 和以前定义的函数一样, 只不过第一个形参要写self.
    def run(self):
        print('汽车会跑!...')

# 在main方法中测试调用.
if __name__ == '__main__':
    # 2. 创建汽车类对象.
    c1 = Car()  # 大白话: 根据图纸制造了1辆汽车, 汽车名叫: c1
    c2 = Car()  # 大白话: 根据图纸制造了1辆汽车, 汽车名叫: c2

    # 3. 调用 汽车类的 功能.md
    # 3.1 调用 属性
    # 3.2 调用 行为.
    c1.run()
    c2.run()

    # 4. 直接打印c1, c2 看看是什么.
    print(f'c1: {c1}')  # 地址值
    print(f'c2: {c2}')  # 地址值

4、self关键字介绍

概述

是python内置的一种属性,代表本类当前对象的应用,大白话来说就是哪个对象调用函数,函数内的self就代表哪个对象。

作用

用于函数内,区分不同的对象的,因为一个类会有不同的对象去调用它,self就是区分这些不同对象的。

例如

python 复制代码
# 需求: 定义汽车类, 有run()的功能, 然后创建该类的对象, 并调用run()功能.  细节: run()要区分是哪个对象调用的.
# 1. 定义汽车类.
class Car:
    # 汽车类内部, 定义: 行为(函数), 表示具有: 跑的功能.
    def run(self):
        print('汽车具有 跑 的功能!')
        # 通过self关键字, 可以实现: 在函数内区分 到底是哪个对象 调用的该函数.
        print(f'当前对象为: {self}')

# 在main函数中, 测试调用.
if __name__ == '__main__':
    # 2. 创建汽车类的对象.
    c1 = Car()
    c2 = Car()

    # 3. 调用Car类的 run()函数, 简写注释为: 调用 Car#run()
    c1.run()
    print('-' * 21)
    c2.run()
    print('-' * 21)

    # 4. 打印地址值.
    print(f'c1: {c1}')
    print(f'c2: {c2}')

5、属性的定义和调用

概述

属性=》名词,描述事物的外在特征,例如姓名,年龄等

定义格式

类内:对象名.属性名 = 属性值

类外:要结合 __ init __()魔法方法一起用

调用格式

类内:对象名.属性名

类外:self.属性名

注意

遇到的问题: 在main函数中, 通过 对象名.属性名 = 属性值的方式 给c1对象设置属性值了, 但是 c2对象没有属性值.

产生的原因: 在类外设置属性, 只能给当前对象设置, 不能给本类的其它对象设置.

解决方案: 通过 __ init __ ()魔法方法在类内给该类的对象设置属性值, 则该类所有的对象默认都有这些属性值.

代码

python 复制代码
# 需求: 给车设置颜色为红色, 4个轮胎, 且获取属性值并输出.
# 扩展: 可以在类内部定义1个show()方法来获取刚才给车设置的属性.

# 1. 定义汽车类.
class Car:
    # 2. 在类内定义show()方法, 获取属性值. 即:  类内获取属性值
    def show(self):
        # self = 本类当前对象的引用.
        print(f'颜色: {self.color}, 轮胎数: {self.num}')

# 在main函数中测试调用.
if __name__ == '__main__':
    # 3. 创建对象.
    c1 = Car()
    # 4. 给c1对象设置属性值.    类外设置属性
    c1.color = '红色'
    c1.num = 4
    # 5. 打印c1对象的属性值.    类外获取属性值.
    print(f'颜色: {c1.color}, 轮胎数: {c1.num}')
    # 6. 调用show()方法, 获取属性值.
    c1.show()
    print('-' * 21)

    # 7. 创建c2汽车类对象, 尝试调用show()函数.
    c2 = Car()
    c2.show()

5、魔法方法

(一)、概述

概述

它是Python内置的一些函数, 主要是用来对类的功能做增强的

格式

__方法名__()

特点

在满足特定情况的场景下, 会被自动调用, 无需用户手动调用

常用的魔法方法

__ init __ () 在创建对象的时候, 会被自动调用, 一般用于给对象初始化一些属性值. 即: 对象名 = 类名() 就会自动调用该魔法方法.

__ str __ ()

__ del __()

(二)、init

(1)、无参数

概述

属性 = 名词, 即: 描述事物的外在特征的. 例如: 姓名, 年龄, 性别...

定义格式

类外: 对象名.属性名 = 属性值

类内: 要结合 __ init __ () 魔法方法一起使用.

调用格式

类外: 对象名.属性名

类内: self.属性名

例如

python 复制代码
# 需求: 给车这个对象默认设置 颜色 color=黑色, 轮胎数 number=3
# 1. 定义汽车类.
class Car():
    # 2. 通过 魔法方法 init, 初始化 汽车对象的属性值, 即: 所有汽车类对象, 默认都有这个属性值.
    def __init__(self):     # 无参数的 init函数.
        print('我是init函数, 我被调用了!')
        # 具体的设置属性值的动作.
        self.color = '黑色'
        self.number = 3

    # 3. 定义函数show(), 在类内调用 属性.
    def show(self):
        print(f'颜色: {self.color}, 轮胎数: {self.number}')

# 在main函数中测试.
if __name__ == '__main__':
    # 4. 创建汽车类对象.
    c1 = Car()
    c2 = Car()
    print('-' * 21)

    # 扩展: 修改c1的属性值.
    c1.color = '霞光紫'
    c1.number = 10

    # # 5. 调用show()函数, 打印属性值.
    c1.show()
    print('-' * 21)
    c2.show()
    print('-' * 21)

    # 6. 扩展, 在类外调用属性.
    print(c1.color, c1.number)
    print(c2.color, c2.number)
(2)、有参数

回顾刚才的代码:

init魔法方法中的 属性值, 都是固定的, 写死的, 很不方便, 我们就思考, 这些参数值能不能通过 外部传参的方式来实现对其进行赋值操作呢?

答案:

肯定是可以的, 通过 带参数的init函数即可解决.

例如

python 复制代码
# 需求: 通过外部传参的方式, 给汽车对象设置属性值, 例如: 褐色, 6个轮胎.
# 1. 定义汽车类.
class Car():
    # 2. 通过 魔法方法 init, 初始化 汽车对象的属性值, 即: 所有汽车类对象, 默认都有这个属性值.
    def __init__(self, color, number):  # 带参数的 init函数.
    # def __init__(self, color = None, number = None):  # 带参数的 init函数.
        print('我是init函数, 我被调用了!')
        # 具体的设置属性值的动作.
        self.color = color
        self.number = number

    # 3. 定义函数show(), 在类内调用 属性.
    def show(self):
        print(f'颜色: {self.color}, 轮胎数: {self.number}')


# 在main函数中测试.
if __name__ == '__main__':
    # 4. 创建汽车类对象.
    c1 = Car('黑色', 6)      # 因为init函数带参, 所以需要传参.
    c2 = Car()
    print('-' * 21)

    # 扩展: 修改c1的属性值.
    c1.color = '霞光紫'
    c1.number = 10

    # # 5. 调用show()函数, 打印属性值.
    c1.show()
    print('-' * 21)
    c2.show()
    print('-' * 21)

    # 6. 扩展, 在类外调用属性.
    print(c1.color, c1.number)
    print(c2.color, c2.number)

(三)、str

概述

__ str __ () 当输出语句直接打印对象的时候, 会自动调用该魔法方法, 默认打印的是地址值, 无意义, 一般会改为: 打印该对象的各个属性值

解释

在调用print打印对象时,会打印一次,该函数可以定义输出函数。

例如

python 复制代码
# 1. 定义汽车类, 属性: 品牌, 价格.
class Car():
    # 定义init, 初始化属性.
    def __init__(self, brand, price):
        self.brand = brand  # 品牌
        self.price = price  # 价格

    # 行为
    def run(self):
        print('汽车会跑!')

    # 魔法方法, str
    def __str__(self):
        # return '我是str魔法方法'
        return f'品牌: {self.brand}, 价格: {self.price}'


# 在main函数中测试调用.
if __name__ == '__main__':
    # 2. 创建汽车类对象.
    c1 = Car('小米Su7', 239999)  # 创建对象时, 自动调用了: init魔法方法
    # 3. 直接打印c1对象
    print(c1)  # 默认调用了 __str__() 魔法方法.

(四)、del

概述

__ del __ () 当手动删除对象时 或者 文件执行结束后, 该函数会被自动调用

解释

在删除对象,或者程序结束时,会调用该函数。

例如

python 复制代码
# 1. 定义汽车类, 属性: 品牌, 价格.
class Car():
    # 定义init, 初始化属性.
    def __init__(self, brand, price):
        self.brand = brand  # 品牌
        self.price = price  # 价格

    # 行为
    def run(self):
        print('汽车会跑!')

    # 魔法方法, del
    def __del__(self):
        print(f'对象 {self} 被删除了!')


# 在main函数中测试调用.
if __name__ == '__main__':
    # 2. 创建汽车类对象.
    c1 = Car('小米Su7', 239999)  # 创建对象时, 自动调用了: init魔法方法

    # 3. 删除c1对象, 看看结果.
    del c1

二、三大特征

1、继承

(一)、概述

概述

  • 实际开发中, 我们发现好多类中的部分内容是相似的, 或者相同的, 每次写很麻烦, 针对于这种情况, 我们可以把这些相似(想同)的部分抽取出来,

  • 单独的放到1个类中(父类), 然后让那多个类(子类) 和这个类产生关系, 这个关系就叫: 继承.

大白话

子承父业, Python中的继承, 子类 => 继承父类的 属性, 行为

格式

class 子类名(父类名):
            pass

好处

  • 提高代码的复用性
  • 提高代码的可维护性.

弊端

耦合性增强了. 父类"不好"的内容, 子类想没有都不行

细节

所有的类都直接或者间接继承自object, 它是所有类的父类, 也叫: 顶级类

(二)、单继承

概述

一个子类只有一个父类~

代码

python 复制代码
# 1. 创建1个师傅类, 充当父类.
class Master(object):
    # 1.1 定义父类的 属性.
    def __init__(self):
        self.kongfu = '[古法煎饼果子配方]'

    # 1.2 定义父类的 行为, 表示: 摊煎饼.
    def make_cake(self):
        print(f'使用 {self.kongfu} 制作煎饼果子')


# 2. 定义徒弟类, 继承自师傅类.
class Prentice(Master):
    pass


# 在main函数中测试
if __name__ == '__main__':
    # 3. 创建子类的对象
    p = Prentice()
    # 4. 尝试打印 p对象的 属性 和 行为
    print(f'从父类继承的属性: {p.kongfu}')
    p.make_cake()  # 从父类继承来的行为.

(三)、多继承

概述

一个子类有多个父类

细节

  • Python中支持多继承写法, 即: 1个类可以有多个父类, 写法为: class 子类名(父类名1, 父类名2...)

  • 多继承关系中, 子类可以继承所有父类的属性和行为. 前提: 父类的私有成员除外.

  • 多继承关系中, 多个父类如果有重名属性或者方法时, 子类会优先使用第1个父类(即: 最前边的父类)的该成员.

  • 上述的继承关系, 我们可以通过 Python内置的 mro属性 或者 mro()方法来查看.

    mro: Method Resolution Order, 即: 方法的解析顺序(调用顺序)

例如

python 复制代码
# 1. 创建1个师傅类, 充当父类.
class Master(object):
    # 1.1 定义父类的 属性.
    def __init__(self):
        self.kongfu = '[古法煎饼果子配方]'
        self.name = 'Master'

    # 1.2 定义父类的 行为, 表示: 摊煎饼.
    def make_cake(self):
        print(f'使用 {self.kongfu} 制作煎饼果子')

# 2. 创建1个师傅类, 充当父类.
class School(object):
    # 2.1 定义父类的 属性.
    def __init__(self):
        self.kongfu = '[海鸥煎饼果子配方]'

    # 2.2 定义父类的 行为, 表示: 摊煎饼.
    def make_cake(self):
        print(f'使用 {self.kongfu} 制作煎饼果子')

# 3. 定义徒弟类, 继承自师傅类.
class Prentice(School, Master):
    pass


# 在main函数中测试
if __name__ == '__main__':
    # 4. 创建子类的对象
    p = Prentice()
    # 5. 尝试打印 p对象的 属性 和 行为
    print(f'从父类继承的属性: {p.kongfu}')
    p.make_cake()  # 从父类继承来的行为.
    print('-' * 21)

    # 6. 演示方法的解析顺序, 即: MRO, 看看方法优先会从哪些类中找.
    print(Prentice.__mro__)     # 输出封装成: 元组
    print(Prentice.mro())       # 输出封装成: 列表

(四)、多层继承

概述

类A继承类B, 类B继承类C

例如

python 复制代码
# 1. 创建1个师傅类, 充当父类.
class Master(object):
    # 1.1 定义父类的 属性.
    def __init__(self):
        self.kongfu = '[古法煎饼果子配方]'
        self.name = 'Master'

    # 1.2 定义父类的 行为, 表示: 摊煎饼.
    def make_cake(self):
        print(f'使用 {self.kongfu} 制作煎饼果子')

# 2. 创建1个师傅类, 充当父类.
class School(object):
    # 2.1 定义父类的 属性.
    def __init__(self):
        self.kongfu = '[海鸥煎饼果子配方]'

    # 2.2 定义父类的 行为, 表示: 摊煎饼.
    def make_cake(self):
        print(f'使用 {self.kongfu} 制作煎饼果子')

# 3. 定义徒弟类, 继承自师傅类.
class Prentice(School, Master):
    # 3.1 定义本类(子类)的 属性.
    def __init__(self):
        self.kongfu = '[独创煎饼果子配方]'

    # 3.2 定义本类(子类)的 行为, 表示: 摊煎饼.
    def make_cake(self):        # 子类出现和父类重名且一模一样的函数, 称之为: 方法重写.
        print(f'使用 {self.kongfu} 制作煎饼果子')

    # 3.3 定义函数 make_master_cake(), 表示: 古法摊煎饼果子配方.
    def make_master_cake(self):
        # 前提(细节): 需要重新初始化一下父类的 属性.
        Master.__init__(self)
        # 调用Master#make_cake()
        Master.make_cake(self)

    # 3.4 定义函数 make_school_cake(), 表示: 海鸥摊煎饼果子配方.
    def make_school_cake(self):
        # 前提(细节): 需要重新初始化一下父类的 属性.
        School.__init__(self)
        # 调用School#make_cake()
        School.make_cake(self)

# 4. 定义徒孙类, 继承: 徒弟类.
class TuSun(Prentice):      # 继承关系:  TuSun => Prentice => School, Master => object
    pass

# 在main函数中测试
if __name__ == '__main__':
    # 4. 创建 徒孙类 的对象
    ts = TuSun()

    # 5. 调用父类的成员.
    print(f'属性: {ts.kongfu}')   # 独创煎饼果子配方
    ts.make_cake()               # 独创煎饼果子配方

    ts.make_master_cake()        # 古法
    ts.make_school_cake()        # 海鸥

(五)、方法重写

概述

子类出现和父类重名的属性, 方法时, 会覆盖父类中的成员, 这种写法就称之为: 重写, 也叫: 覆盖

注意

重写一般特指: 方法重写

场景

当子类需要沿袭父类的功能, 而功能主体又有自己额外需求的时候, 就可以考虑使用方法重写了

细节

子类有和父类重名的属性和方法时, 优先使用 子类的成员. 就近原则

例如

python 复制代码
# 1. 创建1个师傅类, 充当父类.
class Master(object):
    # 1.1 定义父类的 属性.
    def __init__(self):
        self.kongfu = '[古法煎饼果子配方]'
        self.name = 'Master'

    # 1.2 定义父类的 行为, 表示: 摊煎饼.
    def make_cake(self):
        print(f'使用 {self.kongfu} 制作煎饼果子')

# 2. 创建1个师傅类, 充当父类.
class School(object):
    # 2.1 定义父类的 属性.
    def __init__(self):
        self.kongfu = '[海鸥煎饼果子配方]'

    # 2.2 定义父类的 行为, 表示: 摊煎饼.
    def make_cake(self):
        print(f'使用 {self.kongfu} 制作煎饼果子')

# 3. 定义徒弟类, 继承自师傅类.
class Prentice(School, Master):
    # 3.1 定义本类(子类)的 属性.
    def __init__(self):
        self.kongfu = '[独创煎饼果子配方]'

    # 3.2 定义本类(子类)的 行为, 表示: 摊煎饼.
    def make_cake(self):        # 子类出现和父类重名且一模一样的函数, 称之为: 方法重写.
        print(f'使用 {self.kongfu} 制作煎饼果子')


# 在main函数中测试
if __name__ == '__main__':
    # 4. 创建子类的对象
    p = Prentice()
    # 5. 尝试打印 p对象的 属性 和 行为
    print(f'属性: {p.kongfu}')
    p.make_cake()
    print('-' * 21)

    # 6. 演示方法的解析顺序, 即: MRO, 看看方法优先会从哪些类中找.
    print(Prentice.__mro__)     # 输出封装成: 元组
    print(Prentice.mro())       # 输出封装成: 列表

(六)、子类访问父类成员

QA

问: 重写后, 子类如何访问父类的成员呢?

答案:

格式1: 父类名.父类方法名(self)

格式2: super().父类方法()

(1)父类名.父类方法名(self)

例如

python 复制代码
# 1. 创建1个师傅类, 充当父类.
class Master(object):
    # 1.1 定义父类的 属性.
    def __init__(self):
        self.kongfu = '[古法煎饼果子配方]'
        self.name = 'Master'

    # 1.2 定义父类的 行为, 表示: 摊煎饼.
    def make_cake(self):
        print(f'使用 {self.kongfu} 制作煎饼果子')

# 2. 创建1个师傅类, 充当父类.
class School(object):
    # 2.1 定义父类的 属性.
    def __init__(self):
        self.kongfu = '[海鸥煎饼果子配方]'

    # 2.2 定义父类的 行为, 表示: 摊煎饼.
    def make_cake(self):
        print(f'使用 {self.kongfu} 制作煎饼果子')

# 3. 定义徒弟类, 继承自师傅类.
class Prentice(School, Master):
    # 3.1 定义本类(子类)的 属性.
    def __init__(self):
        self.kongfu = '[独创煎饼果子配方]'

    # 3.2 定义本类(子类)的 行为, 表示: 摊煎饼.
    def make_cake(self):        # 子类出现和父类重名且一模一样的函数, 称之为: 方法重写.
        print(f'使用 {self.kongfu} 制作煎饼果子')

    # 3.3 定义函数 make_master_cake(), 表示: 古法摊煎饼果子配方.
    def make_master_cake(self):
        # 前提(细节): 需要重新初始化一下父类的 属性.
        Master.__init__(self)
        # 调用Master#make_cake()
        Master.make_cake(self)

    # 3.4 定义函数 make_school_cake(), 表示: 海鸥摊煎饼果子配方.
    def make_school_cake(self):
        # 前提(细节): 需要重新初始化一下父类的 属性.
        School.__init__(self)
        # 调用School#make_cake()
        School.make_cake(self)

# 在main函数中测试
if __name__ == '__main__':
    # 4. 创建子类的对象
    p = Prentice()
    # 5. 尝试打印 p对象的 属性 和 行为
    print(f'属性: {p.kongfu}')    # 独创煎饼果子配方
    p.make_cake()                # 独创煎饼果子配方
    print('-' * 21)

    # 6. 调用父类 Master类的 古法煎饼果子配方
    p.make_master_cake()         # 古法
    print('-' * 21)

    # 7. 调用父类 School类的 古法煎饼果子配方
    p.make_school_cake()         # 海鸥
(2)super().父类方法()

概述

它类似于self, 只不过: self代表本类当前对象的引用. super代表本类对象 父类的引用

大白话

self = 自己, super = 父类

作用

初始化父类成员, 实现 在子类中访问父类成员的

细节

  • super()在多继承关系中, 只能初始化第1个父类的成员, 所以: super()更适用于 单继承环境.
  • 多继承关系中, 如果想实现精准初始化(操作)某个父类的成员, 可以通过 父类名.父类方法名(self)
  • 在单继承关系中, 用 super() 可以简化代码

例如

python 复制代码
# 1. 创建1个师傅类, 充当父类.
class Master(object):
    # 1.1 定义父类的 属性.
    def __init__(self):
        self.kongfu = '[古法煎饼果子配方]'

    # 1.2 定义父类的 行为, 表示: 摊煎饼.
    def make_cake(self):
        print(f'使用 {self.kongfu} 制作煎饼果子')

# 2. 创建1个师傅类, 充当父类.
class School(object):
    # 2.1 定义父类的 属性.
    def __init__(self):
        self.kongfu = '[海鸥煎饼果子配方]'

    # 2.2 定义父类的 行为, 表示: 摊煎饼.
    def make_cake(self):
        print(f'使用 {self.kongfu} 制作煎饼果子')

# 3. 定义徒弟类, 继承自师傅类.
class Prentice(School, Master):
    # 3.1 定义本类(子类)的 属性.
    def __init__(self):
        self.kongfu = '[独创煎饼果子配方]'

    # 3.2 定义本类(子类)的 行为, 表示: 摊煎饼.
    def make_cake(self):        # 子类出现和父类重名且一模一样的函数, 称之为: 方法重写.
        print(f'使用 {self.kongfu} 制作煎饼果子')

    # 3.3 定义函数 make_old_cake(), 表示: 父类的摊煎饼果子配方.
    def make_old_cake(self):
        # 前提(细节): 需要重新初始化一下父类的 属性.
        super().__init__()

        # 调用 父类的#make_cake()
        # Master.make_cake(self)    # 格式1: 父类名.父类方法名(self)
        super().make_cake()         # 格式2: super().父类方法名()


# 在main函数中测试
if __name__ == '__main__':
    # 4. 创建子类的对象
    p = Prentice()
    # 5. 尝试打印 p对象的 属性 和 行为
    print(f'属性: {p.kongfu}')    # 独创煎饼果子配方
    p.make_cake()                # 独创煎饼果子配方
    print('-' * 21)

    # 6. 调用父类的 煎饼果子配方
    p.make_old_cake()     # 海鸥

2、封装

(一)、私有化属性

概述

封装指的是 隐藏对象的属性 和 实现细节, 仅对外提供公共的访问方式

问1: 怎么隐藏 对象的属性 和 实现细节(函数)?
    答: 通过 私有化解决.
问2: 公共的访问方式是什么?
    答: get_xxx(), set_xxx()函数.
问3: get_xxx()和set_xxx()函数 必须成对出现吗?
    答: 不一定, 看需求, 如果只获取值就用 get_xxx(), 如果只设置值就用 set_xxx(). 如果需求不明确, 建议都写.
问4: 封装指的就是 私有, 这句话对吗?
    答: 不对, 因为我们常用的函数也是封装的一种体现

好处

  • 提高代码的安全性. 通过 私有化 实现的.
  • 提高代码的复用性. 通过 函数 实现的.

弊端

代码量增量了, 封装的代码量会变多.

这里的代码量增加指的是: 私有化以后, 就要提供公共的访问方式, 私有化内容越多, 公共的访问方式就越多, 代码量就越多

格式

__属性名       # 注意: 这里是 两个_
__函数名()

特点

只能在本类中直接访问, 外界无法直接调用

例如

python 复制代码
# 1. 定义徒弟类, 有自己的属性 和 行为.
class Prentice(object):
    # 1.1 属性
    def __init__(self):
        self.kongfu = '[独创的煎饼果子配方]'
        # 私有的属性.
        # self.__money__ = 500000     # 这个不是私有, 就是变量名叫: __money__
        self.__money = 500000         # 这个才是私有化的写法.

    # 1.2 对外提供公共的访问方式, 可以实现: 获取私有的变量, 以及给变量设置值.
    # 获取值.
    def get_money(self):
        return self.__money

    # 设置值
    def set_money(self, money):
        # 可以在这里对 money属性做判断, 但是没必要. 因为Python属于后端代码, 这里的钱肯定是前端传过来的, 而传过来的数据已经经过了前端的校验.
        # 换言之, 这里如果校验就属于 二次校验了.  实际开发中, 重要字段会做二次校验, 否者可以不做校验.
        # if money > 0:
        #     self.__money = money
        # else:
        #     self.__money = 0
        self.__money = money

    # 1.3 行为
    def make_cake(self):
        print(f'采用 {self.kongfu} 制作煎饼果子!')
        # 验证: 私有成员, 在本类中是可以直接访问的.
        print(f'私房钱为: {self.__money}')

# 2. 定义徒孙类, 继承自徒弟类.
class TuSun(Prentice):
    pass

# 在main函数中测试调用
if __name__ == '__main__':
    # 3. 创建徒孙类对象.
    ts = TuSun()
    # 4. 尝试访问父类的成员.
    # 父类的公共的 属性 和 行为.
    print(f'父类的属性: {ts.kongfu}')
    ts.make_cake()
    print("-" * 21)
    # 父类的 私有的 属性.
    # print(f'父类的私有属性: {ts.money}')   # 报错, AttributeError
    # print(f'父类的私有属性: {ts.__money}')   # 报错, AttributeError
    print(f'父类的私有属性, 通过 公共的方式访问: {ts.get_money()}')

    # 通过父类的公共方式, 修改 父类的私有属性.
    ts.set_money(10)

    print(f'父类的私有属性, 通过 公共的方式访问: {ts.get_money()}')

(二)、私有化方法介绍

目的

演示 私有化方法. 即: 父类的私有化方法, 也需要提供1个公共的访问方式, 让子类来访问

QA

问: 什么时候使用私有化呢?

答: 父类的成员不想被子类直接继承(或者重写, 修改等), 但是还想给子类用, 就可以考虑用私有化.

例如

python 复制代码
# 1. 定义徒弟类, 有自己的属性 和 行为.
class Prentice(object):
    # 1.1 属性
    def __init__(self):
        self.kongfu = '[独创的煎饼果子配方]'
        # 私有的属性.
        # self.__money__ = 500000     # 这个不是私有, 就是变量名叫: __money__
        self.__money = 500000         # 这个才是私有化的写法.

    # 1.2 对外提供公共的访问方式, 可以实现: 获取私有的变量, 以及给变量设置值.
    # 获取值.
    def get_money(self):
        return self.__money

    # 设置值
    def set_money(self, money):
        self.__money = money

    # 1.3 行为
    def __make_cake(self):
        print(f'采用 {self.kongfu} 制作煎饼果子!')
        # 验证: 私有成员, 在本类中是可以直接访问的.
        print(f'私房钱为: {self.__money}')

    # 针对于父类的私有方法, 提供公共的访问方式(在其内部调用 私有化的方法即可)
    def my_make(self):
        # 调用私有化方法 __make_cake()
        self.__make_cake()

# 2. 定义徒孙类, 继承自徒弟类.
class TuSun(Prentice):
    # 演示用, 在子类中 恶意的修改 父类的函数内容. 通过 方法重写 实现.
    # def make_cake(self):
    #     print('加入调料: 砒霜')
    #     print('加入调料: 鹤顶红')
    #     print('加入调料: 含笑半步癫')
    #     print('加入调料: 一日断肠散')
    #     # 调用父类的方法.
    #     super().make_cake()

    pass

# 在main函数中测试调用
if __name__ == '__main__':
    # 3. 创建徒孙类对象.
    ts = TuSun()

    # 4. 尝试访问父类的成员.
    # 4.1 父类的 私有的 属性.
    print(f'父类的私有属性, 通过 公共的方式访问: {ts.get_money()}')
    # 通过父类的公共方式, 修改 父类的私有属性.
    ts.set_money(10)
    print(f'父类的私有属性, 通过 公共的方式访问: {ts.get_money()}')
    print('-' * 21)

    # 4.2 父类的 私有的 方法(行为).
    # ts.__make_cake()        # AttributeError, 父类私有成员(方法), 子类无法直接访问.
    # ts.make_cake()
    ts.my_make()

3、多态

概述

多态指的是同一个事物在不同时刻, 不同场景下表现出来的不同形态, 状态

大白话解释

Python中的多态: 同一个函数 接收不同的参数 会有不同的结果.

现实生活中的多态: 一杯水, 高温 => 气体, 常温 => 液体, 低温 => 固体

前提条件

  • 要有继承关系. # 扩展: 没有继承关系也行, 因为Python是弱类型的, 对数据的类型限定不严格, 可以称之为 => 伪多态.
  • 要有方法重写, 否则无意义.
  • 要有父类引用指向子类对象.

好处

提高代码的可维护性. 即: 同样的一个函数, 未来需求变化了, 我们传入不同的参数即可, 无需修改源码, 既有不同的结果。开发原则, 对修改关闭, 对扩展开放. 大白话: 需求变化了, 不能该源码, 尽量加代码。

弊端

不知道传入的是哪一个具体的子类, 所以无法直接访问子类的特有成员

例如

python 复制代码
# 1. 定义父类, 动物类, 有个speak()函数.
class Animal(object):
    def speak(self):
        pass

# 2. 定义子类, 狗类, 继承自动物类, 重写Animal#speak()函数.
class Dog(Animal):
    # 重写父类的speak()函数
    def speak(self):
        print('汪汪汪!')

# 3. 定义子类, 猫类, 继承自动物类, 重写Animal#speak()函数.
class Cat(Animal):
    def speak(self):
        print('喵喵喵!')

    def catch_mouse(self):
        print('猫会抓老鼠!')

# 4.假设需求变化, 增加了 猴子类.
class Monkey(Animal):
    def speak(self):
        print('桀桀桀!')

# 验证Python是伪多态, 即: print_animal()函数, 不传入Animal的子类的对象, 也行.
class Phone:
    def speak(self):
        print('手机叫一次, 你要唱一首歌!')

# 5. 定义函数, 接收Animal类型, 调用speak()函数.
# def print_animal(an):   # an:Animal = Dog(),   an:Animal = Cat(), 父类引用指向子类对象.
def print_animal(an: Animal):   # an:Animal = Dog(),   an:Animal = Cat(), 父类引用指向子类对象.
    an.speak()
    # an.catch_mouse() 不能访问子类的 特有成员.


# 6. 在main函数中测试
if __name__ == '__main__':
    # 6.1 创建猫对象, 狗对象.
    cat = Cat()
    dog = Dog()
    mon = Monkey()

    # 6.2 调用print_animal()函数.
    # 发现: 同一个函数, 接受不同的对象, 实现效果不一样 => 多态.
    print_animal(cat)
    print('-' * 21)
    print_animal(dog)
    print('-' * 21)
    print_animal(mon)
    print('-' * 21)

    # 7. 调用print_animal()函数, 传入 非Animal类的子类.
    phone = Phone()
    print_animal(phone)

三、其他特征

1、抽象类

概述

有抽象方法的类就叫 抽象类, 也可以称之为: 接口

抽象方法

没有方法体的方法, 叫: 抽象方法, 即: 方法体是用 pass 来编写的

目的

抽象类: 一般充当父类, 用于制定: 标准.

子类: 普通类 继承抽象类, 重写抽象方法, 提供具体的实现即可

例如

python 复制代码
# 1. 定义AC类(空调类, 抽象类), 表示: 空调的标准.
class AC(object):
    # 1.1 制冷, 抽象方法(没有方法体的方法)
    def cool_wind(self):
        pass

    # 1.2 热风
    def hot_wind(self):
        pass

    # 1.3 左右摆风
    def swing_l_r(self):
        pass

# 2. 定义Gree类(格力空调类), 继承: AC类.
class Gree(AC):
    # 2.1 重写 AC#cool_wind 方法
    def cool_wind(self):
        print('格力空调 核心制冷技术 制作冷风')

    # 2.2 重写 AC#hot_wind 方法
    def hot_wind(self):
        print('格力空调 核心制热技术 制作热风')

    # 2.3 重写 AC#swing_l_r 方法
    def swing_l_r(self):
        print('格力空调 左右摆风!')

# 3. 定义Media类(美的空调类), 继承: AC类.
class Media(AC):
    # 3.1 重写 AC#cool_wind 方法
    def cool_wind(self):
        print('美的空调 核心制冷技术 制作冷风')

    # 3.2 重写 AC#hot_wind 方法
    def hot_wind(self):
        print('美的空调 核心制热技术 制作热风')

    # 3.3 重写 AC#swing_l_r 方法
    def swing_l_r(self):
        print('美的空调 左右摆风!')

# 定义函数, 测试空调的性能.
def my_ac(ac: AC):
    ac.cool_wind()
    ac.hot_wind()
    ac.swing_l_r()

# 4. 在main函数中测试.
if __name__ == '__main__':
    # 非多态方式
    # 4.1 测试 格力空调.
    g = Gree()
    g.cool_wind()
    g.hot_wind()
    g.swing_l_r()
    print('-' * 21)

    # 4.2 测试 美的空调.
    m = Media()
    m.cool_wind()
    m.hot_wind()
    m.swing_l_r()
    print('-' * 21)

    # 5. 多态方式.
    my_ac(g)
    print('-' * 21)
    my_ac(m)

2、类属性和对象属性

(一)、对象属性

概述

属于 对象的属性, 即: 每个对象都有, 且A对象的属性值修改了 不会影响 B对象的属性值

定义格式

类外: 对象名.属性名 = 属性值

类内: 写到 init魔法方法中, self.属性名 = 属性值

调用格式

类外: 对象名.属性名

类内: self.属性名

(二)、类属性

概述

属于 类的属性, 即: 可以被 该类下所有的对象所共享. 即: 无论是谁修改了这个变量的值, 之后大家用的都是修改后的

定义格式

定义在类中, 方法外的位置, 写法和以前我们写变量的格式 一样

调用格式

方式1: 类名.属性名

方式2: 对象名.属性名 可以这样写, 但是不推荐

细节

修改类属性必须通过 类名.属性名 = 属性值 的方式来修改, 不能通过 对象名.属性名 = 属性值的方式来修改.

因为: 前者是在修改 类属性的值, 后者是在 给对象新增1个属性

(三)、代码

python 复制代码
# 1. 定义学生类.
class Student:
    # 1.1 定义 类属性(类变量).
    teacher_name = '菩提'

    # 1.2 对象属性, 类内 设置.
    def __init__(self):
        self.name = '张三'    # 对象属性, 该类的每个对象都有.

# 在main函数中测试.
if __name__ == '__main__':
    # 2. 创建学生类对象.
    s1 = Student()
    s2 = Student()

    # 3. 对象属性, 在类外 设置 对象属性.
    s1.name = '李四'
    s1.age = 21     # 只有s1对象有.

    # 4. 对象属性, 在类外 获取 对象属性.
    print(f'类外获取对象属性值: {s1.age}')       # 21
    print(f'类外获取对象属性值: {s1.name}')      # 李四
    print(f'类外获取对象属性值: {s2.name}')      # 张三
    print("-" * 21)

    # 5. 在类外, 访问类属性
    # 方式1: 类名. 的方式
    print(Student.teacher_name)     # 菩提
    # 方式2: 对象名. 的方式, 可以, 但是不推荐.
    print(s1.teacher_name)          # 菩提
    print(s2.teacher_name)          # 菩提
    print("-" * 21)

    # 6. 修改类属性的值.
    Student.teacher_name = '唐僧'     # 可以修改 类属性值
    # s1.teacher_name = '唐僧'          # 不是在修改类变量的值, 而是在给s1对象新增1个属性值.

    # 7. 重新打印 类属性的值.
    # 方式1: 类名. 的方式
    print(Student.teacher_name)  # 唐僧
    # 方式2: 对象名. 的方式, 可以, 但是不推荐.
    print(s1.teacher_name)  # 唐僧
    print(s2.teacher_name)  # 唐僧

3、类方法和静态方法

(一)、类方法

概述

它表示属于类的方法, 可以被所有的对象共享

细节

  • 必须用装饰器 @classmethod 来修饰.

  • 第一个参数必须是 cls, 表示: 当前类的 引用, 即: 等价于 类名. 的形式

  • 类方法可以被 对象名. 或者 类名. 的方式来调用, 推荐使用 后者.

(二)、静态方法

概述

它表示属于 所有对象所共享 的方法, 可以被所有的对象共享

细节

  • 必须用装饰器 @staticmethod 来修饰.

  • 无参数要求, 根据需求来即可, 可传可不传.

  • 静态方法可以被 对象名. 或者 类名. 的方式来调用, 推荐使用 后者.

QA

问: 静态方法 和 类方法的区别?

答:

  1. 用的装饰器不同.

类方法: @classmethod

静态方法: @staticmethod

  1. 是否必须要传 第1个参数.

类方法: 必须传cls参数, 表示 当前类的引用.

静态方法: 根据需求来即可, 可传可不传.

问: 以后到底怎么选择用 静态方法 还是 类方法呢?

答:

看需求, 如果某个方法时被所有对象所共享, 就可以考虑用 静态 或者 类方法.

再看是否要使用 cls参数, 用 => 类方法, 不用 => 静态方法.

(三)、代码

python 复制代码
# 1. 定义学生类.
class Student:
    name = '码头'   # 类属性

    def __init__(self):
        self.age = 18     # 对象属性

    def method(self):
        print('我是method函数, 普通方法')

    @staticmethod
    def method2():
        print('我是method2方法, 我是静态方法')
        print(f'调用类属性: {Student.name}')

    @classmethod
    def method3(cls):   # cls来源于 class单词
        # self = 本类对象的引用, 类似于 对象名.
        # cls = 本类的引用, 类似于: 类名.
        print('我是method3方法, 我是类方法')
        print(f'cls表示类属性, 内容为: {cls}')  # <class '__main__.Student'>
        print(f'调用类属性: {cls.name}')

# 在main函数中测试.
if __name__ == '__main__':
    # 2. 创建对象.
    s1 = Student()

    # 3. 访问 静态方法
    Student.method2()       # 方式1: 类名.
    s1.method2()            # 方式2: 对象名.
    print('-' * 21)

    # 4. 访问 类方法
    Student.method3()      # 方式1: 类名.
    s1.method3()           # 方式2: 对象名.
相关推荐
尘浮生1 分钟前
Java项目实战II基于微信小程序的电影院买票选座系统(开发文档+数据库+源码)
java·开发语言·数据库·微信小程序·小程序·maven·intellij-idea
hopetomorrow15 分钟前
学习路之PHP--使用GROUP BY 发生错误 SELECT list is not in GROUP BY clause .......... 解决
开发语言·学习·php
小牛itbull25 分钟前
ReactPress vs VuePress vs WordPress
开发语言·javascript·reactpress
请叫我欧皇i33 分钟前
html本地离线引入vant和vue2(详细步骤)
开发语言·前端·javascript
nuclear201135 分钟前
使用Python 在Excel中创建和取消数据分组 - 详解
python·excel数据分组·创建excel分组·excel分类汇总·excel嵌套分组·excel大纲级别·取消excel分组
闲暇部落36 分钟前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
GIS瞧葩菜1 小时前
局部修改3dtiles子模型的位置。
开发语言·javascript·ecmascript
chnming19871 小时前
STL关联式容器之set
开发语言·c++
Lucky小小吴1 小时前
有关django、python版本、sqlite3版本冲突问题
python·django·sqlite
熬夜学编程的小王1 小时前
【C++篇】深度解析 C++ List 容器:底层设计与实现揭秘
开发语言·数据结构·c++·stl·list