第十章_面向对象(二)——《人人都能学会的Python零基础教程》
程序员四次元ポケット
编辑于 2023年05月31日 18:27
收录于文集
共19篇

封装

学习目标:

  • 理解封装的概念

  • 掌握私有成员的使用

面向对象的三大特性

面向对象编程,是许多编程语言都支持的一种编程思想。

基于前面的学习,我们可以简单的理解为面向对象编程,简单理解是:基于模板(类)去创建实体(对象),使用对象完成功能开发。

面向对象包含3大主要特性:

  • 封装

  • 继承

  • 多态

那么本小节我们就先学习封装这个主要的特性。

封装

封装表示的是,将现实世界事物的:

  • 属性

  • 行为

封装到类中,描述为:

  • 成员变量

  • 成员方法

从而完成程序对现实世界事物的描述

封装其实很简单,我们将现实世界中的事物的属性和行为在类中描述为成员变量、成员方法。那通过这个步骤,我们的类是不是完成了对现实世界事物的描述,那就相当于把现实世界的事物封装到了我们的程序内部。所以说封装这个特性你也可以认为它是一个思想,它是指导我们如何将现实世界中的事物描述为我们程序中的类。

对用户隐藏的属性和行为

现实世界中的事物,有属性和行为。

但是不代表这些属性和行为都是开放给用户使用的。

比如我们以现实生活中的手机为例,那么对于手机它会有属性和行为,比如说序列号、品牌、型号、长宽高这些基础属性,以及上网通话和拍照这些基础的行为,那这些属性和行为其实都是对用户开放的属性和行为。 那么除了对用户开放以外,手机中也会有一系列对用户隐藏的属性和行为,比如说手机的运行电压驱动信息,这些属性我们用户一般是无法获取的,另外包括像程序调度、内存管理这些行为也不是对我们用户开放的,那所以说我们的手机它会既有对用户开放的属性和行为,也会有对用户隐藏的属性和行为。这就像前两年非常火的苹果越狱、安卓 root 一样,那这些操作其实本质上就是为了突破权限去使用这些对用户隐藏的属性和行为。

私有成员

既然我们现实世界中的事物它有不公开的属性和行为,那么作为现实世界中事物在程序中映射的类,是不是也应该支持对私有的不公开的属性和行为进行描述?那这个是支持的,我们的类中其实提供了一个叫做私有成员的形式来去支持这种行为,也就是私有成员变量和私有成员方法,那他们的定义其实也非常的简单,只需要在私有成员变量或者说私有成员方法的名称前面,以两个下划线作为开头,那么这个属性或者说方法就变成了私有的了。

类中提供了私有成员的形式来支持。

  • 私有成员变量

  • 私有成员方法

定义私有成员的方式非常简单,只需要:

  • 私有成员变量:变量名以__开头(2个下划线)

  • 私有成员方法:方法名以__开头(2个下划线)

即可完成私有成员的设置

如下代码所示:

代码块
JavaScript
自动换行
复制代码
class Phone:
    IMEI = None # 序列号
    producer = None # 厂商

    __current_voltage = None # 当前电压 私有成员变量

    def call_by_5g(self):
        print("5g通话已开启")
    def __keep_single_core(self):
        print("让CPU以单核模式运行以节省电量") # 私有成员方法
复制成功

可以看到我们定义了一个私有成员变量,它的开头以两个下划线作为开头,以及一个私有的成员方法,它的名称也是以两个下划线作为开头。好,你只要这样写,它就变成私有的了。那我们再来想一下,在我们的现实世界中,这些不公开的属性和行为我们用户是无法直接使用的,那么程序中我们定义的这些私有的成员他是否能够满足?

使用私有成员

私有方法无法直接被类对象使用

代码块
JavaScript
自动换行
复制代码
class Phone:
    IMEI = None # 序列号
    producer = None # 厂商
    __current_voltage = None # 当前电压

    def call_by_5g(self):
        print("5g通话已开启")
        def __keep_single_core(self):
            print("让cPU以单核模式运行以节省电量")
