B站python入门学习---第二阶段第一章类和对象

第二阶段

第一章 类和对象

#定义一个student类,并定义该类的属性
class Student:
    name = None
    gender = None
    national = None
    native_place = None
    age = None

#使用student类创建一个stu1对象
stu1 = Student()
#为stu1的属性赋值
stu1.name = "王华"
stu1.gender = ""
stu1.national = "中国"
stu1.native_place = "江苏"
stu1.age = 27

print(stu1.name, stu1.gender, stu1.national, stu1.native_place, stu1.age)
#定义一个student类
#类内部定义的数据称为成员属性
#类内部定义的函数称为成员方法
class Student:
    #成员属性
    name = None
    #成员方法
    def say_hi(self):
        print(f"大家好,我是{self.name}。")

#使用student类创建一个stu1对象
stu1 = Student()

#为stu1的成员属性赋值
stu1.name = "王华"
stu1.say_hi()

 

class Clock:
    id = None   #第一个成员属性,编号
    price = None   #第二个成员属性,价格

    def ring(self):     #第一个成员方法
        import winsound
        winsound.Beep(2000, 3000)

#创建一个闹钟对象
clock1 = Clock()
clock1.id = "0001"
clock1.price = 32
print(f"闹钟{clock1.id}的价格是{clock1.price}元。")
clock1.ring()

像上面的案例都是在创建对象后再通过“=”为各个对象的属性赋值,太繁琐,可以通过构造方法(__init__()方法):通过传参为成员属性赋值。

在创建类对象的时候,构造方法会自动执行,即将传入参数自动传递给__init__方法使用。


class Student:
name = None
age = None
number = None
main_teacher = None

def __init__(self, name, age, number, main_teacher):
self.name = name
self.age = age
self.number = number
self.main_teacher = main_teacher
print(f"已创建了一个{self.__class__.__name__}类的实例对象:{self.name},{self.age}岁,编号为{self.number},班主任是{self.main_teacher}。")

student1 = Student("刘云", 15, 1, "马建华")

#属性参数的传递也可以不按顺序,即关键字参数
studen2 = Student(age=16, number=2, name="吴明", main_teacher="马建华")
 

再进一步看,上面代码中起始类定义开始几行属性赋值为None的代码其实可以不需要了;

还要考虑一个情况就是:一些情况下,同一个类的对象的属性一般情况下都可能是相同的,比如上面示例中的属性main_teacher,那么在构造函数中可以采用默认参数的方式。

那么,针对上面代码,优化如下: 

class Student:

    def __init__(self, name, age, number, main_teacher="马建华"):
        self.name = name
        self.age = age
        self.number = number
        self.main_teacher = main_teacher
        print(f"已创建了一个{self.__class__.__name__}类的实例对象:{self.name},{self.age}岁,编号为{self.number},班主任是{self.main_teacher}。")

#实例的main_teacher如果不需要改变可以不传参
student1 = Student("刘云", 15, 1)
studen2 = Student("吴明", 16, 2)
#改变默认参数时需要明确传参
studen3 = Student("马浩然", 15, 3, main_teacher="罗小辉")

 

 

小练习:

 

"""
学生信息录入系统
请设计一个类,记录学生的信息:姓名,年龄,户籍城市
要求:
1、通过for循环,配合input语句,完成学生信息的键盘录入
2、输入完成后,使用print语句,完成学生信息的输出
"""
class Student:
    counter = 0
    def __init__(self, name, age, city):
        self.name = name
        self.age = age
        self.city = city
        Student.counter += 1
        print(f"学生{Student.counter}信息录入完成,信息为:【学生姓名:{self.name},年龄:{self.age},户籍城市:{self.city}】")

def stu_info_input():
    name = input("请输入学生姓名:")
    age = int(input("请输入学生年龄:"))
    city = input("请输入学生户籍城市:")
    return name, age, city

#创建一个列表存储创建的对象
student_list = []
for i in range(10):
    print(f"当前录入第{Student.counter+1}位学生信息,总共需要录入10位学生信息")
    student = Student(*stu_info_input())
    student_list.append(student)

