面向对象

更新时间: 2024-03-27 14:23:52

# 初识对象

# 使用对象组织数据

在程序中是可以做到和生活中那样,设计表格、生产表格、填写表格的组织形式的。

  1. 在程序中设计表格,我们称之为:类设计(class)
class Student:
    name = None # 记录学生姓名
1
2
  1. 在程序中打印生产表格,我们称之为:创建对象
# 基于类创建对象
stu_1 = Student()
stu_2 = Student()
1
2
3
  1. 在程序中填写表格,我们称之为:对象属性赋值
stu_1.name = "周杰伦" # 为学生1对象赋予名称属性值
stu_2.name = "林俊杰" # 为学生2对象赋予名称属性值
1
2

# 成员方法

# 类的定义和使用

在上一节中,我们简单了解到可以使用类去封装属性,并基于类创建出一个个的对象来使用。
现在我们来看看类的使用语法:
class 类名称: 类的属性
类的方法

  • class是关键字,表示要定义类了
  • 类的属性,即定义在类中的变量(成员变量)
  • 类的行为,即定义在类中的函数(成员方法)

创建类对象的语法:
对象 = 类名称()

# 成员变量和成员方法

那么,什么是类的行为(方法)呢?

class Student:
    name = None
    age = None

    def say_hi(self):
        print(f"Hi大家好,我是{self.name}")
1
2
3
4
5
6
  • 类中定义的属性(变量),我们称之为:成员变量
  • 类中定义的行为(函数),我们称之为:成员方法

# 成员方法的定义语法

在类中定义成员方法和定义函数基本一致,但仍有细微差别:

def 方法名(self, 形参1...,形参N):
    方法体
1
2

可以看到,在方法定义的参数列表中,有一个:self关键字
self关键字是成员方法定义的时候,必须填写的。

  • 它用来表示类对象自身的意思
  • 当我们使用类对象调用方法的时候,self会自动被python传入
  • 在方法内部,想要访问类的成员变量,必须使用self

注意

self关键字,尽管在参数列表中,但是传参的时候可以忽略它。
如:

class Student:
    name = None

    def say_hi(self):
        print("Hello 大家好")

    def say_hi2(self, msg):
        print(f"Hello 大家好,{msg}")

stu = Student()
stu.say_hi() # 调用的时候无序传参
stu.say_hi2("很高兴认识你们") # 调用的时候,需要传msg参数
1
2
3
4
5
6
7
8
9
10
11
12

# 类和对象

class Clock:
    id:None
    price:None

    def ring(self):
        import winsound
        winsound.Beep(2000, 3000)

clock1 = Clock()
clock1.id = "003032"
clock1.price = 19.99
print(clock1.id, clock1.price)
clock1.ring()

clock2 = Clock()
clock2.id = "113032"
clock2.price = 79.99
print(clock2.id, clock2.price)
clock2.ring()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 构造方法

Python类可以使用:__init__()方法,称之为构造方法。
可以实现:

  • 在创建类对象(构造类)的时候,会自动执行。
  • 在创建类对象(构造类)的时候,将传入参数自动递给__init__()方法使用。
class Student:
    def __init__(self, name, age, tel):
        self.name = name
        self.age = age
        self.tel = tel
        print("Student类创建了一个类对象")

stu = Student("周杰伦", 31, "102104128412")
1
2
3
4
5
6
7
8
  • 重要的事情说三遍,构造方法名称__init__ __init__ __init__,千万不要忘记init前后都有2个下划线

  • 构造方法也是成员方法,不要忘记在参数列表中提供: self

# 魔术方法

上面的__init__构造方法,是Python类内置的方法之一。
这些内置方法称之为魔术方法

方法名称 作用
__init__ 构造方法
__str__ 字符串方法
__lt__ 小于,大于符号比较
__le__ 小于等于、大于等于符号比较
__eq__ ==符号比较

# __str__字符串方法

class Student:
    def __init__(self, name, age, address):
        self.name = name
        self.age = age
        self.address = address

stu_list = []

student = Student("周杰伦", 11, "长沙")
print(student)
# <__main__.Student object at 0x000002589602D550>
print(str(student))
# <__main__.Student object at 0x000002589602D550>
1
2
3
4
5
6
7
8
9
10
11
12
13

当类对象需要被转换为字符串之时,会输出如上结果(内存地址)

内存地址没有多大用,我们可以通过__str__方法,控制类转换为字符串的行为。

class Student:
    def __init__(self, name, age, address):
        self.name = name
        self.age = age
        self.address = address

    def __str__(self):
        return f"Student类对象,name={self.name},age={self.age},address={self.address}"


stu_list = []

student = Student("周杰伦", 11, "长沙")
print(student)
# Student类对象,name=周杰伦,age=11,address=长沙
print(str(student))
# Student类对象,name=周杰伦,age=11,address=长沙
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# __lt__小于符号比较方法