phone = Phone() # 创建对象
phone.__keep_single_core() # 使用私有方法
复制成功

私有变量无法赋值,也无法获取值

使用私有成员

私有成员无法被类对象使用,但是可以被其它的成员使用。

代码块
JavaScript
自动换行
复制代码
class Phone:
    IMEI = None     # 序列号
    producer = None # 厂商
    __current_voltage = None # 当前电压

    def call_by_5g(self):
        if self.__current_voltage >= 1:
            self._keep_single_core()
            print("5g通话已开启")
        else:
            print("通话失败,电量不足")
    def __keep_single_core(self):
        print("让CPU以单核模式运行以节省电量")
复制成功

私有成员的定义我们已经了解了,但是:

它有什么实际的意义吗?

在类中提供仅供内部使用的属性和方法,而不对外开放(类对象无法使用)

设计带有私有成员的手机

设计一个手机类,内部包含:

  • 私有成员变量:__is_5g_enable,类型bool,True表示开启5g,False表示关闭5g

  • 私有成员方法:check_5g(),会判断私有成员is_5g_enable的值

  • 若为True,打印输出:5g开启

  • 若为False,打印输出:5g关闭,使用4g网络

  • 公开成员方法:call_by_5g(),调用它会执行

  • 调用私有成员方法:__check_5g(),判断5g网络状态

  • 打印输出:正在通话中

运行结果:

代码块
JavaScript
自动换行
复制代码
5g关闭,使用4g网络 
正在通话中
复制成功

通过完成这个类的设计和使用,体会封装中私有成员的作用

  • 对用户公开的,call_by_5g()方法

  • 对用户隐藏的,is_5g_enable私有变量和check_5g私有成员

继承的基本语法

学习目标

  • 理解继承的概念

  • 掌握继承的使用方式

  • 掌握pass关键字的作用

继承的引出

首先,我们来看一下苹果手机图片。同学们都知道,从第六代开始,苹果手机不断更新,推出了6S、7和8等新款手机。虽然每一代手机外观看起来差不多,但每一代手机都有自己的设计图。那么作为设计师,我们可以选择两种方法。第一种是每一代手机都从零开始设计新的设计图。不过,更好的方法是基于老款的设计图来进行修改,这样就可以轻松地得到新款手机的设计图。你会选哪种呢?相信大部分同学都会选择第二种方法,因为新款和老款手机并没有很大的区别。这个问题很符合我们继承的思想。实际上,产生的新设计图本质上是继承老的设计图的,相当于复制粘贴了一份,然后进行修改。

类似的逻辑也存在于编程中。比如,我们定义了一个手机类,它具有一些基础属性,如序列号和厂商,以及一个基础方法:4G通话。然而,到了2022年,手机都支持面部识别和5G通话了。因此,我们需要创建一个新的手机类,比如称为“Phone2022”,并增加面部识别和5G通话的功能。看起来一切都很好,但是这种写法仍然有一些繁琐的地方。同样地,假设我们让你来设计这个类,你会选择从头到尾编写一个全新的类,还是基于旧的类进行修改?请告诉我你更倾向于哪种方法。我相信大部分同学都会选择基于旧类进行修改。

代码块
JavaScript
自动换行
复制代码
class phone:
    IMEI = None # 序列号
    producer = None # 厂商
    def call_by_4g(self):
        print("4g通话")
class phone2022:
    IMEI = None # 序列号
    producer = None # 厂商
    face_id = True # 面部识别

    def call_by_4g(self):
        print("4g通话")

    def call_by_5g(self):
        print("2022最新5g通话")
复制成功

可以直接写成 :

代码块
JavaScript
自动换行
复制代码
class phone2022(Phone):
    face_id = True # 面部识别

    def call_by_5g(self):
        print("2022最新5g通话")