for i in range(10):
    print(f"学生{i+1}:")
    print(f"姓名:{student_list[i].name}", end = "  ")
    print(f"年龄:{student_list[i].age}", end = "  ")
    print(f"户籍城市:{student_list[i].city}。")

 

面向对象的三大特性第一:封装

将现实世界事物的属性和行为封装到类之中,描述为成员变量和成员方法。

有时候,除了对用户开放的属性和行为,还有对用户隐藏的属性和行为。对应到类中即对应私有成员变量和私有成员方法,将其命名前面加上"__"即可。


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()
# print(phone.__current_voltage) #会报错,无法访问私有成员变量
print(Phone.__current_voltage) #通过类去访问同样报错
phone.__current_voltage = 25 #设置不会报错,因为这里并不是类定义中的私有成员变量,而是实例对象新增了一个同名变量并且赋值了
print(phone.__current_voltage) #打印的是上一句新增赋值的变量
phone.call_by_5g()
# phone.__keep_single_core() #使用私有成员方法会报错
# Phone.__keep_single_core() #通过类名去访问也会报错

我们通过上例可以看到,私有成员变量/方法对于类对象是无法使用的,那么他有什么作用呢?他可以被类中的其他成员使用,一般为内部使用。

如下例,私有成员变量和方法在类定义中被其他公开的成员方法访问使用:

class Phone:
    producer = None
    __current_voltage = 0.25   #私有成员变量

    def __keep_single_core(self):    #私有成员方法
        print("CPU以单核模式运行以节电!")

    def call_by_5g(self):
        if self.__current_voltage > 1:
            print("5G通话已开启!")
        else:
            print("电力不足,无法使用5G通话,并已设置为单核运行模式:")
            self.__keep_single_core()

phone = Phone()
phone.call_by_5g()

那么,会想到一个问题,如果通过在类中定义一个更改私有成员变量的公有方法,是不是对象就可以改变私有成员变量了呢?尝试一下:

class Phone:
    producer = None
    __current_voltage = 0.25   #私有成员变量

    def __keep_single_core(self):    #私有成员方法
        print("CPU以单核模式运行以节电!")

    def call_by_5g(self):
        if self.__current_voltage > 1:
            print("5G通话已开启!")
        else:
            print("电力不足,无法使用5G通话,并已设置为单核运行模式:")
            self.__keep_single_core()

    #定义一个可以调整私有成员变量的公开方法
    def set_voltage(self, voltage):
        self.__current_voltage = voltage

phone = Phone()
phone.call_by_5g()
phone.set_voltage(12)    #可以更改私有成员变量
phone.call_by_5g()    

可以看出,还是可以更改的。

小练习:

"""
设计一个手机类,内部包含:
1、私有成员变量:__is_5g_enable,bool类型,True表示开启5G,False表示关闭
2、私有成员方法:__check_5g(),判断__is_5g_enable的值
    —若为True,打印输出,5G开启
    —若为False,打印输出:5G关闭,使用4G网络
3、公开成员方法:call_by_5g():
    —调用__check_5g(),判断网络状态
    —打印输出:正在通话中
"""

class Phone():
    __is_5g_enable = False

    def __check_5g(self):
        if self.__is_5g_enable:
            print("5G开启!")
        else:
            print("5G关闭,使用4G网络!")

    def call_by_5g(self):
        self.__check_5g()
        print("正在通话中...")

myphone = Phone()
myphone.call_by_5g()

 

面向对象的三大特性第二:继承

单继承:

#类的继承

class Phone:
    IMEI = None        #序列号
    producer = "blackmerry"  #生产商

    def call_by_4g(self):
        print("4G通话中...")

#单继承的写法def 子类名(父类名)
class Phone2025(Phone):
        face_id = "10001"      #新的成员变量,面部识别ID

        def call_by_5g(self):
            print("2025年手机新功能:5G通话中...")

myphone = Phone2025()
print(myphone.producer)    #父类中定义的成员变量
myphone.call_by_4g()        #父类中定义的成员方法
myphone.call_by_5g()        #子类中定义的成员方法

 

多继承:

class Phone:
    IMEI = None
    producer = "apple"

    def call_by_4g(self):
        print("4G通话中...")

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("红外遥控开启了")


