面向对象基础

课程介绍

  • 面向对象基础
  • 面向对象高阶
  • 异常
  • 模块与包
  • 项目(ATM/Library)

面向对象是什么

面向对象编程 ——(Object Oriented Programming,简称 OOP),是一种以对象为中心的程序设计思想

面向过程编程 ——(Procedure Oriented Programming,简称 POP),是一种以过程为中心的程序设计思想

面向对象的总结和类与对象

面向过程

面向过程的核心是过程,过程就是只解决问题的步骤

  • 优缺点
    • 优点:将负责的问题流程化,进而实现简单化
    • 缺点:扩展性差(更新、维护、迭代)

总结:再去完成一些简单的程序时,可以使用面向过程去解决,但是如果有复杂的程序或任务,而且需要不断的进行迭代和维护,那么肯定是优先选择面向对象的编程思想

面向对象

面向对象的核心是对象,是一个特征和功能的综合体

  • 优缺点
    • 优点:可扩展性高
    • 缺点:变成复杂度相对面向过程高一些,指的是在计算机在执行面向对象的程序时的性能表现

面向对象中的一些名称(术语)

  • 类:类是对象的一个抽象的概念
  • 对象:对象是类创建的一个实例
  • 类和对象的关系就是:模具和铸件
    • 类是由对象总结而来的,总结的这个过程叫做抽象
    • 对象是由类具体实施出来的,这个过程叫做实例化

面向对象-基本实现

如果需要实例一个对象,那么需要先抽象一个类

  • 类名的书写规范:使用驼峰命名法
    • 大驼峰:MyCar、XiaoMi
    • 小驼峰:myCar、xiaoMi

例如需要创建一个汽车对象,先要创建一个汽车类

  • 使用 class 定义一个类
  • 一个类中有属性和方法
    • 属性:就是特征,即变量,类中称为属性;分为类属性和实例属性
    • 方法:就是功能,即函数,在类中称为方法;分为类方法和实例方法
  • 类中的属性一般定义在方法前面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Cart():
# 属性
color = '白色'
brand = 'Audi'
pailiang = 2.4

# 方法
def lahuo(self):
print(f'{self.color}{self.brand}能拉货')

def doufeng(self):
print(f'排量{self.pailiang}能兜风')


mycar = Cart()
print(mycar, type(mycar)) # <__main__.Cart object at 0x100b338d0> <class '__main__.Cart'>
print(mycar.brand) # Audi
mycar.lahuo() # 白色的Audi能拉货
mycar.doufeng() # 排量2.4能兜风

对象成员的操作

什么是对象的成员? 一个对象通过实例化之后,在类中定义的属性和方法,可以使用实例化的对象进行操作 类中定义的属性就是成员属性,类中定义的方法就是成员方法

对象的成员操作:

  • 在类的外部,使用对象操作成员
    • 通过对象访问类中的属性
      • 先访问对象自己的属性
      • 若对象自己没有这个属性,就去访问对象的类的属性
    • 修改对象访问的类的属性
      • 实际上修改的并不是对象的类的属性
      • 而是创建了一个对象自己的属性并赋值
    • 给对象添加属性
      • 这是给对象创建了自己的属性 不是对象的类的属性
    • 删除属性
      • 只能删除对象属性,不能删除对象访问的类属性
  • 在类的外部 通过对象,访问类中的方法
    • 访问对象的方法
      • 先访问对象自己的方法
      • 若对象自己没有这个方法,则访问对象的类的方法
    • 修改对象的方法
      • 实际上修改的并不是对象的类方法
      • 而是创建了一个对象的方法并调用
    • 给对象添加方法
      • 这创建的是对象的新方法,并没有给对象的类创建方法
    • 删除对象的方法
      • 同样只能删除对象的方法,对象的类的方法无法删除
  • 总结:一个类定义类成员属性和成员方法,那么通过这个类实例化的对象,也具备这些方法和属性嘛?
    • 实际上,在创建对象的时候,并不会把类中的属性和方法也复制一份给对象,实际上是在对象中引用类的方法,所以不同对象调用同一个属性或方法,是相通的。
    • 因此在访问对象的属性时,会先去寻找对象自己的属性,如果没有去找类的属性和方法
    • 而如果给对象的属性或方法进行修改或者添加的时候,等于给对象创建了一个自己的属性和方法,并不调用类的,而且删除的时候也只能删除对象本身的添加或修改的那个属性和方法。
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
class Cart():
# 属性
color = '白色'
brand = 'Audi'
pailiang = 2.4

