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")
 
                     
                    
                 
                    
                 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号