1. 初识对象
设计类:
class Student:
name = None
创建对象:
stu_1 = Student()
stu_2 = Student()
对象属性赋值:
stu_1.name="tom"
stu_2.name ="jerry"
【例】
class Student:
name = None
age = None
gender = None
nationality = None
stu = Student()
stu.name = "小明"
stu.age = 11
stu.gender = "男"
stu.nationality = "中国"
print(stu.name)
print(stu.age)
print(stu.gender)
print(stu.nationality)
2. 成员方法
上一节介绍了如何封装一个类,并基于类创建对象来使用。现在来看类更详细的使用语法:
class 类名称:
成员变量
成员方法
在类中定义成员方法和在类外定义函数基本一致,但仍有细微区别:
def 方法名(self,形参1,...,形参N):
方法体
在定义方法时,形参列表中的 self 关键字必须填写,用于代表对象自身。
当使用对象调用方法时,self 会被自动传入,即传参时忽略 self,当它不存在。
在方法内部访问成员变量,必须使用 self。(Java 中只有方法内的变量与成员变量同名时,才必须使用 this)
创建对象的语法:
对象 = 类名()
【例】
class Student:
name = None
age = None
def say_hi(self):
print(f"hi, 我是{self.name}")
def say_hello(self, msg):
print(f"hello, 我是{self.name}, {msg}")
stu1 = Student()
stu1.name = "tom"
stu1.say_hi()
stu1.say_hello("希望大家多多关照")
stu2= Student()
stu2.name = "jerry"
stu2.say_hi()
stu2.say_hello("很高兴认识大家")
输出结果:
hi, 我是tom
hello, 我是tom, 希望大家多多关照
hi, 我是jerry
hello, 我是jerry, 很高兴认识大家
3. 类和对象
类相当于模板,对象就是根据类这个模板生产出来的具体实体。
class Clock:
id = None
price = None
def ring(self): # 响铃
import winsound
# 参数1:频率;参数2:持续时间
winsound.Beep(2000,3000)
clock1 = Clock()
clock1.id = 111
clock1.price = 19.8
print(f"闹钟{clock1.id}的价格是{clock1.price}")
clock1.ring()
clock2 = Clock()
clock2.id = 222
clock2.price = 32.3
print(f"闹钟{clock2.id}的价格是{clock2.price}")
clock2.ring()
输出结果(输出的时候还会有响铃,声音略微感人~~):
闹钟111的价格是19.8
闹钟222的价格是32.3
4. 构造方法
看一段代码:
class Student:
name = None
age = None
tel = None
student1 = Student()
student1.name ="tom"
student1.age = 20
student1.tel ="18012340000"
student2 = Student()
student2.name="jerry"
student2.age = 30
student2.tel ="19017840900"
在上面代码中,为对象的属性赋值需要依次进行,略显繁琐。可以使用构造方法简化。
可以使用构造方法__init__() 初始化对象:在创建对象时,会自动执行 __init__() ,并将传入参数自动传递给__init__()方法使用。
构造方法使用1:
class Student:
# 这三行定义可以不用写,见“构造方法使用2”
# 如果不写的话,__init__()构造方法中的self.name self.age self.gender 三条语句就既有定义作用,也有赋值作用
name = None
age = None
gender = None
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
print("创建了Student对象")
stu = Student("tom", 14, "男")
print(stu.name)
print(stu.age)
print(stu.gender)
输出结果:
创建了Student对象
tom
14
男
构造方法使用2:
class Student:
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
print("创建了Student对象")
stu = Student("tom", 14, "男")
print(stu.name)
print(stu.age)
print(stu.gender)
输出结果:
创建了Student对象
tom
14
男
5. 内置方法 / 魔术方法
上文学习的__init__()构造方法,是 Python 类内置的方法之一。
内置的类方法,各自有各自特殊的功能,也称为:魔术方法。主要介绍下面几个魔术方法:
5.1__str__() 字符串方法
print(对象) 或者 str(对象) 时,会自动调用__str__()方法。默认情况下,该方法返回的是对象的内存地址。
class Student:
name = None
age = None
def __init__(self, name, age):
self.name = name
self.age = age
stu = Student("tom", 15)
print(stu)
print(str(stu))
输出结果:
<__main__.Student object at 0x01C6CB68>
<__main__.Student object at 0x01C6CB68>
但我们基本不关心对象的内存地址。因此,可以通过__str__()方法控制直接输出对象或把对象转换成字符串的行为。
class Student:
name = None
age = None
def __init__(self, name, age):
self.name = name
self.age = age
# 返回值:字符串;内容:自行定义
def __str__(self):
return f"Student对象:name={self.name}, age={self.age}"
stu = Student("tom", 15)
print(stu)
print(str(stu))
输出结果:
Student对象:name=tom, age=15
Student对象:name=tom, age=15
5.2__lt__() 小于比较方法
不能像下面这样直接比较两个对象:
class Student:
name = None
age = None
def __init__(self, name, age):
self.name = name
self.age = age
stu1 = Student("tom", 15)
stu2 = Student("jerry", 9)
print(stu1 < stu2)
print(stu1 > stu2)
在类中实现__lt__()方法,可以同时完成小于和大于两种比较。(虽然名称是小于号比较方法,但是小于和大于都能比较)
class Student:
name = None
age = None
def __init__(self, name, age):
self.name = name
self.age = age
# 传入参数:other,即另一个对象
# 返回值:True或False
# 内容:自行定义
def __lt__(self, other):
return self.age < other.age
stu1 = Student("tom", 9)
stu2 = Student("jerry", 15)
print(stu1 < stu2)#True
print(stu1 > stu2)#False
5.3__le__() 小于等于比较方法
__le__()可以用于 <= 或 >= 两种比较运算符上。
class Student:
name = None
age = None
def __init__(self, name, age):
self.name = name
self.age = age
# 传入参数:other,即另一个对象
# 返回值:True或False
# 内容:自行定义
def __le__(self, other):
return self.age <= other.age
stu1 = Student("tom", 9)
stu2 = Student("jerry", 15)
stu3 = Student("smith", 15)
print(stu1 <= stu2)#True
print(stu1 >= stu2)#False
print(stu2 <= stu3)#True
print(stu2 >= stu3)#True
5.4__eq__() 比较运算符实现方法
不实现__eq__()方法,对象之间可以进行 “==” 比较,但比较的是内存地址。所以不同对象比较一定是 False。
class Student:
name = None
age = None
def __init__(self, name, age):
self.name = name
self.age = age
stu1 = Student("tom", 9)
stu2 = Student("tom", 9)
stu3 = Student("tom", 15)
print(stu1 == stu2)#False
print(stu1 == stu3)#False
实现了__eq__()方法后,就可以按照需要来判断两个对象是否相等。
class Student:
name = None
age = None
def __init__(self, name, age):
self.name = name
self.age = age
# 传入参数:other,即另一个对象
# 返回值:True或False
# 内容:自行定义
def __eq__(self, other):
return self.age == other.age
stu1 = Student("tom", 9)
stu2 = Student("tom", 9)
stu3 = Student("tom", 15)
print(stu1 == stu2)#True
print(stu1 == stu3)#False
6. 封装
私有成员:包括私有成员变量和私有成员方法。
定义私有成员的方式:
私有成员变量:变量名以__开头(2个下划线)
私有成员方法:方法名以__开头(2个下划线)
私有成员无法被类对象使用,但是可以被类中的其它成员使用。
练习:
设计一个手机类,内部包含:
(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(),判断 5g 网络状态。
打印输出正在通话中。
输出样例:
5g关闭,使用4g网络
正在通话中
class Phone:
# 手机的内部信息不需要用户知道,所以做成私有的
__is_5g_enable = False
def __check_5g(self):
if self.__is_5g_enable == True:
print("5g开启")
else:
print("5g关闭,使用4g网络")
# 用户只需要能够打电话
def call_by_5g(self):
self.__check_5g()
print("正在通话中")
phone = Phone()
phone.call_by_5g()
输出结果:
5g关闭,使用4g网络
正在通话中
7. 继承
7.1 继承的概念
继承:从父类继承(复制)非私有的成员变量和成员方法,分为单继承和多继承。
7.2 单继承
语法:
class 类名(父类名):
类内容体
【例】
class Phone:
IMEI = None #序列号
producer = "HM" #生产厂商
def call_by_4g(self):
print("4g通话")
class NewPhone(Phone):
face_id = "10010" #面部识别id
def call_by_5g(self):
print("5g通话")
phone = NewPhone()
print(phone.producer)#HM
phone.call_by_4g()#4g通话
print(phone.face_id)#10010
phone.call_by_5g()#5g通话
7.3 多继承
Python 的类也支持多继承,即:一个类可以继承多个父类。
多个父类中,如果有同名的成员,那么默认以继承顺序(从左到右)为优先级。即:先继承的保留,后继承的被覆盖。
语法:
class 类名(父类1,父类2,...,父类N):
类内容体
【例】
class Phone:
producer = "ITCAST" #生产厂商
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 Myphone(Phone, NFCReader):
# 类体中不想写内容了,但是又不能空着,可以用pass占位
pass
my_phone = Myphone()
#ITCAST, Phone类中的producer属性
print(my_phone.producer)#ITCAST
my_phone.call_by_4g()#4g通话
print(my_phone.nfc_type)#第五代
my_phone.read_card()#NFC读卡
my_phone.write_card()#NFC写卡
7.3 pass 关键字
pass:占位语句,用来保证函数、方法或类定义的完整性,表示无内容,空的意思。具体用法见 7.4 代码中的 Myphone 类。
7.5 复写父类成员和调用父类成员
复写父类成员
子类继承父类的成员属性和成员方法后,若对其 “不满意”,可以进行复写。即:在子类中重新定义同名的属性或方法。
class Phone:
producer = "ITCAST" #生产厂商
def call_by_5g(self):
print("父类中的5g通话")
class Myphone(Phone):
producer = "ITHEIMA" #生产厂商
def call_by_5g(self):
print("子类中的5g通话")
my_phone = Myphone()
print(my_phone.producer)#ITHEIMA
my_phone.call_by_5g()#子类中的5g通话
调用父类同名成员
子类复写了父类成员后,子类对象调用成员时,就会调用复写后的新成员。若要使用父类成员,可以用以下两种调用方式:
方式1:
使用成员变量:父类名.成员变量
使用成员方法:父类名.成员方法(self)
方式2:
使用成员变量:super().成员变量
使用成员方法:super().成员方法()
class Phone:
producer = "ITCAST" #生产厂商
def call_by_5g(self):
print("父类中的5g通话")
class Myphone(Phone):
producer = "ITHEIMA" #生产厂商
def call_by_5g(self):
# 有时可以通过这样,既添加新功能,又利用父类原有功能
# 方式1
print(f"父类中的producer={Phone.producer}")
Phone.call_by_5g(self)
# 方式2
print(f"父类中的producer={super().producer}")
super().call_by_5g()
my_phone = Myphone()
my_phone.call_by_5g()
输出结果:
父类中的producer=ITCAST
父类中的5g通话
父类中的producer=ITCAST
父类中的5g通话
注意:只可以在子类内部调用父类的同名成员,子类对象默认调用子类复写的成员。
8. 类型注解
类型注解:在代码中显式地说明数据的类型。
主要功能:
(1) 帮助第三方 IDE 工具(如PyCharm)对代码进行类型推断,协助代码提示。
(2) 帮助开发者对变量进行类型注释。
类型注解支持:变量的类型注解、函数(方法)形参列表和返回值的类型注解。
8.1 变量的类型注解
方式1:
# 基础数据类型注解
var_1: int = 10
var_2: float = 3.1415926
var_3: bool = True
var_4: str ="itheima"
# 对象类型注解
class Student:
pass
stu: Student = Student()
# 容器简单类型注解
my_list1: list = [1, 2, 3]
my_tuple1: tuple = (1, 2, 3)
my_set1: set = {1, 2, 3}
my_dict1: dict = {"itheima":666}
my_str1: str = "itheima"
# 容器详细类型注解
my_list2: list[int] = [1, 2, 3]
my_tuple2: tuple[str, int, bool] = ("itheima", 666, True)
my_set2: set[int] = {1, 2, 3}
my_dict2: dict[str, int] = {"itheima": 666}
注意事项:
元组设置详细类型注解,需要将每一个元素都标记出来。
字典设置详细类型注解,需要两个类型,第一个是 key,第二个是 value。
方式2:
除了上面的方式,也可以在注释中进行变量的类型注解。
语法:#type:类型
为变量设置注解时,显式的变量定义一般无需注解。因为就算不写注解,也能够明确知晓变量的类型。
当无法直接看出变量类型时,会添加变量的类型注解。
注意:类型注解仅仅是建议性的,不是强制性的。所以,若数据类型与注解类型无法对应,也不会导致错误。如下面代码不会报错:
var_1: int = "itheima"
var_2: str = 123
8.2 函数和方法的类型注解
8.2.1 函数 / 方法形参的类型注解
如下图所示,编写了函数 / 方法,使用形参 data 的时候,工具没有任何提示。
在调用函数 / 方法传入参数的时候,工具无法提示参数类型。
这都是因为,在定义函数 / 方法的时候,没有对形参进行注解。
函数 / 方法的形参类型注解语法:
def 函数/方法名(形参名: 类型, 形参名: 类型,....):
pass
注解后的效果:
8.2.2 函数 / 方法返回值的类型注解
语法(使用->
符号):
def 函数/方法名(形参: 类型,...,形参: 类型) -> 返回值类型:
pass
【例】
def func(data: list) -> list:
return data
另外,注解仅仅是建议性的,不是强制性的。所以,若数据类型与注解类型无法对应,也不会导致错误。
8.3 Union 联合类型注解
8.3.1 为什么需要 Union 联合类型注解
如下面代码,当列表元素中只有一种数据类型、字典的 value 只有一种数据类型时,可以利用前面的方法方便地进行详细的类型注解。
my_list: list[int] = [1, 2, 3]
my_dict: dict[str, int] ={"age": 11, "num": 3}
但是,若列表元素中有多于一种数据类型、字典的 value 多余一种数据类型时,利用前面的方法进行详细的类型注解就行不通了。
my_list = [1, 2, "itcast", "itheima"]
my_dict= {"name": "周杰轮", "age": 31}
这时,可以使用 Union 定义联合类型注解:Union[类型,...,类型]
。
8.3.2 Union 联合类型注解的使用
变量的联合类型注解:
from typing import Union
# Union[str, int]表示类型是str和int中的一种
my_list: list[Union[str, int]] = [1, 2, "itcast", "itheima"]
my_dict: dict[str, Union[str, int]]= {"name": "周杰轮", "age": 31}
函数的联合类型注解:
9. 多态
9.1 多态的概念
多态:多种状态,即完成某个行为时,使用不同的对象会得到不同的状态。
多态常常作用在继承关系上,比如:函数 / 方法的形参声明接收父类对象,但实际传入子类对象进行工作。
class Animal:
def speak(self):
pass
class Cat(Animal):
def speak(self):
print("喵喵喵")
class Dog(Animal):
def speak(self):
print("汪汪汪")
def make_noise(animal: Animal):
animal.speak()
cat = Cat()
dog = Dog()
make_noise(cat)#喵喵喵
make_noise(dog)#汪汪汪
9.2 抽象类(接口)的编程思想
在 9.1 的代码中,父类 Animal 的 speak 方法内容为空。以使父类确定有哪些方法,子类决定方法的具体实现。
抽象方法:方法体为空(只有pass)的方法称之为抽象方法,如:父类 Animal 的 speak 方法。
抽象类:含有抽象方法的类称之为抽象类,如:Animal 类。
为什么要使用抽象类:
举个例子:空调有一定的国家标准,假设为:可以制冷、可以制热、左右摆风……有了这个标准,各个厂家可以自行地去具体实现。
抽象类就好比定义一个标准,它包含一些抽象方法,要求子类必须实现,从而达到约束子类的目的。
定义了抽象类之后,并不会直接使用,即:不会直接用抽象类创建对象。真正工作的是抽象类的子类。
class AC: #国家标准
def cool_wind(self):
pass
def hot_wind(self):
pass
def swing_l_r(self):
pass
class Midea_AC(AC): #美的
def cool_wind(self):
print("美的冷风")
def hot_wind(self):
print("美的热风")
def swing_l_r(self):
print("美的左右摆风")
class GREE_AC(AC): #格力
def cool_wind(self):
print("格力冷风")
def hot_wind(self):
print("格力热风")
def swing_l_r(self):
print("格力左右摆风")
def make_cool(ac: AC):#制冷
ac.cool_wind()
make_cool(Midea_AC())#美的冷风
make_cool(GREE_AC())#格力冷风
10. 综合案例
所需文件:
链接:https://pan.baidu.com/s/1nI0lFNOxptAmQ3kh5UHMEQ?pwd=sumg
提取码:sumg
要求:读取 “2011年1月销售数据.txt” 和 “2011年2月销售数据JSON.txt” 两个文件,统计每天各省的销售额之和,并用柱状图展示。两个文件的内容形式分别为:
2011年1月销售数据.txt:
2011年2月销售数据JSON.txt:
效果图:
代码(共三个文件):
data_define.py
class Record:
def __init__(self, date, order_id, money, province):
self.date = date #订单日期
self.order_id = order_id #订单编号
self.money = money #订单金额
self.province = province #省份
# 自定义输出对象时的输出格式
def __str__(self):
return f"{self.date} {self.order_id} {self.money} {self.province}"
file_define.py
import json
from data_define import Record
class FileRead: #文件读取抽象类
def read_data(self) -> list[Record]:
pass
class TextFileRead(FileRead): #文本文件读取
def __init__(self, path): #传入文件路径
self.path = path
def read_data(self) -> list[Record]:
f = open(self.path, "r", encoding="utf-8")
records_list: list[Record] = [] #保存文件每行数据对象的列表
for line in f.readlines(): #f.readlines()返回列表,列表中每个元素是一行数据
line = line.strip() #去掉”\n“
data_list = line.split(",")#每行数据按照”,“拆分,存储在列表中
# 每行数据的列表生成一个对象,追加到records_list中
records_list.append(Record(data_list[0], data_list[1], int(data_list[2]), data_list[3]))
f.close()
return records_list
class JsonFileRead(FileRead): #json文件读取
def __init__(self, path): #传入文件路径
self.path = path
def read_data(self) -> list[Record]:
f = open(self.path, "r", encoding="utf-8")
records_list: list[Record] = []#保存文件每行数据对象的列表
for line in f.readlines(): #f.readlines()返回列表,列表中每个元素是一行数据
data_dict = json.loads(line)#不用去掉”\n“,直接把json字符串转换成字典,为什么?
# 每行数据的字典生成一个对象,追加到records_list中
records_list.append(Record(data_dict["date"], data_dict["order_id"], int(data_dict["money"]), data_dict["province"]))
f.close()
return records_list
main.py
from file_define import FileRead, TextFileRead, JsonFileRead
from data_define import Record
from pyecharts.charts import Bar
from pyecharts.options import *
from pyecharts.globals import ThemeType
#text_file_read = TextFileRead("file/数据分析案例/2011年1月销售数据.txt")
text_file_read = TextFileRead("E:/pythonProject/testpro01/file/数据分析案例/2011年1月销售数据.txt")
jan_data_list = text_file_read.read_data()
json_file_read = JsonFileRead("E:/pythonProject/testpro01/file/数据分析案例/2011年2月销售数据JSON.txt")
feb_data_list = json_file_read.read_data()
# 合并两个列表
all_data_list = jan_data_list + feb_data_list
date_money_dict = {}
for record in all_data_list:
if record.date in date_money_dict:
date_money_dict[record.date] += record.money
else:
date_money_dict[record.date] = record.money
# bar = Bar(init_opts=InitOpts(theme=ThemeType.LIGHT))#也可以
bar = Bar({"theme":ThemeType.LIGHT})
# date_money_dict.keys()、date_money_dict.values()得到的都是dict_keys类型
bar.add_xaxis(list(date_money_dict.keys()))
# label_opts=LabelOpts(is_show=False):每个柱不显示数字
bar.add_yaxis("销售额",list(date_money_dict.values()), label_opts=LabelOpts(is_show=False))
bar.set_global_opts(
# 柱状图标题
title_opts=TitleOpts(title="每日销售额柱状图", pos_left="center", pos_bottom="1%")
)
bar.render("每日销售额.html")