class Student:
    def __init__(self, name, age, address):
        self.name = name
        self.age = age
        self.address = address

    def __str__(self):
        return f"Student类对象,name={self.name},age={self.age},address={self.address}"


stu_list = []

student = Student("周杰伦", 11, "长沙")
student2 = Student("林俊杰", 13, "武汉")
print(student < student2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

直接对2个对象进行比较是不可以的,但是在类中实现__lt__方法,即可同时完成:小于符号和大于符号2种比较。

  • 方法名:lt
  • 传入参数: other, 另一个对象
  • 返回值: True 或 False
  • 内容: 自行定义
class Student:
    def __init__(self, name, age, address):
        self.name = name
        self.age = age
        self.address = address

    def __lt__(self, other):
        return self.age < other.age

stu_list = []

student = Student("周杰伦", 11, "长沙")
student2 = Student("林俊杰", 13, "武汉")
print(student > student2) # False
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# __le__小于等于比较符号方法

魔术方法:__le__可用于 <= 、>=两种比较运算符上。

class Student:
    def __init__(self, name, age, address):
        self.name = name
        self.age = age
        self.address = address

    def __le__(self, other):
        return self.age <= other.age

stu_list = []

student = Student("周杰伦", 11, "长沙")
student2 = Student("林俊杰", 13, "武汉")
print(student >= student2) # False
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# __eq__比较运算符实现方法

不实现__eq__方法,对象之间可以比较,但是是比较内存地址,不同对象比较一定是False结果。
实现了__eq__方法,就可以按照自己的想法来决定2个对象是否相等了。

class Student:
    def __init__(self, name, age, address):
        self.name = name
        self.age = age
        self.address = address

    def __eq__(self, other):
        return self.age == other.age

stu_list = []

student = Student("周杰伦", 11, "长沙")
student2 = Student("林俊杰", 11, "武汉")
print(student == student2) # True
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 封装

# 面向对象的三大特性

面向对象编程,是许多编程语言都支持的一种编程思想。
简单理解是:基于模板(类)去创建实体(对象),使用对象完成功能开发。

面向对象包含3打主要特性:

  • 封装
  • 继承
  • 多态

# 私有成员

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

  • 私有成员变量: 变量名以__开头(两个下划线)
  • 私有成员方法: 方法名以__开头(两个下划线)

私有方法无法直接被类对象使用,私有变量无法赋值也无法取值

class Phone:
    __current_voltage = None # 当前手机运行电压

    def __keep_single_core(self):
        print("让CPU以单核模式运行")

phone = Phone()
phone.__keep_single_core() # 报错!
1
2
3
4
5
6
7
8

注意

私有成员无法被类对象使用,但是可以被其他的成员使用。

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("正在通话中")


phone = Phone()

phone.call_by_5g()
# 5g关闭,使用4g网络
# 正在通话中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 继承

继承分为:单继承和多继承。
继承表示:将从父类那里继承(复制)来成员变量和成员方法(不含私有)

# 单继承

class Phone:
    IMEI = None # 序列号
    producer = None # 厂商

    def call_by_4g(self):
        print("4g通话")

class Phone2022(Phone):
    face_id = True

    def call_by_5g(self):
        print("2022最新5g通话")

phone = Phone2022()
phone.call_by_4g() # 4g通话
phone.call_by_5g() # 2022最新5g通话
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 多继承

语法:
class 类名(父类1, 父类2, ..., 父类N):
类内容体

class Phone:
    IMEI = None # 序列号
    producer = "HM" # 厂商

    def call_by_4g(self):
        print("4g通话")


class NFCReader:
    nfc_type = "第五代"
    producer = "ITCAST"

    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 # 补全语法的,表示这里是空的


phone = MyPhone()
phone.call_by_4g() # 4g通话
phone.read_card() # NFC读卡
phone.write_card() # NFC写卡
phone.control() # 红外遥控开启了
print(phone.producer) # HM
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

pass关键字的作用

pass是一个普通的占位语句,他用来保证我们的函数或者方法,或者说类定义的完整性,表示无内容,空的意思

多继承注意事项

多个父类中,如果有同名成员,那么默认以继承顺序(从左到右)为优先级,即:先继承的保留,后继承的被覆盖

# 复写

子类继承父类的成员属性和成员方法后,如果对其“不满意”,那么可以进行复写。
即:在子类中重新定义同名的属性或方法即可。

class Phone:
    IMEI = None # 序列号
    producer = "HM" # 厂商

    def call_by_4g(self):
        print("4g通话")


class MyPhone(Phone):
    producer = "ME"

    def call_by_4g(self):
        print("子类的call")


phone = MyPhone()
phone.call_by_4g() # 子类的call
print(phone.producer) # ME
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 调用父类同名成员

一旦复写父类成员,那么类的对象调用成员的时候,就会调用复写后的新成员,如果需要使用被复写的父类的成员,需要特殊的调用方式:

  • 方式一: 调用父类成员
    使用成员变量: 父类名.成员变量
    使用成员方法: 父类名.成员方法(self)














 
 








class Phone:
    IMEI = None # 序列号
    producer = "HM" # 厂商

    def call_by_4g(self):
        print("4g通话")


class MyPhone(Phone):
    producer = "ME"

    def call_by_4g(self):
        print("子类的call")
        # 方式1
        Phone.call_by_4g(self)
        print(f"父类的厂商是{Phone.producer}")


phone = MyPhone()
phone.call_by_4g()
# 子类的call
# 4g通话
# 父类的厂商是HM
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  • 方式二: 使用super()调用父类成员
    使用成员变量: super().成员变量
    使用成员方法: super().成员方法()














 
 








class Phone:
    IMEI = None # 序列号
    producer = "HM" # 厂商

    def call_by_4g(self):
        print("4g通话")


class MyPhone(Phone):
    producer = "ME"

    def call_by_4g(self):
        print("子类的call")
        # 方式2
        super().call_by_4g()
        print(f"父类的厂商是{super().producer}")


phone = MyPhone()
phone.call_by_4g()
# 子类的call
# 4g通话
# 父类的厂商是HM
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 类型注解

# 类型注解

Python在3.5版本的时候引入了类型注解,以方便静态类型检查工具,IDE等第三方工具。
类型注解:在代码中涉及数据交互的地方,提供数据类型的注解(显式的说明)。

主要功能:

  • 帮助第三方IDE工具对代码进行类型推断,协助做代码提示
  • 帮助开发者自身对变量进行类型注释

支持:

  • 变量的类型注解
  • 函数(方法)形参列表和返回值的类型注解

并不会真正的对类型做验证和判断。
也就是,类型注解仅仅是提示性的,不是决定性的。

# 类型注解语法

# 为变量设置类型注解

基础语法:
变量:类型

  • 基础数据类型注解
var_1: int = 10
var_2: float = 3.1415926
var_3: bool = True
var_4: str = "itheima"
1
2
3
4
  • 类对象类型注解
class Student:
    pass

stu: Student = Student()
1
2
3
4
  • 基础容器类型注解
my_list: list = [1, 2, 3]
my_tuple: tuple = (1, 2, 3)
my_set: set = {1, 2, 3}
my_dict: dict = {"itheima": 666}
my_str: str = "itheima"
1
2
3
4
5
  • 容器类型详细注解
my_list: list[int] = [1, 2, 3]
my_tuple: tuple[str, int, bool] = ("itheima", 666, True)
my_set: set[int] = {1, 2, 3}
my_dict: dict[str, int] = {"itheima": 666}
1
2
3
4

注意

  • 元组类型设置类型详细注解,需要将每个元素都标记出来
  • 字典类型设置类型详细注解,需要2个类型,第一个是key,第二个是value
  • 在注释中进行类型注解
    语法: # type: 类型
import random
import json


class Student:
    pass


data = '{"name":123}'

var_1 = random.randint(1, 10) # type: int
var_2 = json.loads(data) # type: dict[str, int]
var_3 = Student() # type: Student
1
2
3
4
5
6
7
8
9
10
11
12
13

# 函数(方法)的类型注解

  • 形参注解
def 函数方法名(形参名:类型, 形参名:类型,.....):
    pass
1
2
  • 返回值注解
def 函数方法名(形参名:类型, 形参名:类型,.....) -> 返回值类型:
    pass
1
2
def add(x:int, y:int) -> int:
    return x + y
1
2

# Union类型

from typing import Union

my_list: list[Union[str, int]] = [1, 2, "itheima", "itcast"]
my_dict: dict[str, Union[str, int]] = {"name":"周杰伦", "age": 13}
1
2
3
4

使用Union[类型,...类型],可以定义联合类型注解。

# 多态

多态,指的是:多种状态,即完成某个行为时,使用不同的对象会得到不同的状态。

def make_noise(animal: Animal):
    animal.speak()

dog = Dog()
cat = Cat()

make_noise(dog) # 输出:汪汪汪
make_noise(cat) # 输出:喵喵喵
1
2
3
4
5
6
7
8

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

# 抽象类(接口)

class Animal:
    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        print("汪汪汪")

class Cat(Animal):
    def speak(self):
        print("喵喵喵")
1
2
3
4
5
6
7
8
9
10
11

父类Animal的speak方法,是空实现,这种设计的含义是:

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

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

抽象类: 含有抽象方法的类称之为抽象类
抽象方法: 方法体是空实现的(pass)称之为抽象方法。

抽象类就好比定义一个标准,包含了一些抽象的方法,要求子类必须实现。