复制成功

继承分为:单继承和多继承,如下代码,可以完成类的单继承。 继承表示:将从父类那里继承(复制)来成员变量和成员方法(不含私有)

代码块
JavaScript
自动换行
复制代码
class 类名(父类名):
    类内容体
复制成功

多继承

接下来,让我们来学习关于继承的另一种形式:多继承。我们说继承分为单继承和多继承两种形式。单继承指的是一个子类继承一个父类,而多继承则表明一个子类继承多个父类。

举个例子,我们可以以小米手机为例。如果我们将小米手机看作子类,可以认为它有三个父类。首先,小米手机本身是一款手机,这是毫无疑问的。另外,我们也知道安卓手机可以作为 NFC 读卡器使用,用于门禁卡、公交卡等的复制。此外,小米手机也可以作为红外遥控器使用,用于电视、空调等设备的控制。一个看似简单的小米手机不仅仅是一部手机,还包含了许多额外的功能。

现在我们来用这个例子来描述多继承的概念。假设在我们的程序中,我们定义了三个类:手机、NFC读卡器和红外遥控器。有了这三个类之后,我们可以构建一个小米手机,让它同时继承这三个类,这样小米手机就可以拥有它们三个类的所有属性和方法。这个想法是可行的。

现在让我们来看一下多继承的语法。与单继承类似,我们只需要在子类中的括号里写出所有要继承的父类名字即可。不同之处在于,多继承需要使用逗号来分隔不同的父类名字。如果要继承多个父类,只需在逗号后面继续添加父类名即可。因此,多继承的语法非常简单。 Python的类之间也支持多继承,即一个类,可以继承多个父类:

代码块
JavaScript
自动换行
复制代码
class 类名(父类1,父类2,……,父类N):
    类内容体
class Phone:
    IMEI = None # 序列号
    producer = None # 厂商

def call_by_5g(self):
    print("5g通话")

class NFCReader:
    nfc_type="第五代"
    producer = "HM"

    def read_card(self):
        print("读取NFC卡")

    def write_card(self):
        print("写入NFC卡")

class Remotecontrol:
    rc_type="红外遥控"

    def control(self):
        print("红外遥控开启")
class Myphone(Phone,NFCReader,RemoteControl):
    pass
复制成功

多继承注意事项

多个父类中,如果有同名的成员,那么默认以继承顺序(从左到右)为优先级。

即:先继承的保留,后继承的被覆盖

代码块
JavaScript
自动换行
复制代码
my_phone = Myphone()
print(my_phone.producer) # 结果为None而非"HM"
复制成功

复写和使用父类成员

  • 掌握如何在子类中调用父类成员

  • 掌握如何在子类中调用父类成员

复写

让我们来了解一下继承中的一个概念——复写。复写指的是在子类继承父类的成员属性和成员方法后,如果对父类提供的功能不满意,我们可以对其进行修改,这个修改就被称为复写。

具体使用起来也非常简单。比如我们有这么一段包含4列代码,父类中有一个属性producer和一个名为call_by_5g的方法。子类继承了父类后就拥有了这个属性和方法。但是如果我们觉得父类中的内容并不满足我们的需求,比如我们想把生产厂商从ITCAST改成ITHEIMA,我们就可以直接在子类中修改这个属性。

除此之外,如果我们还想对父类中的call_by_5g方法进行修改,同样可以在子类中重新定义这个方法,这就叫做复写父类的成员方法。

总之,复写非常简单,只需要重新定义不满意的部分即可。

子类继承父类的成员属性和成员方法后,如果对其“不满意”,那么可以进行复写。

代码块
JavaScript
自动换行
复制代码
class Phone:
    IMEI = None # 序列号
    producer="ITCAST" # 厂商

    def call_by_5g(self):
        print("父类的5g通话")

class MyPhone(Phone):
    proucer = "ITHEIMA" # 复写父类属性

    def call_by_5g(self): # 复写父类方法
        print("子类的5g通话")