# 方法
def lahuo(self):
print(f'{self.color}{self.brand}能拉货')

def doufeng(self):
print(f'排量{self.pailiang}能兜风')


a = Cart()
b = Cart()
print(a) # <__main__.Cart object at 0x104e27250>
print(b) # <__main__.Cart object at 0x104e31450>

# 通过对象访问类中的属性
res = a.color
print(res) # 白色
res = b.color
print(res) # 白色

# 修改对象的属性
a.color = '黑色'
print(a.color) # 黑色
print(b.color) # 白色

# 给对象添加属性
a.name = 'A6L'
print(a.name) # A6L
try:
print(b.name) # b不存在
except:
print('b不存在')

# 删除属性,只能删除对象属性,不能删除对象访问的类属性
try:
del a.name
print('成功') # 成功
except:
print('不成功')

try:
del a.brand # 不能删除对象的类属性
except:
print('不能删除对象的类属性')

# 访问对象的方法
a.doufeng() # 排量2.4能兜风


# 修改对象的方法
def newfunc():
print('这是一个新的方法')


a.doufeng = newfunc
a.doufeng() # 这是一个新的方法

# 给对象添加方法
a.xiuche = newfunc
a.xiuche() # 这是一个新的方法

# 删除对象的方法
try:
del a.brand # 不能删除类方法
print('可以删除类方法')
except:
print('不能删除类方法')

try:
del a.xiuche # 可以删除对象的方法
print('可以删除对象的方法')
except:
print('不能删除对象的方法')

类成员的操作

  • 访问类成员属性、方法
    • 使用类名.类属性进行访问
    • 使用 getattr()方法
    • 使用类名.类方法(类名)进行访问
  • 修改类成员属性、方法
    • 使用类名.类属性=新值进行修改
    • 使用 setattr()方法
    • 使用类名.类方法=新方法()进行修改
    • 修改后的类成员属性、方法就会彻底更新,创建一个新的对象调用类属性、方法,属性、方法会更新
  • 添加类成员属性、方法
    • 使用类名.新类属性=新值进行新增
    • 使用 setattr()方法
    • 使用类名.新类方法=新方法()进行新增
    • 新增的类属性、类方法,可以被创建的对象访问到
  • 删除类成员属性、方法
    • 使用 del 类名.类属性删除类成员属性
    • 使用 delattr()方法
    • 使用 del 类名.类方法删除类成员方法
    • 删除之后再使用对象访问类属性、方法会访问不到
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
class Cart():
# 属性
color = '白色'
brand = 'Audi'
pailiang = 2.4

# 方法
def lahuo(self):
print(f'{self.color}{self.brand}能拉货')

def doufeng(self):
print(f'排量{self.pailiang}能兜风')


# 访问类成员属性
print(Cart.brand) # Audi

# 修改类成员属性
Cart.brand = '宝马'
print(Cart.brand) # 宝马
opp = Cart()
print(opp.brand) # 宝马

# 添加类成员属性
Cart.name = 'A7'
print(Cart.name) # A7
opp = Cart()
print(opp.name) # A7

# 删除类成员属性
del Cart.brand
try:
opp = Cart()
print(opp.brand) # opp没有brand
except:
print('opp没有brand')

# 访问类成员方法
Cart.doufeng(Cart) # 排量2.4能兜风


# 修改类成员方法
def newm(self):
print('这是一个新的方法')


Cart.doufeng = newm
Cart.doufeng(Cart) # 这是一个新的方法
opp = Cart()
opp.doufeng() # 这是一个新的方法

# 添加类成员方法
Cart.xinfangfa = newm
Cart.xinfangfa(Cart) # 这是一个新的方法
opp = Cart()
opp.xinfangfa() # 这是一个新的方法

# 删除类成员方法
del Cart.doufeng
try:
Cart.doufeng() # 类的成员对象已删除
print('类的成员对象')
except:
print('类的成员对象已删除')

opp = Cart()
try:
opp.doufeng() # 对象没有doufeng
print('对象还有doufeng')
except:
print('对象没有doufeng')