#多继承写法:子类名(父类1名,父类2名...)
class MyPhone(Phone, NFCReader, RemoteControl):
    pass

myphone2025 = MyPhone()
print(myphone2025.IMEI)        #继承自父类phone中的成员变量
print(myphone2025.nfc_type)   #继承自父类NFCReader中的成员变量
#如果多个父类中有同名成员,则按继承的先后顺序确定优先级
print(myphone2025.producer)  #输出继承自第一个父类Phone的producer
myphone2025.read_card()       #继承自父类NFCReader的成员方法
myphone2025.call_by_4g()      #继承自父类Phone的成员方法
myphone2025.control()           #继承自父类RemoteControl的成员方法

复写父类成员:

 

#复写父类成员

class Phone:
    IMEI = None
    producer = "ITCAST"

    def call_by_5g(self):
        print("This is Class Phone's method:正在5G通话中...")

class Myphone(Phone):
    producer = "ITHEIMA"        #复写父类中成员变量

    def call_by_5g(self):           #复写父类中成员方法
        Phone.call_by_5g(self)     #复写中当然也可以调用父类同名方法
        print("开启单核CPU运行!")
        print("This is Class MyPhone's method:正在5G通话中...")

phone = Myphone()
print(f"This is producer of Phone:{Phone.producer}.")
print(f"This is producer of Myphone:{phone.producer}")
phone.call_by_5g()

 

调用父类同名成员:

在复写后,正常的调用和访问只能是本类复写后的成员,如何访问父类的原同名成员呢?

一种是通过父类名:使用成员变量:父类名.成员变量 ;使用成员方法:父类名.成员方法(self),如上例代码中复写call_by_5g方法中调用父类方法的写法

还有一种通过super():使用成员变量:super().成员变量;使用成员方法:super().成员方法()

需要注意的是:在子类定义外部是无法通过对象访问父类成员的 : 子类重写父类成员后 , 通过子类实例对象调用该重写后的成员时 , 默认调用的就是 重写后的成员 ;无法通过super()去调用父类成员,只能通过父类名去调用。

#复写父类成员

class Phone:
    IMEI = None
    producer = "ITCAST"

    def call_by_5g(self):
        print("This is Class Phone's method:正在5G通话中...")

class Myphone(Phone):
    producer = "ITHEIMA"        #复写父类中成员变量

    def call_by_5g(self):           #复写父类中成员方法
        print("开启单核CPU运行!")
        # 复写中当然也可以调用父类同名方法
        #调用父类成员方式一
        Phone.call_by_5g(self)
        #调用父类成员方式二
        super().call_by_5g()
        print("This is Class MyPhone's method:正在5G通话中...")

phone = Myphone()
phone.call_by_5g()
#打印父类成员,可以通过父类名去引用
print(f"父类Phone的厂家是{Phone.producer}.")
#在类的定义外部,无法通过super()去调用父类对象,即外部无法用实例对象去调用父类
#print(f"父类Phone的厂家是{super().producer}.")
#上面代码会报错,包括phone.super()也报错——不提供该方法

类型注解:

主要功能是帮助如pycharm等IDE工具对代码进行类型推断,协助补全、提示等功能;另外也可以帮助开发者本身对变量进行类型注释。

其标准写法就是:变量:类型,如下示例代码:

#类型注解

#对变量的类型注解

a: int = 12
b: float = 3.14159
c: str = "abcxyz"
d: bool = True


#也可以对类进行类型注解
class Student:
    pass

stu:Student = Student()   #将变量stu注解为Student类类型

#对函数参数进行类型注解
def my_add(a:int, b:int):
    return a+b

print(my_add(1, 22))

#也可以对数据容器进行类型注解
#数据容器变量的简单注解
my_list:list = [1, 2, 3]
my_tuple:tuple = (1, 2, 3)
my_dict:dict = {"tom":19, "mike":20}
my_str:str = "itheima"
#数据容器变量的详细注解
my_list1:list[int] = [10, 20, 30]
my_tuple1:tuple[str, int, bool] = ("heima", 12, False)  #元组类型的详细注解需要把每个元素类型都注解
my_dict1:dict[str, int] = {"tom":19, "mike":20}   #字典类型需要把key和value类型都注解


