文章目录
- 一、面向对象与面向过程
- 1、什么是面向过程?
- 2、什么是面向对象?
- 二、类与对象
- 1. 初识对象
- 2. 类的成员方法
- 2.1 类的定义和使用
- 2.2 成员方法
- 3. 类和对象
- 4. 魔法方法
- 1. _ _ inint _ _ 构造方法
- 2. _ _ str _ _ 字符串方法
- 3. _ _ lt _ _ 小于符号比较方法
- 4. _ _ le _ _ 小于等于比较符号方法
- 5. _ _ eq _ _ 相等比较方法
- 三、面向对象的三大特征:封装、继承、多态
- 1. 封装
- 2. 继承
- 3. 多态
- 四、类型注解
- 1. 变量的类型注解
- 2. 基础容器类型注解
- 3. 类对象类型注解
- 4. 函数形参和返回值的类型注解
- 5. Union类型联合类型注解
一、面向对象与面向过程
面向对象编程(Object-Oriented Programming,简称OOP)和面向过程编程(Procedural Programming,简称PP)是两种不同的编程范式。
面向对象编程强调把问题分解成对象,通过封装、继承和多态等机制,来处理对象之间的关系。每个对象都可以独立地处理自己的数据和行为,而不需要依赖其他对象。面向对象编程更加注重代码的重用性、可维护性和可扩展性,适用于大型、复杂的软件系统开发。
而面向过程编程则是一种以过程为中心的编程方式,它将问题分解成一系列的步骤,然后按照顺序执行这些步骤,以达到求解问题的目的。在面向过程编程中,数据和操作是分离的,函数是处理数据的主要手段。面向过程编程更加注重效率和速度,适用于小型、简单的程序或者性能要求较高的场景。
1、什么是面向过程?
面向过程:问题分解成一系列的步骤,然后按照顺序执行这些步骤
举个简单的例子
相信大家都被问过这样一个问题: 把大象装入冰箱需要几步?
按照面向过程的思想:需要三步
第一步:打开冰箱
第二步:把大象塞进去
第三步:关上冰箱
从这里就可以看出:面向过程就是把一件事按步骤一步一步来实现
代码实现:
# 第一步:打开冰箱门
def open_door():
print("打开冰箱门")
# 第二步:把大象放进去
def put_elephant():
print("把大象放进去")
# 第三步:关闭冰箱门
def close_door():
print("关闭冰箱门")
# 完成三个步骤
def put_elephant_in_fridge():
open_door()
put_elephant()
close_door()
# 测试程序
put_elephant_in_fridge()
这就是面向过程代码的具体实现啦
2、什么是面向对象?
对象:就是对问题中的事物的抽象
对象可以说是对现实事物的一种抽象映射
。
面向对象:就是把现实中的事物都抽象为“对象”。每个对象是唯一的,且都可以拥有它的属性与行为。我们就可以通过调用这些对象的方法、属性去解决问题。
按照面向对象的思想
在这个例子中:
我们可以把大象看作一个对象,冰箱看作一个对象。
冰箱的一些功能:开门、装物体、关门
class Elephant: # 大象类
def Eat(self): # 吃
print('吃')
class Fridge: # 冰箱类
def open_door(self): # 打开冰箱门
print('打开冰箱门')
def Put_In(self): # 放入东西
print('放入东西')
def close_door(self): # 关闭冰箱门
print('关闭冰箱门')
在面向对象中,每个对象是独立的,有属于它自己的功能,只需要专心实现自己的功能就好。所以在建立对象模型阶段,仅仅关注对象有什么的功能,但不考虑如何实现这些功能。
面向对象对象的特点:有很好的延展性,比如我给大象赋予了一个吃的功能,它通过调用就可以在冰箱里去吃东西。面向对象就是把现实问题抽象为对象,通过调用每个对象的属性或功能去解决问题。
二、类与对象
1. 初识对象
什么是对象?
对象是面向对象编程中的一个概念,它是类的一个实例化(即具体化)的结果。对象是具体的、实际存在的,可以在程序中被创建、操作和销毁。
面向对象编程中,对象是由属性 和方法组成的。属性表示对象的状态和特征,方法表示对象可以执行的操作和行为
。
每个对象都属于某个类,类是对象的抽象,它定义了对象所具有的属性和方法的结构和行为。对象通过实例化类来创建,并且可以根据类的定义进行属性和方法的访问和调用。
以下是一个简单的示例:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def say_hello(self):
print(f"Hello, 我叫 {self.name}.")
# 创建两个Person对象
person1 = Person("张三", 25)
person2 = Person("李四", 30)
# 调用对象的方法
person1.say_hello() # 输出: Hello, 我叫张三.
person2.say_hello() # 输出: Hello, 我叫李四.
通过创建对象,我们可以根据类的定义来操作和管理数据,并执行对象所具有的行为。
2. 类的成员方法
2.1 类的定义和使用
在python中,用关键字class
来定义类
# 定义一个带有成员方法的类
class Student:
# 成员变量
name = None # 学生的姓名
# 定义方法
def say_hi1(self):
print(f'大家好,我叫{self.name}') # 成员方法中访问成员变量必须要用self关键字!
def say_hi2(self, msg):
print(f'大家好,我是{self.name},{msg}') # msg为外部传入的不需要用self!
stu1 = Student()
stu1.name = '张三'
stu1.say_hi1()
stu2 = Student()
stu2.name = '李四'
stu2.say_hi2('请多多关照')
运行结果:
可以看出,在类中:
- 不仅可以定义属性用来记录数据
- 也可以定义函数,用来记录行为
其中:
- 类中定义的属性(变量),我们称之为:成员变量
- 类中定义的行为(函数),我们称之为:成员方法
2.2 成员方法
语法:
在类中定义成员方法和定义函数基本一致,只存在细微区别:
注意:self关键字是成员方法定义的时候,必须填写的。
- 它用来表示类对象自身的意思
- 当我们使用类对象调用方法的是,self会自动被python传入
- self出现在形参列表中,但是不占用参数位置,无需理会
- 在方法内部,想要访问类的成员变量,必须使用self
例如:
class Student:
# 成员变量
name = None # 学生的姓名
# 定义方法
def say_hi(self):
print(f'大家好,我叫{self.name}')
class Student:
# 成员变量
name = None # 学生的姓名
def say_hi(self, msg):
print(f'大家好,{msg}') # msg为外部传入的不需要用self!
stu = Student()
stu.say_hi('请多多关照')
可以看到,在传入参数的时候,没有传入self,但也没有报错。
3. 类和对象
基于类创建对象的语法:
对象名 = 类名称()
为什么非要创建对象才能使用呢?
类只是一种程序内的“设计图纸”或者摸具,需要基于图纸或摸具生产实体(对象),才能正常工作这种套路,称之为:面向对象编程
举一个简单的例子:
类就相当于是闹钟的设计图纸,而对象就相当于按照闹钟的设计图纸所生产出来的闹钟。
从上面可以看出,设计出来的类包含编号和价格,具有响铃的功能。而基于类所创建的对象也有对应的编号和价格
所以面向对象编程就是设计类,基于类创建对象,由对象做具体的工作。
4. 魔法方法
内置的类方法,各自有各自特殊的功能,这些内置方法我们称之为:魔术方法
常见的魔法方法:
魔法方法 | 描述 |
---|---|
_ _ init _ _(self, …) | 初始化方法,用于初始化对象的属性,在对象创建时调用。 |
_ _ str _ _(self) | 将对象转换为字符串的方法,可以通过str(obj)或print(obj)来触发,返回对象的可读性好的字符串表示。 |
_ _ repr _ _(self) | 将对象转换为供解释器读取的形式的字符串,通常用于调试和开发,可以通过repr(obj)来触发。 |
_ _ len _ _(self) | 返回对象的长度,可以通过len(obj)来调用。 |
_ _ getitem _ _(self, key) | 获取对象的索引值,用于支持索引操作,如obj[key]。 |
_ _ setitem _ _(self, key, value) | 设置对象的索引值,用于支持赋值操作,如obj[key] = value。 |
_ _ delitem _ _(self, key) | 删除对象的索引值,用于支持删除操作,如del obj[key]。 |
_ _ iter _ _(self) | 返回一个迭代器,用于支持对象的迭代功能,如在for循环中使用。 |
_ _ next _ _(self) | 用于迭代器,返回迭代对象的下一个元素。 |
_ _ eq _ _(self, other) | 定义对象相等的比较方式,通常用于==操作符的比较。 |
_ _ lt _ _(self, other) | 定义对象小于的比较方式,通常用于<操作符的比较。 |
_ _ add _ _(self, other) | 定义对象相加的方式,通常用于+操作符的运算。 |
_ _ sub _ _(self, other) | 定义对象相减的方式,通常用于-操作符的运算。 |
_ _ mul _ _(self, other) | 定义对象相乘的方式,通常用于*操作符的运算。 |
_ _ div _ _(self, other) | 定义对象相除的方式,通常用于/操作符的运算。 |
上述表格因为语法限制,把下面的下划线之间都空了一格,实际上两条下划线之间并没有空格!
常见的魔法方法
1. _ _ inint _ _ 构造方法
class Student:
name = None # 姓名
age = None # 年龄
tel = None # 手机号
stu1 = Student()
stu1.name = '张三'
stu1.age = 18
stu1.tel = "1686400001"
stu2 = Student()
stu2.name = '李四'
stu2.age = 19
stu2.tel = "163200002"
上面代码中,为对象的属性赋值需要依次进行,略显繁琐。可不可以像函数传参那样直接一次性对属性进行赋值呢?
答案是肯定的,需要使用构造方法:_ _ init _ _()
Python类可以使用:_ _ init _ _() 方法,称之为构造方法。
可以实现:
- 在创建类对象(构造类)的时候,会自动执行。
- 在创建类对象(构造类)的时候,将传入参数自动传递给__init__方法使用。
class Student:
# 成员对象
"""
__init__构造方法里对成员变量进行了声明并赋值
所以成员对象这里可省略
"""
name = None
age = None
tel = None
def __init__(self, name, age, tel):
self.name = name
self.age = age
self.tel = tel
print('Student类创建了一个类对象') # 输出:Student类创建了一个类对象
stu1 = Student('张三', '18', '13066660')
print(stu1.name) # 输出:张三
print(stu1.age) # 输出:18
print(stu1.tel) # 输出:13066660
注意:
在构造方法内定义成员变量,需要使用self关键字
这是因为:变量是定义在构造方法内部,如果要成为成员变量,需要用self来表示。
2. _ _ str _ _ 字符串方法
- 方法名:_ _ str _ _
- 返回值:字符串
- 内容:自行定义
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
stu1 = Student('张三', 18)
print(stu1) # 输出:<__main__.Student object at 0x000001EDFFB09FD0>
print(str(stu1)) # 输出:<__main__.Student object at 0x000001EDFFB09FD0>
当类对象需要被转换为字符串之时,会输出如上结果(内存地址)
内存地址没有多大作用,我们可以通过 _ _ str _ _ 方法,控制类转换为字符串的行为。
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f'name:{self.name}, age:{self.age}'
stu1 = Student('张三', 18)
print(stu1) # 输出:name:张三, age:18
print(str(stu1)) # 输出:name:张三, age:18
3. _ _ lt _ _ 小于符号比较方法
- 方法名:_ _ lt _ _
- 传入参数:other,另一个类对象
- 返回值:True 或 False
- 内容:自行定义
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
stu1 = Student('张三', 18)
stu2 = Student('李四', 19)
print(stu1 < stu2)
print(stu1 > stu2)
像这样直接对2个对象进行比较是不可以的
在类中实现 _ _ lt _ _ 方法,可同时完成:小于符号和大于符号2种比较。
- 方法名:_ _ lt _ _
- 传入参数:other,另一个类对象
- 返回值:True 或 False
- 内容:自行定义
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
# __lt__魔法方法: 两个类对象进行大于或小于的比较
def __lt__(self, other):
return self.age < other.age
stu1 = Student('张三', 18)
stu2 = Student('李四', 19)
print(stu1 < stu2) # 输出:True
print(stu1 > stu2) # 输出:False
比较大于符号的魔术方法是:_ _ gt _ _, 方法和 _ _ lt _ _ 一样,所以实现了 _ _ lt _ _ ,_ _gt _ _ 就没必要实现了。
4. _ _ le _ _ 小于等于比较符号方法
这个和 _ _ lt _ _ 一样,唯一不同的是 _ _ lt _ _ 是比较大于或小于,而 _ _ le _ _ 则是比较大于等于和小于等于。
- 方法名:_ _ le _ _
- 传入参数:other,另一个类对象
- 返回值:True 或 False
- 内容:自行定义
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
# __lt__魔法方法: 两个类对象进行大于或小于的比较
def __lt__(self, other):
return self.age < other.age
stu1 = Student('张三', 18)
stu2 = Student('李四', 19)
print(stu1 <= stu2)
print(stu1 >= stu2)
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
# __lt__魔法方法: 两个类对象进行大于或小于的比较
def __lt__(self, other):
return self.age < other.age
# __le__魔法方法: 两个类对象进行大于等于或小于等于的比较
def __le__(self, other):
return self.age <= other.age
stu1 = Student('张三', 18)
stu2 = Student('李四', 19)
print(stu1 <= stu2) # 输出:True
print(stu1 >= stu2) # 输出:False
5. _ _ eq _ _ 相等比较方法
- 方法名:eq
- 传入参数:other,另一个类对象
- 返回值:True 或 False
- 内容:自行定义
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
stu1 = Student('张三', 18)
stu2 = Student('李四', 19)
print(stu1 == stu2) # 输出:False
不实现 _ _ eq _ _ 方法,对象之间也可以进行比较,但是是比较内存地址,也即是:不同对象==比较一定是False结果。
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
# __eq__魔法方法: 两个类对象进行相等的比较
def __eq__(self, other):
return self.age == other.age
stu1 = Student('张三', 18)
stu2 = Student('李四', 18)
print(stu1 == stu2) # 输出:True
实现了 _ _ eq _ _ 方法,就可以按照自己的想法来决定2个对象是否相等了。
三、面向对象的三大特征:封装、继承、多态
1. 封装
封装(Encapsulation):将数据和操作封装在对象中,使其成为一个独立的实体,外界只能通过对象提供的接口访问和操作内部数据。
对用户隐藏的属性和行为
现实世界中的事物,有属性和行为。但是不代表这些属性和行为都是开放给用户使用的。
私有成员
既然现实事物有不公开的属性和行为,那么作为现实事物在程序中映射的类,也应该支持。
类中提供了私有成员的形式来支持。
- 私有成员变量
- 私有成员方法
定义私有成员的方式非常简单,只需要:
- 私有成员变量:变量名以__开头(2个下划线)
- 私有成员方法:方法名以__开头(2个下划线)
class Phone:
IMEI = None # 序列号
producer = None # 厂商
# 私有成员变量
__current_voltage = None # 当前电压
def call_by_5g(self):
print('5g通话已开启')
# 私有成员方法
def __keep_5g(self):
print('保持5g')
私有方法无法直接被类对象使用
phone = Phone()
phone.__keep_5g()
使用私有成员
class Phone:
# 构造方法
def __init__(self, __is_5g_enable):
# 设置私有变量
self.__is_5g_enable = __is_5g_enable
# 设置私有方法
def __check_5g(self):
if self.__is_5g_enable == True: # 类内部访问私有成员要用self
print('5G开启')
else:
print('5G关闭,使用4G网络')
def call_by_5g(self):
self.__check_5g()
print('正在通话中...')
phone = Phone(False)
phone.call_by_5g()
2. 继承
继承(Inheritance):继承允许一个类(子类)继承另一个类(父类)的属性和方法,子类可以重用父类的代码,并且可以在不修改原有代码的情况下进行扩展和修改。
简单来说就是一个类,继承另外一个类的成员变量和成员方法
举个简单的例子:
手机的更新换代并不是完完全全重新开始,而是在原先的基础上新添一些属性和功能。
类比到程序中,我们也可以在已经设计好的类上进行更新和修改,这就会用到继承。
继承分为:单继承和多继承
单继承语法:
class 类名(父类):
类内容体
# 单继承演示
# 定义父类
class Phone:
IMEI = None # 序列号
producer = 'APPLE' # 厂商
def call_by_4g(self):
print('4g通话')
# 定义子类
class Phone2022(Phone):
face_id = '10001'
def call_by_5g(self):
print('5g通话')
phone = Phone2022()
phone.call_by_4g() # 输出:4g通话
print(phone.producer) # 输出:APPLE
phone.call_by_5g() # 输出:5g通话
print(phone.face_id) # 输出:10001
通过继承可以将从父类那里继承(复制)来成员变量和成员方法(不含私有)给子类使用。
多继承语法
Python的类之间也支持多继承,即一个类,可以继承多个父类
class 类名(父类1, 父类2, 父类3, ... , 父类N):
类内容体
举例:
# 多继承演示
# 定义父类
class Phone:
IMEI = None # 序列号
producer = 'HM' # 厂商
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('红外遥控开启')
# 定义子类
class MyPhone(Phone, NFCReader, RemoteControl):
pass
phone = MyPhone()
print(phone.producer) # 输出:HM
print(phone.nfc_type) # 输出:第五代
phone.read_card() # 输出:读取NFC卡
phone.call_by_4g() # 输出:4g通话
phone.control() # 输出:红外遥控开启
多继承注意事项:多个父类中,如果有同名的成员,那么**默认以继承顺序(从左到右)**为优先级。即:先继承的保留,后继承的被覆盖。
pass语句
pass是占位语句,用来保证函数(方法)或类定义的完整性,表示无内容,空的意思。
复写
子类继承父类的成员属性和成员方法后,如果对其“不满意”,那么可以进行复写。
即:在子类中重新定义同名的属性或方法即可。
# 定义父类
class Phone:
IMEI = None # 序列号
producer = 'APPLE' # 厂商
def call_by_4g(self):
print('4g通话')
# 定义子类
class MyPhone(Phone):
producer = 'HUAWEI' # 复写父类属性
def call_by_4g(self): # 复写父类方法
print('可用5g通话')
phone = MyPhone()
print(phone.producer) # 输出:HUAWEI
phone.call_by_4g() # 输出:可用5g通话
调用父类同名成员
一旦复写父类成员,那么类对象调用成员的时候,就会调用复写后的新成员
如果需要使用被复写的父类的成员,需要特殊的调用方式:
方式一:
- 调用父类成员
使用成员变量:父类名.成员变量
使用成员方法:父类名.成员方法(self)
方式二:
- 使用super()调用父类成员
使用成员变量:super().成员变量
使用成员方法:super().成员方法()
注意:只能在子类内调用父类的同名成员。子类的类对象直接调用会调用子类复写的成员
# 定义父类
class Phone:
IMEI = None # 序列号
producer = 'APPLE' # 厂商
def call_by_4g(self):
print('4g通话')
# 定义子类
class MyPhone(Phone):
producer = 'HUAWEI' # 复写父类属性
def call_by_4g(self): # 复写父类方法
print('可用5g通话')
# 调用父类属性和方法
# 方法一
# print(f'调用父类属性[1]:{Phone.producer}') # 调用父类属性
# Phone.call_by_4g(self) # 调用父类方法
# 方法二
print(f'调用父类属性[2]:{super().producer}') # 调用父类属性
super().call_by_4g() # 调用父类方法
phone = MyPhone()
print(phone.producer)
phone.call_by_4g()
3. 多态
多态(Polymorphism):多态使得对象可以根据上下文表现出不同的行为,同一个方法可以在不同对象上有不同的实现。这样可以提高代码的灵活性和可复用性。
也就是说:多种状态,即完成某个行为时,使用不同的对象会得到不同的状态。
如何理解?
同样的行为(函数),传入不同的对象,得到不同的状态
多态常作用在继承关系上。
比如
- 函数(方法)形参声明接收父类对象
- 实际传入父类的子类对象进行工作
即:
- 以父类做定义声明
- 以子类做实际工作
- 用以获得同一行为, 不同状态
抽象类(接口)
抽象类:含有抽象方法的类称之为抽象类
抽象方法:方法体是空实现的(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 cool_wind(ac: AC):
ac.cool_wind()
# 创建对象
midea = Midea_AC()
gree = GREE_AC()
cool_wind(midea) # 输出:美的空调制冷
cool_wind(gree) # 输出:格力空调制冷
四、类型注解
在PyCharm中编写代码,我们经常能够见到如下提示:
为什么PyCharm工具能够做到这一点?
因为:PyCharm确定这个对象,是list类型
同样,我们换一份代码:定义一个函数func,接收一个参数data,你会发现,PyCharm不会在做出任何提示了
为什么PyCharm工具无法提示了?
这是因为PyCharm不确定这个对象是什么类型
PyCharm无法通过代码确定应传入什么类型,我们需要使用类型注解
1. 变量的类型注解
基础语法: 变量: 类型
为变量设置注解,显示的变量定义,一般无需注解
# 变量的类型注
var1: int = 10
var2: str = '张三'
var3: bool = True
像上面这样:就算不写注解,也明确的知晓变量的类型
2. 基础容器类型注解
# 基础容器类型注解
my_list: list = [1, 2, 3]
my_tuple: tuple = (1, 2, 3)
my_dict: dict = {"name": "张三"}
# 容器类型详细注解
my_list1: list[int] = [1, 2, 3]
my_tuple1: tuple[int, str, bool] = (1, "张三", True)
my_dict1: dict[str, str] = {"name": "张三"}
注意:
- 元组类型设置类型详细注解,需要将每一个元素都标记出来
- 字典类型设置类型详细注解,需要2个类型,第一个是key第二个是value
3. 类对象类型注解
# 类对象类型注解
class Student:
pass
stu: Student = Student()
4. 函数形参和返回值的类型注解
函数和方法的形参类型注解语法:
def 函数名(形参1: 类型, 形参2: 类型, ..., 形参n: 类型):
pass
# 函数形参的类型注解
def add(x: int, y: int):
return x + y
返回值注解
语法:
def 函数名(形参1: 类型, 形参2: 类型, ..., 形参n: 类型) -> 返回值类型:
pass
# 函数形参和返回值的类型注解
def fnuc(data: list) -> list:
return data
除了使用 变量: 类型, 这种语法做注解外,也可以在注释中进行类型注解。
语法:# type: 类型
# 在注释中进行类型注解
var_1: random.randint(1, 10) # type: int
var_2: json.loads('{"name": "张三"}') # type: dict
5. Union类型联合类型注解
Union 类型用于指定一个变量可以是多种类型中的一种。
Union联合类型注解,在变量注解、函数(方法)形参和返回值注解中,均可使用。
Union的使用方式
导包:from typing import Union
语法:Union[类型, …, 类型]
# 使用Union类型,必须先导包
from typing import Union
my_list: list[Union[int, str, dict]] = [1, 2, 'name', {"age": 18}]
my_dict: dict[str, Union[int, str, list]] = {"name": "张三", "age": 18, "grade": [98, 97]}
my_list1: list[Union[int, str, dict[Union[str, int]]]] = [1, 2, 'name', {"age": 18}]
# 函数的Union类型
def func(data: Union[int, str]) -> Union[int, str]:
return data