类与对象的总结

  • 一个类可以实例化出多个对象,每个对象在内存中都独立存在
  • 当通过类实例化对象时,并不会把类中的成员复制一份给对象,而去给对象了一个引用
  • 访问对象成员的时候,如果对象自己没有这个成员,对象会向实例化它的类去查找
  • 对象成员的添加和修改,都只会影响当前对象自己,不会影响类和其他对象
  • 删除对象的成员时,必须是该对象自己具备的成员才可以
  • 对类的成员操作,会影响通过这个类创建的对象,包括之前创建的

self 详解

  • self 在方法中只是一个形参,并不是关键字
  • self 在方法中代表的是当前这个对象自身
  • self 在方法中代表对象可以去操作成员,可以使用 self 在类的内部访问成员
  • 只要是对象能完成的事情,self 都可以
  • 在类中定义的方法,都需要有一个形参 self,
    • 这样的方法称为非绑定类方法
    • 可以使用对象和类去访问
  • 否则,类中的方法没有形参 self,对象将无法调用这个方法,但是可以使用类去调用
    • 这样的方法称为绑定类方法
    • 只能使用类去访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person():
name = 'xiaoduan'
age = 23

# 非绑定类方法
def introduce(self):
print(f'i am {self.name} {self.age} years old')

# 绑定类方法
def func():
print('这是一个绑定类方法')


duan = Person()
duan.introduce() # i am xiaoduan 23 years old
Person.func() # 这是一个绑定类方法

初始化方法_魔术方法

魔术方法:

魔术方法也和普通方法一样都是类中定义的成员方法 就是不需要去手动调用的,会在某种情况下自动触发(自动执行) 特殊点:多数的魔术方法 前后都有两个连续的下划线 魔术方法不是我们自己定义的,是系统定义好的,我们使用

  • init 初始化方法
    • 通过类实例化对象之后,自动触发的一个方法
    • 作用:可以在对象实例化之后完成对象的初始化(属性的赋值,方法的调用)
    • 应用场景:文件的打开,数据的获取
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
class Person():
name = 'xiaoduan'
age = 23

def __init__(self):
print('这是一个初始化方法')
self.introduce()

def introduce(self):
print(f'i am {self.name} {self.age} years old')


class Person2():
name = None
age = None

def __init__(self, name, age):
self.name = name
self.age = age
print(f'i am {self.name} {self.age} years old')


duan = Person() # 这是一个初始化方法
# i am xiaoduan 23 years old
huang = Person2('huang', 24) # i am huang 24 years old

析构方法

  • del 析构方法
    • 当前类实例化的对象被销毁时,自动触发
    • 作用:可以在析构方法中完成一些特殊任务,关闭一些打开的资源,比如在初始化方法中打开的文件,可以在析构方法中关闭
    • 注意:对象被销毁时,触发了这个方法,而不是这个方法销毁了对象
    • 对象在哪些情况会被销毁:
      • 当程序执行完毕,内存中所有的资源都会被销毁
      • 使用 del 删除对象
      • 对象不再被引用时,会自动销毁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class writeLog():
# 成员属性
# 文件的路径
fileurl = './'
# 文件的名称
filename = '2022-3-9'

def __init__(self):
print('写入日志初始化方法')
self.fileobj = open(self.fileurl+self.filename,'a+',encoding='utf-8')

def log(self,s):
print(f'日志内容为:{s}')
self.fileobj.write(s)

def __del__(self):
print('析构方法触发')
self.fileobj.close()

obj = writeLog()
obj.log('今天温度适宜出行')

日志类的封装

  • 日志类
    • class Mylog
    • 功能:能够随时写入一个日志信息
  • 分析
    • 日志文件在什么地方? 默认在当前目录
    • 日志文件名是什么? 当前日期 2022-03-11.log
    • 日志的格式是什么样的? 2022-03-11 12:12:12 错误信息
    • 属性:成员属性的作用就是存储信息,供成员方法来使用
      • fileurl 日志文件的地址
      • filename 日志文件的名称
      • fileobj 打开的文件对象
    • 方法:具体完成的一个功能的过程
      • init() ==> 初始化方法,完成对象的初始化,并打开文件
      • wlog() ==> 负责接受用户给的日志信息,并写入到日志文件中
      • del() ==> 析构方法,在对象被销毁时,关闭打开的文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import time