#也可以在注释中进行类型注解
var_a = 18          #type:int
var_b = [1, 2, 100]  #type:list[int]
var_c = {"jenny" : "girl", "miachel" : "boy"}    #type:dict[str, str]
var_s = Student()             #type:Student

类型注解只是备注,标记,即便标记类型错误也不会报错。

上面示例中已有函数形参的类型注解,函数的返回值类型注解相对特殊一些:

#对函数参数、返回值进行类型注解
def my_add(a:int, b:int) -> int:
    return a+b

Union类型注解:

对混合数据类型的注解,如包含多种数据类型元素的列表、元组、集合等,再如不同类型键和值的字典:

#联合类型注解需要先导入Union模块
from typing import Union

my_lista: list[Union[str, int]] = [1, "abc", 18, "IT"]
my_dicta: dict[Union[str, int], Union[str, int]] = {"name": "Jay", "age": 45, 1:"青花瓷"}

#函数的形参和返回值同样可以使用Union联合注解方式
def func(data:Union[str, int]) -> Union[str, int]:
    return data

 

 

面向对象的三大特性第三:多态

同样的行为(函数),传入不同的对象,得到不同的结果(状态)

"""
演示面向对象的多态特性以及抽象类(接口)的使用
"""
class Animal:
    def speak(self):
        pass

class Dog(Animal):   #继承Animal类
    def speak(self):
        print("汪汪汪!")

class Cat(Animal):   #继承Animal类
    def speak(self):
        print("喵喵喵!")

def make_noise(animal:Animal):    #定义一个形参为Animal类型的函数
    animal.speak()

black_dog = Dog()
white_cat = Cat()

make_noise(black_dog)
make_noise(white_cat)

1、通过上面代码示例,可以看出多态一般是函数(方法)形参声明为父类对象,但函数(方法)调用时传入父类的子类对象作为实参进行工作。

即:以父类做定义声明,以子类做实际工作,用以获得同一行为,不同状态结果的作用。

2、上面示例代码中父类Animal中定义了speak()方法,但是是空实现(pass),具体由其子类定义中具体实现。这种设计的含义就是:父类做顶层设计,约定提出有哪些方法,子类自行确定该方法的实现(复写父类方法),这种写法就叫做抽象类(也可以称为“接口”)

抽象方法:方法体通过pass空实现的方法。

抽象类:含有抽象方法的类。

抽象类就好比定义一个标准,约定了某一类型的事物必须能够实现哪些功能或做法,而其子类就按其约定的功能方法去各自实现。

#演示抽象类
class AirCondition:
    def cool_wind(self):
        """制冷"""
        pass

    def hot_wind(self):
        """制热"""
        pass

    def swing_lr(self):
        """左右摆风"""
        pass

class Midea_AC(AirCondition):
    def cool_wind(self):
        print("美的空调制冷模式开启!")

    def hot_wind(self):
        print("美的空调制热模式开启!")

    def swing_lr(self):
        print("美的空调左右摆风!")

class Gree_AC(AirCondition):
    def cool_wind(self):
        print("格力空调制冷模式开启!")

    def hot_wind(self):
        print("格力空调制热模式开启!")

    def swing_lr(self):
        print("格力空调左右摆风!")

def make_cool(ac:AirCondition):
    ac.cool_wind()

def make_hot(ac:AirCondition):
    ac.hot_wind()

def air_swing(ac:AirCondition):
    ac.swing_lr()


midea001 = Midea_AC()
gree001 = Gree_AC()

make_hot(midea001)
make_hot(gree001)

make_cool(midea001)
make_cool(gree001)

air_swing(midea001)
air_swing(gree001)

 

综合案例:数据分析及可视化

 这是目前为止学过的最复杂的一个案例,也是可以深化“面向对象”模式思路的一个案例。

案例的需求是:

一、从两个数据文件:一个是文本文件,一个是JSON文件中获取数据信息,两份文件中都包含四种信息:日期、编号、销售额、省份;

二、然后将其中每一天的销售额数据计算累加

三、利用Pyecharts绘制图表文件

学完之后,感受最深的有两点:一是面对上述一二三需求,如何设计合适的类,这需要清晰的思路。

