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 封装

封装表示的是,将现实世界事物的:属性、行为,封装到类中,描述为:成员变量、成员方法,从而完成程序对现实世界事物的描述

image-20230927210055389

(1) 私有成员

既然现实事物有不公开的属性和行为,那么作为现实事物在程序中映射的类,也应该支持。

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

  1. 私有成员变量
  2. 私有成员方法

【说明】私有成员包括变量和方法

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

  1. 私有成员变量:变量名以__开头(2个下划线)
  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):
	类内容体

下面是一个案例

image-20230927213536057

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

这种设计的含义是:

  1. 父类用来确定有哪些方法
  2. 具体的方法实现,由子类自行决定

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

  • 抽象类含有抽象方法的类称之为抽象类
  • 抽象方法:方法体是空实现的(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")

image-20230929220947156

posted @ 2023-10-07 11:41  Crispy·Candy  阅读(19)  评论(0编辑  收藏  举报