class Mylog():
fileurl = './'
filename = str(time.strftime('%Y-%m-%d')) + '.log'
fileobj = None

def __init__(self):
# 打开文件
self.fileobj = open(self.fileurl+self.filename,'a+',encoding='utf-8')

def wlog(self,s):
date = time.strftime('%Y-%m-%d %H:%M:%S')
msg = date+' '+s+'\n'
self.fileobj.write(msg)

def __del__(self):
# 关闭打开的文件
self.fileobj.close()

log = Mylog()
log.wlog('变量赋值错误')

面向对象的三大特性:封装、继承、多态

封装

封装就是使用特殊的语法,对成员属性和成员方法进行包装,达到保护和隐藏的目的 但是封装是为了限制一些访问和操作,但是不能全部都限制(不能不让使用) 被封装的成员只是限制了访问权限,并不是不让访问 通常情况下,被封装的成员主要是供内部使用

封装的级别

  • 公有的 public
    • 就是类中普通的成员属性
  • 受保护的 protected
    • 在类中的成员属性前 加一个 _ 受保护的成员
  • 私有的 private
    • 在类中的成员属性前 加两个 __ 私有的成员
封装的级别 公有的 public 受保护的 protected 私有的 private
在类的内部 OK OK OK
在类的外部 OK NO(Python 可以) NO

实际上私有是这么实现的 对于成员属性 ===> _类名成员属性 进行了改写

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
37
38
39
40
class Resume():
# 成员属性
name = None
_age = None
__gender = None

def __init__(self, name, age, gender):
self.name = name
self._age = age
self.__gender = gender

# 成员方法
def introduce(self):
print(f'This is {self.name}')
print(self.__gender)

def _experience(self):
print(f'I am {self._age} years old')

def __superior(self):
print(f'I am a {self.__gender}')


# 实例化对象
duanduan = Resume('duanduan', 23, 'female')
# 查看对象的所有属性
print(duanduan.__dict__)

try:
print(duanduan.name)
print(duanduan._age)
# print(duanduan.__gender) # 在类的外部不能操作私有成员
except:
print('NO')

duanduan._experience() # I am 23 years old
# duanduan.__superior() # 在类的外部不能操作私有成员
duanduan.introduce()
# This is duanduan
# female

继承

单继承

什么是继承? 在面向对象中,一个类去继承父类,这个类就拥有了父类的所有成员(除了私有成员)

概念

  • 被其他类继承的类,这个类称为父类 也叫做基类 或者 超类
  • 继承其他类的类,这个类称为子类 也叫做派生类
  • 子类可以有自己独立的成员,也可以没有
  • 在不指定继承的父类时,所有类都继承自 object 类(系统提供)
  • 子类继承了父类之后,就拥有了父类中所有的成员包括魔术方法(除了私有成员)
  • 子类继承父类后,重新定义了父类中的方法,这种情况称为对父类方法的重写
    • 在子类中可以直接调用父类中定义的方法super().父类方法名()
  • 子类继承父类后,定义了父类中没有的方法,这种情况称为对父类的扩展
  • 一个父类可以被多个子类继承
  • 子类调用父类方法时,如果该方法有参数要求,也需要传递参数

继承的意义 提高代码的重用性,建立新的类与类的关系,方便其他逻辑的操作

继承语法格式

1
2
3
4
5
class 父类():
pass

class 子类(父类):
pass
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
37
38
class maoke():
# 属性
maose = '猫纹'
sex = 'm'

# 方法
def pao(self):
print('走猫步')

def pa(self):
print('能上树')


# 定义猫类 继承 猫科
class mao(maoke):

def zhua(self):
print('喜欢抓老鼠')

def pa(self):
super().pa()
print('可以上树很快')


class bao(maoke):
pass


h = mao()
print(h.__dict__) # {}
print(h.maose) # 猫纹
h.zhua() # 喜欢抓老鼠
h.pa()
# 能上树
# 可以上树很快

b = bao()
b.pa() # 能上树

多继承

  • 单继承
    • 一个类只能继承一个父类
  • 多继承
    • 一个类可以去继承多个父类

多继承语法格式

1
2
3
4
5
6
7
8
class 父亲():
pass

class 母亲():
pass

