Python第十三章 面向对象
第十三章 面向对象
13.1 类的定义
class 类名称:
类的属性
类的行为
- class是关键字,表示要定义类了
- 类的属性,即定义在类中的变量(成员变量)
- 类的行为,即定义在类中的函数(成员方法)
创建类的语法
对象 = 类名称()
13.1.1 成员变量
class Student:
name = None
age = None
sex = None
student = Student()
student.name = "jack"
student.age = 18
student.sex = "male"
print(student.name) # jack
print(student.age) # 18
print(student.sex) # male
13.1.2 成员方法
class Student:
name = None
def say_hi(self):
print(f"hi, my name is {self.name}")
student = Student()
student.name = "jack"
student.say_hi() # hi, my name is jack
在类中定义成员方法和定义函数基本一致,但仍有细微区别:
def 方法名(self, param1, param2, ..., paramN):
方法体
可以看到,在方法定义的参数列表中,有一个:self关键字
self关键字是成员方法定义的时候,必须填写的。
- 它用来表示类对象自身的意思
- 当我们使用类对象调用方法的是,self会自动被python传入(也就是调用方法的时候不需要传入self,传入后面的参数即可)
- 在方法内部,想要访问类的成员变量,必须使用self
比如
def say_hi(self):
print(f"hi, my name is {self.name}")
调用成员方法的时候忽略self参数
student.say_hi()
13.2 特殊成员方法
下面几种方法是每个类的内置方法,也称为魔术方法
方法 | 功能 |
---|---|
__init__ |
构造方法,可用于创建类对象的时候设置初始化行为 |
__str__ |
用于实现类对象转字符串的行为 |
__lt__ |
用于2个类对象进行小于或大于比较 |
__le__ |
用于2个类对象进行小于等于或大于等于比较 |
__eq__ |
用于2个类对象进行相等比较 |
【说明】实际上的魔术方法有很多,这里只学习最常用的几个
13.2.1 构造方法
Python类可以使用:__init__()
方法,称之为构造方法。
class Student:
name = None
age = None
sex = None
student = Student()
student.name = "jack"
student.age = 18
student.sex = "male"
上述代码太复杂了,可以使用构造方法__init__()
可以实现:
- 在创建类对象(构造类)的时候,会自动执行。
- 在创建类对象(构造类)的时候,将传入参数自动传递给
__init__
方法使用。
class Student:
name = None
age = None
sex = None
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
print("constructor method")
def say_hi(self):
print(f"hi, my name is {self.name}, my age is {self.age}, my sex is {self.sex}")
student = Student("jack", 18, "female")
print(student.name) # jack
print(student.age) # 18
print(student.sex) # female
student.say_hi() # hi, my name is jack, my age is 18, my sex is female
【注意】不要将
init
写成int
在构造函数里面赋值的变量可以不必在类中声明了,在构造函数中直接 定义 + 声明
class Student:
# 成员变量不用写
# name = None
# age = None
# sex = None
def __init__(self, name, age, sex):
# 构造方法里面声明 + 初始化
self.name = name
self.age = age
self.sex = sex
print("constructor method")
def say_hi(self):
print(f"hi, my name is {self.name}, my age is {self.age}, my sex is {self.sex}")
student = Student("jack", 18, "female")
print(student.name) # jack
print(student.age) # 18
print(student.sex) # female
student.say_hi() # hi, my name is jack, my age is 18, my sex is female
如果显示定义了构造函数,那么默认的无参构造函数就没有了
student2 = Student() # 错误:TypeError: Student.__init__() missing 3 required positional arguments: 'name', 'age', and 'sex'
13.2.2 字符串方法
默认情况下:当类对象需要被转换成字符串的时候,会被转换成内存地址
student = Student()
print(student) # <__main__.Student object at 0x00000150609B7010>
可以使用__str__
方法设置转换的字符串
class Student:
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
def __str__(self):
return f"name:{self.name}, age:{self.age}, sex:{self.sex}"
student = Student("jack", 18, "male")
print(student) # name:jack, age:18, sex:male
13.2.3 小于比较方法
默认情况下,对象是不允许比较的
student1 = Student("jack", 18, "male")
student2 = Student("mary", 19, "female")
print(student1 < student2) # TypeError: '<' not supported between instances of 'Student' and 'Student'
使用__lt__
来比较
class Student:
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
# 定义了<的比较规则
def __lt__(self, other):
return self.age < other.age
student1 = Student("jack", 18, "male")
student2 = Student("mary", 19, "female")
print(student1 < student2) # True
print(student1 > student2) # False
【说明】比较大于符号的魔术方法是:gt__不过,实现了__lt,__gt__就没必要实现了
13.2.4 ≤、=比较方法
≤对应的是__le__
,=对应的是__eq__
class Student:
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
print("constructor method")
def __le__(self, other):
return self.age <= other.age
def __eq__(self, other):
return self.age == other.age
student1 = Student("jack", 18, "male")
student2 = Student("mary", 19, "female")
print(student1 <= student2) # True
print(student1 == student2) # False
13.3 面向对象三大特性
面向对象编程,是许多编程语言都支持的一种编程思想。简单理解是:基于模板(类)去创建实体(对象),使用对象完成功能开发。
面向对象包含3大主要特性:封装、继承、多态
13.3.1 封装
封装表示的是,将现实世界事物的:属性、行为,封装到类中,描述为:成员变量、成员方法,从而完成程序对现实世界事物的描述
(1) 私有成员
既然现实事物有不公开的属性和行为,那么作为现实事物在程序中映射的类,也应该支持。
类中提供了私有成员的形式来支持。
- 私有成员变量
- 私有成员方法
【说明】私有成员包括变量和方法
定义私有成员的方式非常简单,只需要:
- 私有成员变量:变量名以__开头(2个下划线)
- 私有成员方法:方法名以__开头(2个下划线)
即可完成私有成员的设置
1、私有成员无法访问
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() # 不能使用私有方式
phone.__current_voltage = 13 # 不能访问私有变量
print(phone.__current_voltage)
2、在成员方法内,可以访问私有方法
class Phone:
IMEI = None # 序列号
producer = None # 厂商
__current_voltage = 10 # 当前电压
def call_by_5g(self):
if self.__current_voltage >= 1:
self.__keep_single_core()
print("5g通话已开始")
def __keep_single_core(self):
print("让CPU一但和模式运行以节省电量")
phone = Phone()
phone.call_by_5g() # 5g通话已开始
13.3.2 继承
(1) 继承语法
继承的语法
class 子类名(父类名):
类内容体
继承分为:单继承和多继承
继承表示:将从父类那里继承(复制)来成员变量和成员方法(不含私有)(私有的成员不会继承)
1、单继承
class Father:
name = "father"
def father_name(self):
print(f"father_name is {self.name}")
class Son(Father):
name = "son"
def son_name(self):
print(f"son_name is {self.name}")
son = Son()
son.father_name() # father_name is son
son.son_name() # son_name is son
2、多继承
class 子类名(父类1, 父类2, 父类3... 父类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
xiaomi = MyPhone()
xiaomi.call_by_5g() # 5g通信
xiaomi.read_card() # 读取NFC卡
xiaomi.write_card() # 写入NFC卡
xiaomi.control() # 红外遥控开启
【说明】pass是占位语句,用来保证函数(方法)或类定义的完整性,表示无内容,空的意思
3、多继承的变量覆盖问题
多个父类中,如果有同名的成员,那么默认以继承顺序(从左到右)为优先级。也就是最左边的优先级最高
如上面的例子,Phone和NFCReader都有producer的变量,最后只有是最左边的Phone的变量有效
print(xiaomi.producer) # None
(2) 重写父类成员
class Father:
var = "father"
def func(self):
print("father method")
class Son(Father):
var = "son"
# 覆盖了父类方法
def func(self):
print("son method")
son = Son()
son.func() # son method
print(son.var) # son
(3) 调用父类同名成员
一旦复写父类成员,那么类对象调用成员的时候,就会调用复写后的新成员
如果需要使用被复写的父类的成员,需要特殊的调用方式:
方式1:调用父类成员
- 使用成员变量:父类名.成员变量
- 使用成员方法:父类名.成员方法(self)
方式2:使用super()调用父类成员
- 使用成员变量:super().成员变量
- 使用成员方法:super().成员方法()
【说明】只能在子类内调用父类的同名成员(不能在类外)。子类的类对象直接调用会调用子类复写的成员
1、父类名调用
class Father:
var = "father"
def func(self):
print("father method")
class Son(Father):
var = "son"
# 覆盖了父类方法
def func(self):
# print("son method")
Father.func(self) # father method,这里得传入self
print(Father.var) # father
son = Son()
son.func()
2、super()调用
class Father:
var = "father"
def func(self):
print("father method")
class Son(Father):
var = "son"
# 覆盖了父类方法
def func(self):
# print("son method")
super().func() # father method,不用传入self参数
print(super().var) # father
son = Son()
son.func() # son method
13.3.3 多态
多态,指的是:多种状态,即完成某个行为时,使用不同的对象会得到不同的状态。
class Animal:
def speak(self):
pass
class Dog(Animal):
def speak(self):
print("wangwang")
class Cat(Animal):
def speak(self):
print("miaomiao")
def make_noise(animal: Animal):
animal.speak()
dog = Dog()
cat = Cat()
make_noise(dog) # wangwang
make_noise(cat) # miaomiao
多态常作用在继承关系上,比如
- 函数(方法)形参声明接收父类对象
- 实际传入父类的子类对象进行工作
即:以父类做定义声明、以子类做实际工作,用以获得同一行为, 不同状态
上述例子可以看到父类Animal的speak方法是空实现
class Animal:
def speak(self):
pass
这种设计的含义是:
- 父类用来确定有哪些方法
- 具体的方法实现,由子类自行决定
这种写法,就叫做抽象类(也可以称之为接口)
- 抽象类:含有抽象方法的类称之为抽象类
- 抽象方法:方法体是空实现的(pass)称之为抽象方法
【说明】含有pass的方法是抽象方法,含有抽象方法的类就是抽象类
抽象类的作用:多用于做顶层设计(设计标准),以便子类做具体实现;也是对子类的一种软性约束,要求子类必须复写(实现)父类的一些方法;并配合多态使用,获得不同的工作状态。
【说明】抽象类(接口)是很能体现多态的一点,父类都设置为抽象方法,都交由子类实现
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()
midea_ac = Midea_AC()
gree_ac = GREE_AC()
make_cool(midea_ac) # 美的空调制冷
make_cool(gree_ac) # 格力空调变频省电制冷
13.4 面向对象练习
【说明】本例题重在讲解Python中的面向对象思维
某公司,有2份数据文件,现需要对其进行分析处理,计算每日的销售总额(所有地区的销售额加起来)并以柱状图表的形式进行展示。
两份文件如下
# 2011-1-data.txt, 文本文件,中间用,隔开
2011-01-01,4b34218c-9f37-4e66-b33e-327ecd5fb897,1689,湖南省
2011-01-01,4b34218c-9f37-4e66-b33e-327ecd5fb897,1689,湖南省
2011-01-01,5b6a6417-9a16-4243-9704-255719074bff,2353,河北省
2011-01-01,ae240260-68a9-4e59-b4c9-206be4c08a8d,2565,湖北省
2011-01-01,c833e851-880f-4e05-9de5-b547f5ffc5e1,2877,山东省
...
2011-01-02,4681b5fe-b1e9-468b-a6f5-9f98f5daa841,1931,山西省
2011-01-02,d1a62946-f470-4782-9461-fde425c759e2,2416,江苏省
2011-01-02,6d49171f-4522-4561-a191-4514e3d2a5d3,1171,江苏省
2011-01-02,a92ca744-b61d-44dc-ad03-7d99842e2b53,170,广西省
2011-01-02,25b46bc5-e686-4de1-b382-ef105261752f,2517,浙江省
2011-01-03,a59fcc9f-fd03-4232-9da7-6ef73ceb4aba,2696,贵州省
2011-01-03,0cdb43a5-3a61-4ae9-b67a-f2216f2f0f79,1890,云南省
2011-01-03,0fec45b5-6f3d-4906-a057-f5dc87bb6891,2231,贵州省
2011-01-03,6c36b4f8-b4d3-4ca0-94dd-d0863e695e40,914,山东省
2011-01-03,cdec53ab-8c14-4d56-afc8-6512bd82f5d3,1021,河北省
2011-01-03,24c1f00b-04b9-474b-a2f5-a893c47667f2,2317,河南省
【说明】下面的文件每行是一个JSON文件,但是整体不能解析,因为外面没有[],每行后面也没有,如果作为JSON转换的话,需要一行一行读取
# 2011-2-data.txt 每行都是一个JSON文件
{"date": "2011-02-01", "order_id": "caf99222-53d6-427b-925d-3187fc71a86a", "money": 1805, "province": "江西省"}
{"date": "2011-02-01", "order_id": "3dea6f83-a9b2-4197-ba9f-2b25704c530b", "money": 2547, "province": "广东省"}
{"date": "2011-02-01", "order_id": "93cf7a56-3f90-4df9-af76-de7233c1dddb", "money": 1216, "province": "福建省"}
{"date": "2011-02-01", "order_id": "0042323a-e555-4d64-a70b-81380ca3aae7", "money": 1193, "province": "四川省"}
{"date": "2011-02-01", "order_id": "0fbe1745-ac65-48f4-985b-b71875fcfbf7", "money": 230, "province": "云南省"}
{"date": "2011-02-01", "order_id": "6ee5af28-f6ce-42e3-96b6-96f3844c5fda", "money": 2770, "province": "江西省"}
{"date": "2011-02-01", "order_id": "2f629b33-62a5-44c1-8a0f-b983e811fb40", "money": 2291, "province": "福建省"}
{"date": "2011-02-01", "order_id": "773d1259-f81d-413d-81cb-b5f7c0d8f97c", "money": 2453, "province": "河南省"}
...
{"date": "2011-02-02", "order_id": "1e7189bb-9369-4bae-9d70-fe349ca75997", "money": 2127, "province": "安徽省"}
{"date": "2011-02-02", "order_id": "99fc2f6c-f373-42d0-ac27-0203dbad385b", "money": 396, "province": "江西省"}
{"date": "2011-02-02", "order_id": "9e0eeebd-4776-4023-bd3c-a4c449f1702f", "money": 577, "province": "广西省"}
{"date": "2011-02-02", "order_id": "0a8e7c56-cd8a-4649-a6a6-327578bc4793", "money": 2272, "province": "江苏省"}
{"date": "2011-02-02", "order_id": "bfb831c9-859b-4bef-a762-c466c32e7e8f", "money": 220, "province": "广西省"}
{"date": "2011-02-03", "order_id": "3eff3650-9999-4278-8b40-8ba5217888a1", "money": 366, "province": "云南省"}
{"date": "2011-02-03", "order_id": "ebb139e5-4037-4019-b4a6-2e074c651d1f", "money": 2555, "province": "广东省"}
{"date": "2011-02-03", "order_id": "08c78f66-46c2-46e6-b155-bbd259c354d4", "money": 1955, "province": "福建省"}
{"date": "2011-02-03", "order_id": "315ae98c-c0fa-4e08-ae29-5c8c8abcdb7a", "money": 1430, "province": "山东省"}
【感悟】面向对象过程能够将过程变得更具有模块化,很好地分割业务
1、面向过程思路
import json
from pyecharts.charts import Bar
data_2011 = open("D:/2011-1-data.txt", "r", encoding="UTF-8")
# data_2011.close()
# print(data_2011.read())
# {date_sum}的列表
date_money: dict[str, int] = {}
#
for line in data_2011.readlines():
line_split = line.split(",")
date = line_split[0]
money = int(line_split[2])
try:
date_money[date] += money
except KeyError:
date_money[date] = money
data_2011.close()
# 第一个数据结束了
# 然后是第二轮数据
data_2022 = open("D:/2011-2-data.txt", "r", encoding="UTF-8")
# print(data_2022.read())
# 这里前后要加上[],这里面每行都要加上,来进行,那就每行都处理吧?
for line in data_2022.readlines():
line_json = json.loads(line)
date = line_json["date"]
money = line_json["money"]
try:
date_money[date] += money
except KeyError:
date_money[date] = money
data_2022.close()
# for date in date_money.keys():
# print(date, date_money[date])
# 然后按照时间排序
date_list = sorted(date_money.keys())
print(date_list)
# 然后加入到list中
money_list = []
for date in date_list:
money_list.append(date_money[date])
print(money_list)
# 然后加入到柱状图中
bar = Bar()
# 添加x轴数据
bar.add_xaxis(date_list)
bar.add_yaxis("money", money_list)
bar.render("1-2月销售额.html")
2、面向对象思路
首先将每一行当做一个Record对象
# Record.py
class Record:
def __init__(self, date: str, order_id: str, money: int, province: str):
# print("constructor:", 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}"
然后将文件读取也方入一个类中
# FileReader.py
import json
from Record import Record
class FileReader:
def __init__(self, path):
"""constructor"""
self.path = path
def parse_file(self) -> list[Record]:
pass
class TextFileReader(FileReader):
def parse_file(self) -> list[Record]:
"""
解析path对应的文件,然后将每行解析为Record,最后作为列表返回
:return:
"""
file = open(self.path, "r", encoding="UTF-8")
record_list = []
for line in file.readlines():
split_line = line.strip().split(",")
# 下面的可以直接放到构造器里面的
date = split_line[0]
order_id = split_line[1]
money = int(split_line[2])
province = split_line[3]
record = Record(date, order_id, money, province)
record_list.append(record)
file.close()
return record_list
class JsonFileReader(FileReader):
def parse_file(self) -> list[Record]:
"""
解析path对应的文件,然后将每行解析为Record,最后作为列表返回
:return:
"""
file = open(self.path, "r", encoding="UTF-8")
record_list = []
for line in file.readlines():
record_json = json.loads(line)
# 下面的可以直接放到构造器里面的
date = record_json["date"]
order_id = record_json["order_id"]
money = int(record_json["money"])
province = record_json["province"]
record = Record(date, order_id, money, province)
record_list.append(record)
file.close()
return record_list
if __name__ == '__main__':
# 测试TextFileReader
text_file_reader = TextFileReader("D:/2011-1-data.txt")
# for record in text_file_reader.parse_file():
# print(record.date, record.order_id, record.money, record.province)
# text_file_reader.parse_file()
# 测试JsonFileReader
json_file_reader = JsonFileReader("D:/2011-2-data.txt")
for record in json_file_reader.parse_file():
print(record)
最后进行读取,构建图表
from pyecharts.charts import Bar
from FileReader import TextFileReader, JsonFileReader
# 读取两个文件
text_file_reader = TextFileReader("D:/2011-1-data.txt")
json_file_reader = JsonFileReader("D:/2011-2-data.txt")
# 两个文件的列表加起来,就是最终的记录列表
record_list = text_file_reader.parse_file() + json_file_reader.parse_file()
# 然后将所有的数据聚合在一起,构成dict[date, money]
date_money = {}
for record in record_list:
try:
date_money[record.date] += record.money
except KeyError:
date_money[record.date] = record.money
# 然后排序
date_list = sorted(date_money.keys())
# 然后将钱按时间取出放入列表中
money_list = []
for date in date_list:
money_list.append(date_money[date])
# 构造表格
bar = Bar()
# 添加横坐标
bar.add_xaxis(date_list)
bar.add_yaxis("money", money_list)
bar.render("1-2月每日销售额.html")