本例中二、三步之前,也就是第一步其实都是数据准备工作,这其中的数据准备又分为两件事情:第一个事情是文件读取,第二个事情是数据规整,即数据封装。

第一个文件读取的事情,因为面对的是两个类型的文件,所以先设计了一个文件读取得到抽象父类,约定该类需要实现的功能:读取文件。然后再设计其两个分别针对文本格式和JSON格式文件的具体实现类。

第二个事情就是规整数据(数据封装),要把从不同文件中读取的数据按照统一的格式规整起来,同时为第二步的工作提供统一格式的数据,所以需要设计一个数据类,规范数据格式。

整合上述两个事情,即需要设计一个抽象父类,其功能约定为:读取文件、规整数据。同时需要设计一个规整格式的数据类。再设计两个抽象类的子类,分别复写实现父类功能。

二就是细节,出错太多,包括父类的功能需求、JSON文件和文本文件读取、数据封装的格式选择等等,也包括因为思路不足够清晰带来的代码问题。

具体程序设计思路如下:

一、考虑数据规整需求:规整不同文件读取后的数据格式,并且为下步数据计算提供规范统一格式,设计一个数据类。

二、考虑不同文件读取、数据规整的不一致,设计一个抽象父类,定义其功能为文件读取和数据规整,由两个子类(分别针对文本文件和JSON文件)具体实现。

三、用设计的类去读取文件,获取规范格式的数据

四、计算

五、使用pyecharts绘图

代码如下:

一、数据类

"""
数据定义的类
对应数据文件中四类数据信息
"""

class Record:

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

    #定义一个魔术方法,实现数据输出
    def __str__(self):
        return f"{self.date},{self.ord_id},{self.money},{self.province}"

二、文件读取(数据规整)类

"""
和文件相关的类定义
"""
import json

from data_define import Record


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

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

#适应于文本文件(txt,CSV)的文件封装类
class TextFileReader(FileReader):

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

    #适应于文本文件的复写父类方法(实现抽象类方法)
    def read_data(self) ->list[Record]:
        record_list:list[Record] = []
        f = open(self.path, "r", encoding="UTF-8")
        for line in f.readlines():      #逐行读取
            line = line.strip()           #去除每行末尾的回车符,也可以通过line[:-1]
            line_data_list = line.split(",")         #分隔符取出每一段组装进列表
            #到上一步为止,只是数据封装完成,下步还要将数据转换为Record对象
            r = Record(line_data_list[0], line_data_list[1], int(line_data_list[2]), line_data_list[3])
            record_list.append(r)

        f.close()
        return record_list


#适应于JSON文件的封装类
class JsonFileReader(FileReader):

    #提供文件路径的构造方法
    def __init__(self, path):
        self.path = path

    #适应于JSON格式文件的复写方法
    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["provice"])
            record_list.append(record)

        f.close()
        return record_list


#测试代码
if __name__ == "__main__":
    text_file_reader = TextFileReader("2011年1月销售数据.txt")
    list1 = text_file_reader.read_data()
    json_file_reader = JsonFileReader("2011年2月销售数据.txt")
    list2 = json_file_reader.read_data()

    for l in list1:
        print(l)
    for l in list2:
        print(l)

三、主程序

"""
面向对象,数据分析案例
有两个数据源文件,分别为csv文件和json文件,都对应四种信息数据
实现步骤:
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("2011年1月销售数据.txt")
json_file_reader = JsonFileReader("2011年2月销售数据.txt")

jan_data:list[Record] = text_file_reader.read_data()
feb_data:list[Record] = json_file_reader.read_data()
#将上述一、二月数据合并为一个数据列表
all_data:list[Record] = jan_data + feb_data

#定义一个字典,存取某天对应的销售额
data_dict:dict[str, int] = {}

#进行计算,求每一天的销售额
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

print(data_dict)


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

bar.add_xaxis(list(data_dict.keys()))
bar.add_yaxis("销售额", list(data_dict.values()))

bar.set_global_opts(
    title_opts=TitleOpts(title="每日销售额")
)

bar.render("每日销售额柱状图.html")

 

 

 

 

 

posted @ 2025-09-07 20:44  tsembrace  阅读(5)  评论(0)    收藏  举报