复制成功

调用父类同名成员

一旦复写父类成员,那么类对象调用成员的时候,就会调用复写后的新成员

如果需要使用被复写的父类的成员,需要特殊的调用方式:

方式1:

  • 调用父类成员      

  • 使用成员变量:父类名.成员变量      

  • 使用成员方法:父类名.成员方法(self)

方式2:

  • 使用super()调用父类成员      

  • 使用成员变量:super().成员变量      

  • 使用成员方法:super().成员方法()

代码块
JavaScript
自动换行
复制代码
class Phone:
    IMEI = None # 序列号
    producer="ITCAST" # 厂商
    def call_by_5g(self):
        print("父类的5g通话")

class MyPhone(Phone):
    proucer = "ITHEIMA"

    def call_by_5g(self):
        # 方式1调用父类成员
        print(f"父类的品牌是:{Phone.producer}")
        Phone.call_by_5g(self)

        # 方式2调用父类成员
        print(f"父类的品牌是:{super().producer}")
        superO.call_by_5g()
        print("子类的5g通话")
复制成功

变量的类型注解

学习目标:

  • 理解为什么使用类型注解

  • 掌握变量的类型注解语法

为什么需要类型注解

在PyCharm中编写代码,我们经常能够见到如下提示:

自动提示可用方法 思考,为什么PyCharm工具能够做到这一点? 它是如何知道这个对象有append方法?

因为:PyCharm确定这个对象,是list类型

为什么需要类型注解

同样,我们换一份代码: 定义一个函数func,接收一个参数data 你会发现,PyCharm不会在做出任何提示了

思考,为什么PyCharm工具无法提示了?

因为:PyCharm不确定这个对象是什么类型

提示传入2个参数,类型是int

仅能提示传入1个参数data,类型未知

又或者当我们调用方法,进行传参的时候(快捷键ctrl + p弹出提示):

为什么内置模块random的方法可以提示类型

自己定义的就不可以?

因为PyCharm无法通过代码

确定应传入什么类型

我们需要使用类型注解

类型注解

Python在3.5版本的时候引入了类型注解,以方便静态类型检查工具,IDE等第三方工具。

类型注解:在代码中涉及数据交互的地方,提供数据类型的注解(显式的说明)。

主要功能:

  • 帮助第三方IDE工具(如PyCharm)对代码进行类型推断,协助做代码提示

  • 帮助开发者自身对变量进行类型注释

支持:

  • 变量的类型注解

  • 函数(方法)形参列表和返回值的类型注解

类型注解的语法

为变量设置类型注解好

基础语法:

变量: 类型

代码块
JavaScript
自动换行
复制代码
#基础数据类型注解
var_1:int = 10
var_2:float = 3.1415926
var3:bool = True
var4:str "itheima"
复制成功

类对象类型注解

代码块
JavaScript
自动换行
复制代码
# 类对象类型注解
class student:
    pass
stu:Student = StudentO
复制成功

基础容器类型注解