class 子类(父亲,母亲):
pass
  • 如果在多继承的时候使用了super(),这个时候继承的方法优先是是第一个父类的,若第一个父类没有,寻找下一个父类
  • 同时,在多继承时,若想指定某个父类的方法,使用父类.方法名(self)
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
# 父亲类
class F():
def eat(self):
print('魁梧、强壮')

def hello(self):
print("F hello")


# 母亲类
class M():
def eat(self):
print('温文、尔雅')

def hello(self):
print("M hello")


# 子类
class C(F, M):
def eat(self):
super().eat() # 魁梧、强壮
print('挑食、闹腾')

def hello(self):
# 指定使用父类的hello方法
F.hello(self)
print("C hello")


c = C()
c.eat() # 挑食、闹腾
c.hello()

菱形继承(钻石继承)

1
2
3
A
B C
D
  • 可以使用 mro 方法获取 MRO 列表,就是类的继承关系
    • 使用 super 去调用父级的方法时,实际上是在用 super 调用 MRO 列表中的上一级中的方法
    • 使用 super 去访问父级的属性时,实际上是在用 super 访问 MRO 列表中的上一级中的属性
  • super()本身调用父级方法时,传递的 self 对象,就是这个方法中的那个 self 对象自己
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# 人类
class Human():
num = 1

def eat(self):
print(self.num)
print('最最年长')


# 父亲类
class F(Human):
num = 2

def eat(self):
super().eat()
print(super().num)
print('魁梧、强壮')


# 母亲类
class M(Human):
num = 3

def eat(self):
super().eat()
# print(super().num)
print('温文、尔雅')


# 子类
class C(F,M):
num = 4

def eat(self):
super().eat() # 魁梧、强壮
print(super().num) # 2
print('挑食、闹腾')


c = C()
c.eat()
# 4
# 最最年长
# 温文、尔雅
# 3
# 魁梧、强壮
# 2
# 挑食、闹腾

print(C.mro()) #[<class '__main__.C'>, <class '__main__.F'>, <class '__main__.M'>, <class '__main__.Human'>, <class 'object'>]

继承关系检测

在实现继承的语法之后,程序会自动生成一个继承的列表(MRO(Method Realtion Order) 方法关系列表

  • MRO 列表生成原则:
  1. 子类永远在父类前
  2. 同一等级的类,按照子类中的继承顺序摆放
  3. 先子类,后父类的顺序原则,最终的类是系统提供的 object 类

[<class ‘**main**.C’>, <class ‘**main**.F’>, <class ‘**main**.M’>, <class ‘**main**.Human’>, <class ‘object’>]

super()在调用的时候,并不是查找父类,而是去 MRO 列表上找上一个类 super()方法在调用时,会自动把当前 self 传入到上一级的类的方法中

类关系检测 issubclass() issubclass() 是一个类是否是另一个类的子类

1
2
3
4
5
6
7
# 沿用之前的类
print(
C.mro()) # [<class '__main__.C'>, <class '__main__.F'>, <class '__main__.M'>, <class '__main__.Human'>, <class 'object'>]

print(issubclass(C, (F, M))) # True
print(issubclass(F, M)) # False
print(issubclass(C, Human)) # True

多态

对于同一个方法,由于调用的对象不同,产生了不同形态的结果

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
class Computer():

def usb(self, obj):
obj.start()


class Mouse():
def start(self):
print('鼠标启动成功')


class KeyBoard():
def start(self):
print('键盘启动成功')


class Udisk():
def start(self):
print('U盘启动了')


c = Computer()
m = Mouse()
k = KeyBoard()
u = Udisk()

c.usb(m) # 鼠标启动成功
c.usb(k) # 键盘启动成功
c.usb(u) # U盘启动了

多态——继承版

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
class USB():
# 在USB类中定义一个规范的接口方法,但是不实现任何功能
def start(self):
pass


class Mouse(USB):
def start(self):
print('鼠标启动成功')


class KeyBoard(USB):
def start(self):
print('键盘启动成功')


class Udisk(USB):
def start(self):
print('U盘启动了')


m = Mouse()
k = KeyBoard()
u = Udisk()

m.start() # 鼠标启动成功
k.start() # 键盘启动成功
u.start() # U盘启动了

更新: 2024-01-23 06:42:42
原文: https://www.yuque.com/zacharyblock/cx2om6/vak1xgsu38h6xhbv