Python面向对象
类的空间问题
添加对象或类的属性
添加对象或类的属性哪里(类__init__
方法中、类方法中、类外)都可以添加。
1 | class A: |
对象以及类查询"名字"的顺序
对象查找属性的顺序:先从对象空间找 ------> 类空间找 ------> 父类空间找 ------->…
类名查找属性的顺序:先从本类空间找 -------> 父类空间找--------> …
上面的顺序都是单向不可逆,类名不可能找到对象的属性。
类与类之间的关系
依赖关系
将一个类的对象或者类名传到另一个类的方法使用. 这种关系比较弱。
组合(关联、聚合)关系
关联关系, 其实就是 我需要你. 你也属于我
组合:将一个类的对象封装到另一个类的对象的属性中,就叫组合。
继承
继承分为单继承和多继承
- python中类的分类
这里需要补充一下python中类的种类(继承需要): 在python2x版本中存在两种类.:
⼀个叫经典类. 在python2.2之前. ⼀直使⽤的是经典类. 经典类在基类的根如果什么都不写.
⼀个叫新式类. 在python2.2之后出现了新式类. 新式类的特点是基类的根是object类。
python3x版本中只有一种类:
python3中使⽤的都是新式类. 如果基类谁都不继承. 那这个类会默认继承 object类。
单继承
- 类名.对象执行父类方法
- 执行顺序
- 实例化对象时必须执行
__init__
方法,类中没有,从父类找,父类没有,从object
类中找。 - 先要执行自己类中的eat方法,自己类没有才能执行父类中的方法。
- 实例化对象时必须执行
- 同时执行类以及父类方法
- 子类方法中
父类.func(对象,其他参数)
super().func(参数)
- 子类方法中
多继承
一个类继承多个类
经典类
沿用深度优先算法
- 从左至右,依次查找。
- 每次都选取节点的最左边,一直找到头,如果没有,返回上一个节点在查询其他路线。
- 如果上一个节点没有其他路线或者都已经查询完毕,再返回上一个节点,直至遍历完所有的节点
新式类
沿用
c3
算法MRO
super()
示例一
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class A:
def f1(self):
print('in A f1')
def f2(self):
print('in A f2')
class Foo(A):
def f1(self):
super().f2()
print('in A Foo')
obj = Foo()
obj.f1()示例二
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
27class A:
def f1(self):
print('in A')
class Foo(A):
def f1(self):
super().f1()
print('in Foo')
class Bar(A):
def f1(self):
print('in Bar')
class Info(Foo,Bar):
def f1(self):
super().f1()
print('in Info f1')
obj = Info()
obj.f1()
'''
in Bar
in Foo
in Info f1
'''
print(Info.mro()) # [<class '__main__.Info'>, <class '__main__.Foo'>, <class '__main__.Bar'>, <class '__main__.A'>, <class 'object'>]示例三
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class A:
def f1(self):
print('in A')
class Foo(A):
def f1(self):
super().f1()
print('in Foo')
class Bar(A):
def f1(self):
print('in Bar')
class Info(Foo,Bar):
def f1(self):
super(Foo,self).f1()
print('in Info f1')
obj = Info()
obj.f1()
封装
将属性写到__init__
中
多态
同样名称的方法在不同的子类中会有不同的行为。
同⼀个对象, 多种形态. 这个在python中其实是很不容易说明⽩的. 因为我们⼀直在⽤. 只是没有具体的说. 比如. 我们创建⼀个变量a = 10 , 我们知道此时a是整数类型. 但是我们可以通过程序让a = “太白”, 这时, a⼜变成了字符串类型. 这是我们都知道的. 但是, 我要告诉你的是. 这个就是多态性. 同⼀个变量a可以是多种形态。
对类的约束
- 提取⽗类. 然后在⽗类中定义好⽅法. 在这个⽅法中什么都不⽤⼲. 就抛⼀个异常就可以了. 这样所有的⼦类都必须重写这个⽅法. 否则. 访问的时候就会报错.
- 使⽤元类来描述⽗类. 在元类中给出⼀个抽象⽅法. 这样⼦类就不得不给出抽象⽅法的具体实现. 也可以起到约束的效果.
- 方式一:
1 | class Payment: |
- 方式二:引入抽象类的概念
1 | from abc import ABCMeta,abstractmethod |
抽象类和接口类做的事情 :建立规范,只要子类继承了我写的这个抽象类,实例化对象时就会报错。
类的私有成员
对于每一个类的成员而言都有两种形式:
- 公有成员,在任何地方都能访问
- 私有成员,只有在类的内部才能方法
类 | 类 | 类内部 | 派生类 |
---|---|---|---|
公有类的静态属性 | 可以访问 | 可以访问 | 可以访问 |
公有方法 | 可以访问 | 可以访问 | 可以访问 |
私有类的静态属性 | 不可以访问 | 可以访问 | 不可以访问 |
私有方法 | 不可以访问 | 可以访问 | 不可以访问 |
对象属性 | 对象 | 类内部 | 派生类 |
---|---|---|---|
公有对象属性 | 可以访问 | 可以访问 | 可以访问 |
私有对象属性 | 不可以访问 | 可以访问 | 不可以访问 |
对于这些私有成员来说,他们只能在类的内部使用,不能再类的外部以及派生类中使用.
类的其他成员
类方法
使用装饰器@classmethod。第一个参数必须是当前类对象,该参数名一般约定为“cls”,通过它来传递类的属性和方法(不能传实例的属性和方法);
调用:实例对象和类对象都可以调用。
1 | class Student: |
静态方法
定义:使用装饰器@staticmethod。参数随意,没有“self”和“cls”参数,但是方法体中不能使用类或实例的任何属性和方法;
调用:实例对象和类对象都可以调用。
静态方法是类中的函数,不需要实例化。可以理解为,静态方法是个独立的、单纯的函数,仅仅托管于某个类的名称空间中,便于使用和维护。
1 | import time |
属性
property
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
1 | class Foo: |
isinstance
isinstance(a,b):判断a是否是b类(或者b类的派生类)实例化的对象
1
2
3
4
5
6
7
8
9
10
11
12
13class A:
pass
class B(A):
pass
obj = B()
print(isinstance(obj,B))
print(isinstance(obj,A))
isinstanceissubclass(a,b): 判断a类是否是b类(或者b的派生类)的派生类
1
2
3
4
5
6
7
8
9
10
11
12
13class A:
pass
class B(A):
pass
class C(B):
pass
print(issubclass(B,A))
print(issubclass(C,A))
issubclass
反射
python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)
Python实现自省的函数hasattr
、getattr
、setattr
、delattr
应用于对象的反射
1 | class Foo: |
应用于类的反射
与对象基本相同
反射的作用
更优雅的写代码😆
1 | class User: |
异常处理
AttributeError 试图访问一个对象没有的树形,比如foo.x,但是foo没有属性x
IOError 输入/输出异常;基本上是无法打开文件
ImportError 无法引入模块或包;基本上是路径问题或名称错误
IndentationError 语法错误(的子类) ;代码没有正确对齐
IndexError 下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5]
KeyError 试图访问字典里不存在的键
KeyboardInterrupt Ctrl+C被按下
NameError 使用一个还未被赋予对象的变量
SyntaxError Python代码非法,代码不能编译(个人认为这是语法错误,写错了)
TypeError 传入对象类型与要求的不符合
UnboundLocalError 试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量,
导致你以为正在访问它
ValueError 传入一个调用者不期望的值,即使值的类型是正确的
基本语法:
1 | try: |
多分支+万能异常
1 | dic = { |
try…except…else组合
与循环中的else比较类似,try代码中,只要出现了异常,则不会执行else语句,如果不出现异常,则执行else语句。
比如我们完成一个转账功能的代码,需要一个转账给另一个人,然后另一个人确认收到才算是转账成功,我们用伪代码写一下,他就可以用在这个地方:
1
2
3
4
5
6
7
8
9
10# 伪代码
try:
print('扣第一个人钱')
...
print('给第二个人加钱')
except ValueError:
print('必须输入数字。。。')
else:
print('转账成功')Try…excet…finally组合
finally这个用法比较有意思,他是在捕获异常发生之前,先执行finally的代码,有点未卜先知的意思。
如果出现异常并且成功捕获了,finally会在try中最后执行。
1
2
3
4
5
6
7
8
9try:
dic = {'name': '太白金星'}
print(dic[1])
except KeyError:
print('出现了keyError错误....')
finally:
print('正常执行')如果出现异常但是没有成功捕获,finally会在异常发生之前执行。
1
2
3
4
5
6
7
8
9try:
dic = {'name': '太白金星'}
print(dic[1])
except NameError:
print('出现了NameError错误....')
finally:
print('异常发生之前,先执行我')finally用在哪里呢?
- 关闭文件的链接链接,数据等链接时,需要用到finally。
1
2
3
4
5
6
7
8f = open('file',encoding='utf-8')
try:
'''各种操作'''
print(f.read())
'''但是发生错误了, 此时没关闭文件句柄,所以'''
finally:
f.close()- 函数中,finally也会在return之前先执行。
1
2
3
4
5
6def func():
try:
return 1
finally:
print('finally')
func()- 循环中,finally也会在return之前执行。
1
2
3
4
5while 1:
try:
break
finally:
print('finally')finally一般就是收尾工作,在一些重要环节出错之前必须一定要做的比如关闭链接的问题时,最好是用上finally作为最后一道防线,收尾。
主动出发异常
在类的约束中,我们已经用过此方法,主动发出异常
1
raise TypeError('类型错误')
断言
表示一种强硬的态度,只要assert后面的代码不成立,直接报错,下面的代码就不让你执行。
1
2
3
4
5
6
7
8
9
10# assert 条件
assert 1 == 1
assert 1 == 2
# 应用:
assert 条件
代码
代码
.......自定义异常
python中给你提供的一些错误类型并不是所有的,只是常见的异常,如果以后你在工作中,出现了某种异常无法用已知的错误类型捕获(万能异常只能捕获python中存在的异常),那么你就可以尝试自定义异常,只要继承BaseException类即可。
1
2
3
4
5
6
7
8
9
10class EvaException(BaseException):
def __init__(self,msg):
self.msg=msg
def __str__(self):
return self.msg
try:
raise EvaException('类型错误')
except EvaException as e:
print(e)异常处理正确的使用方式
有的同学会这么想,学完了异常处理后,好强大,我要为我的每一段程序都加上try…except,干毛线去思考它会不会有逻辑错误啊,这样就很好啊,多省脑细胞===》2B青年欢乐多
try…except应该尽量少用,因为它本身就是你附加给你的程序的一种异常处理的逻辑,与你的主要的工作是没有关系的
这种东西加的多了,会导致你的代码可读性变差,只有在有些异常无法预知的情况下,才应该加上try…except,其他的逻辑错误应该尽量修正