代码块
JavaScript
自动换行
复制代码
# 基础容器类型注解
my_1ist:1ist=[1,2,3]
my_tuple: tuple = (1,2,3)
my_set:set = {1,2,3}
my_dict:dict = {itheima":666}
my_str:str ="itheima"
# 容器类型详细注解
my_list : list[int] = [1,2,3]
my_tuple : tuple[str,int,bool] = ("itheima",666,True)
my_set : set[int] = {1,2,3}
my_dict : dict[str,int] = {"itheima":666}
# 在注释中进行类型注解
class Student:
    pass
var_1 = random.randint(1,10) # type:int
var_2 = json.loads(data)     # type:dict[str,int]
var_3 = func()               # type:Student
复制成功

为变量设置注解,显示的变量定义,一般无需注解:

代码块
JavaScript
自动换行
复制代码
var_1:int = 10
var_2:1ist=[1,2,3]
var_3:dict ={itheima":666}
var_4:student = Student()
复制成功

如上,就算不写注解,也明确的知晓变量的类型

代码块
JavaScript
自动换行
复制代码
class Student:
    pass
var_1:int = random.randint(1,10)
var_2:dict = json.loads(data)
var_3:student = func()
复制成功

一般,无法直接看出变量类型之时,会添加变量的类型注解

注意:

元组类型设置类型详细注解,需要将每一个元素都标记出来

字典类型设置类型详细注解,需要2个类型,第一个是key第二个是value

类型注解的限制

类型注解主要功能在于:

  • 帮助第三方IDE工具(如PyCharm)对代码进行类型推断,协助做代码提示

  • 帮助开发者自身对变量进行类型注释(备注)

并不会真正的对类型做验证和判断。

也就是,类型注解仅仅是提示性的,不是决定性的

代码块
Python
自动换行
复制代码
var_1:int "itheima
var2:str 123
复制成功

如图代码,是不会报错的哦。

函数(方法)的类型注解

学习目标:

  • 掌握为函数(方法)形参进行类型注解

  • 掌握为函数(方法)返回值进行类型注解

函数(方法)的类型注解 - 形参注解

让我们来了解一下在函数和方法中进行形参类型注解的方法。在我们定义函数或方法时,如果其中有形参,例如在图片中定义了一个名为 data 的形参,我们会发现当我们在使用这个函数时输入 data. 后,没有出现任何的提示。这是因为 Pycharm 不知道这个形参具体应该是什么类型,而且当我们使用这个函数时,它的提示框中也无法提示传入参数的类型。这是因为我们没有对形参进行类型注解。

函数(方法)的类型注解 - 返回值注解

同时,函数(方法)的返回值也是可以添加类型注解的。

函数和方法的形参类型注解语法:

函数(方法)的类型注解 - 返回值注解

同时,函数(方法)的返回值也是可以添加类型注解的。

语法如下:

代码块
Python
自动换行
复制代码
def 函数方法名(形参:类型,.....,形参:类型)->返回值类型:
    pass
def add(x:int,y:int)->int
    return x+y
def func(data:list[int])-> list[int]:
    pass
复制成功

Union类型

学习目标:

  • 理解Union类型

  • 掌握使用Union进行联合类型注解

Union类型

让我们来了解一下在复杂数据类型中如何使用联合类型注解。在图片中,我们可以看到列表中存储的是 Int 和字符串类型的元素,而字典中的 value 则既包含字符串也包含数字。对于这种混合的数据类型,我们需要使用到联合类型注解,这就是 union 类型的作用。

代码块
Python
自动换行
复制代码
my_list:list[int] = [1,2,3]
my_dict:dict[str,int] = {"age":11,"num":3}
my_list = [1,2,"itcast","itheima"]
my_dict = {"name":"周杰轮","age":31}
复制成功

首先我们需要导入 typing 中的 Union 类。然后我们可以在列表的类型注解中写成 Union[s, str, int],表示列表中既可以是字符串类型,也可以是 Int 类型。同样的,对于字典中的 value,我们也可以使用 Union[str, int] 进行类型注解,表示 value 可以是字符串或者是数字类型,二选一。

使用 union 类型注解的语法非常简单,我们只需要导入 Union 类之后,将要注解的类型放在中括号里即可,多种类型使用逗号进行分隔。

同时,我们可以对函数的形参和返回值进行 union 类型注解。在图片中的示例代码中,我们可以看到在函数的形参中使用了 union 类型注解,返回值同样也使用了 union 类型注解。union 类型注解非常方便,既可以对变量进行注解,也可以对函数进行注解。

代码块
Python
自动换行
复制代码
from typing import Union
my_list:list[Union[str,int]] = [1,2,"itcast","itheima"]
my_dict:dict[str,Union[str,int]]={"name":"周杰轮","age":31}
复制成功

使用Union[类型, ......, 类型]

可以定义联合类型注解

Union联合类型注解,在变量注解、函数(方法)形参和返回值注解中,均可使用。

代码块
Python
自动换行
复制代码
my_list:list[Union(int,str)] = [1,2,"itcast","itheima"]
my_dict:dict[str,Union[str,int]] = {"name":"周杰轮","age":31}
def func(data: Union[int,str])-> Union[int,str]:
    pass
复制成功

多态

学习目标:

  • 理解多态的概念

  • 理解抽象类(接口)的编程思想

多态实际上指的是多种状态。当我们完成某个具体的行为时,使用不同的对象就会得到不同的状态。那么如何理解这句话呢?我们可以看一下如下示例代码。在我们的 PPT 中有一段代码定义了一个 animal 类,其中有一个方法 speak 表示动物发出声音。然后我们定义了两个子类——狗和猫,它们都继承了 animal 类并且复写了 speak 方法。比如,狗的叫声输出为“汪汪汪”,猫的叫声输出为“喵喵喵”。

你可能会觉得这很普通,这不是很常见的继承和复写吗?但是,让我们现在假设我们定义了一个函数,该函数需要接收一个类型为 animal 的对象。我们按照先前学习的类型注释为对象添加了注释,以表明它是 animal 类型的。然后我们调用 speak 方法,让动物叫起来。这是一个普通的函数,但是我们要看一看它是如何使用的。我们创建了狗和猫这两个子类的对象。然后我们并没有传入父类对象,而是在调用函数时,我们传入了狗和猫,然后它会输出“汪汪汪”和“喵喵喵”。

这其实就是多态的体现,我们完成了某个具体的行为——即调用函数,但是我们传入了不同的对象,从而得到了不同的状态。比如,我们传入狗就会输出“汪汪汪”,传入猫就会输出“喵喵喵”。 你可能会问,老师你不是说需要传入 animal 这个父类的对象吗?但是,为什么传入子类对象它依旧可以正常运行呢?我们来想一下,我们的父类中是不是有 speak 方法呢?既然子类都继承了父类,子类中也一定有 speak 方法。因此,我们传入子类对象不会报错,每个子类都会有 speak 方法。因此,我们可以再次强调,这就是多态的基本概念:使用一个同样的行为(例如函数),但是传入不同的对象可以得到不同的运行状态。

多态常作用在继承关系上。

比如

函数(方法)形参声明接收父类对象

实际传入父类的子类对象进行工作

即:

  • 以父类做定义声明

  • 以子类做实际工作

  • 用以获得同一行为, 不同状态

抽象类(接口)

细心的同学可能发现了,父类Animal的speak方法,是空实现

这种设计的含义是:

  • 父类用来确定有哪些方法

  • 具体的方法实现,由子类自行决定

这种写法,就叫做抽象类(也可以称之为接口)

抽象类:含有抽象方法的类称之为抽象类

抽象方法:方法体是空实现的(pass)称之为抽象方法

为什么要使用抽象类呢?

提出标准后,不同的厂家各自实现标准的要求。

抽象类就好比定义一个标准,包含了一些抽象的方法,要求子类必须实现。

综合案例

学习目标

  • 使用面向对象思想完成数据读取和处理

  • 基于面向对象思想重新认知第三方库使用(PyEcharts)

数据分析案例

某公司,有2份数据文件,现需要对其进行分析处理,计算每日的销售额并以柱状图表的形式进行展示。 数据内容

1月份数据是普通文本,使用逗号分割数据记录,从前到后分别是(日期,订单id,销售额,销售省份)

2月份数据是JSON数据,同样包含(日期,订单id,销售额,销售省份)

需求分析 作为面向对象的程序员,我们全程将使用面向对象的思想来进行任务的开发。 data_define.py

代码块
Python
自动换行
复制代码
"""
数据定义的类
"""


class Record:

    def __init__(self, date, order_id, money, province):
        self.date = date            # 订单日期
        self.order_id = order_id    # 订单ID
        self.money = money          # 订单金额
        self.province = province    # 销售省份


    def __str__(self):
        return f"{self.date}, {self.order_id}, {self.money}, {self.province}"
复制成功

file_define.py

代码块
Python
自动换行
复制代码
"""
和文件相关的类定义
"""
import json

from data_define import Record


# 先定义一个抽象类用来做顶层设计,确定有哪些功能需要实现
class FileReader:

    def read_data(self) -> list[Record]:
        """读取文件的数据,读到的每一条数据都转换为Record对象,将它们都封装到list内返回即可"""
        pass


class TextFileReader(FileReader):

    def __init__(self, path):
        self.path = path            # 定义成员变量记录文件的路径

    # 复写(实现抽象方法)父类的方法
    def read_data(self) -> list[Record]:
        f = open(self.path, "r", encoding="UTF-8")

        record_list: list[Record] = []
        for line in f.readlines():
            line = line.strip()     # 消除读取到的每一行数据中的\n
            data_list = line.split(",")
            record = Record(data_list[0], data_list[1], int(data_list[2]), data_list[3])
            record_list.append(record)

        f.close()
        return record_list


class JsonFileReader(FileReader):

    def __init__(self, path):
        self.path = path            # 定义成员变量记录文件的路径


    def read_data(self) -> list[Record]:
        f = open(self.path, "r", encoding="UTF-8")

        record_list: list[Record] = []
        for line in f.readlines():
            data_dict = json.loads(line)
            record = Record(data_dict["date"], data_dict["order_id"], int(data_dict["money"]), data_dict["province"])
            record_list.append(record)

        f.close()
        return record_list


if __name__ == '__main__':
    text_file_reader = TextFileReader("D:/2011年1月销售数据.txt")
    json_file_reader = JsonFileReader("D:/2011年2月销售数据JSON.txt")
    list1 = text_file_reader.read_data()
    list2 = json_file_reader.read_data()

    for l in list1:
        print(l)

    for l in list2:
        print(l)
复制成功

main.py

代码块
Python
自动换行
复制代码
"""
面向对象,数据分析案例,主业务逻辑代码
实现步骤:
1. 设计一个类,可以完成数据的封装
2. 设计一个抽象类,定义文件读取的相关功能,并使用子类实现具体功能
3. 读取文件,生产数据对象
4. 进行数据需求的逻辑计算(计算每一天的销售额)
5. 通过PyEcharts进行图形绘制
"""
from file_define import FileReader, TextFileReader, JsonFileReader
from data_define import Record
from pyecharts.charts import Bar
from pyecharts.options import *
from pyecharts.globals import ThemeType

text_file_reader = TextFileReader("D:/2011年1月销售数据.txt")
json_file_reader = JsonFileReader("D:/2011年2月销售数据JSON.txt")

jan_data: list[Record] = text_file_reader.read_data()
feb_data: list[Record] = json_file_reader.read_data()
# 将2个月份的数据合并为1个list来存储
all_data: list[Record] = jan_data + feb_data

# 开始进行数据计算
# {"2011-01-01": 1534, "2011-01-02": 300, "2011-01-03": 650}
data_dict = {}
for record in all_data:
    if record.date in data_dict.keys():
        # 当前日期已经有记录了,所以和老记录做累加即可
        data_dict[record.date] += record.money
    else:
        data_dict[record.date] = record.money

# 可视化图表开发
bar = Bar(init_opts=InitOpts(theme=ThemeType.LIGHT))

bar.add_xaxis(list(data_dict.keys()))       # 添加x轴的数据
bar.add_yaxis("销售额", list(data_dict.values()), label_opts=LabelOpts(is_show=False))      # 添加了y轴数据
bar.set_global_opts(
    title_opts=TitleOpts(title="每日销售额")
)

bar.render("每日销售额柱状图.html")
复制成功