2020-3-27 学习面向对象的笔记
1. 了解面向对象
我刚接触编程世界,首先学习的语言是python,为什么?python与其语言相比来说:最重要的语法简单,灵活,让人容易理解。对于C语言:面向过程,函数式编程,代码从上到下的执行;Java语言:它没有面向过程编程的写法,直接就是面向对象化编程。而对于我们python支持多种编程方式:面向过程编程,函数式编程,面向对象编程。从而提高我们编程代码可调用性,可执行性,耦合性!!!
函数是你编程 vs 面向对象编程
def shop(name,app):
print(f'{name}用手机在{app}看中了一个娃娃!!!')
def car(name,lcoal):
print(f'{name}今天上午开车去{lcoal}野炊')
def like(name,people):
print(f'{name}心里喜欢{people}')
shop('alex','淘宝')
car('alex','郊区')
like('alex','wusir')
当我执行调用的时候,会输出三个不同的结果。我发现这些函数的功能都是关于人的生活习惯,我就可以通过面向对象的类他们三个归为一类,方便调用。
函数的语法的关键字是: def 类的语法是:class 关键字
class Foo: # class 是类的关键字,后面跟着类名 + : (def后面跟着是函数名)
pass
class People:
# 在类中,编写一个函数时,必须有一个默认的参数 self 且类中称为叫做方法,不叫函数了
def shop(self,name, app):
print(f'{name}用手机在{app}看中了一个娃娃!!!')
def car(self,name, lcoal):
print(f'{name}今天上午开车去{lcoal}野炊')
def like(self,name, people):
print(f'{name}心里喜欢{people}')
# 我们说过,面向对象是通过对象来调用类中功能
obj = People() # 通过 类() 来实例化这个类的对象,后面详见
# 通过 对象.方法() 先不管self时干什么,只要在调用方法时传入除了self以外参数必须传
obj.shop('alex','淘宝')
obj.car('alex','郊区')
obj.like('alex','wusir')
虽然的说,把三个函数整合到一个类中,这样方便我们进行分类调用方法,执行特定的功能。但是在现在我写的类,对于在调用方法的时候,还是过多的传入同样的参数 'alex',怎么办?
2.三大特性--封装
封装:函数可以封装一个功能,不如冒泡排序封装到一个函数内部,方便我们多次的调用冒泡排序的功能。
包,也是把多个的py文件,封装成一个项目,来方法去综合的调用各种py文件的功能。同样的类也可以封装数据:变量,方法。
如何的封装变量?
在python的基本的数据类型:list tuple dict set 都是封装数据的,其实它们本身就是一个个类,当我们学习了类,就可以定义自己的数据类型
在类中提供一个特殊的方法:__init__
方法,帮助我们在类的内部接受外部传来的一些类中公共的初始化变量
class People:
# 定义了一个初始化方法: __init__
def __init__(self ,name):
self.name = name # 固定的写法,后面封装详见
def shop(self, app):
print(f'{self.name}用手机在{app}看中了一个娃娃!!!')
def car(self, lcoal):
print(f'{self.name}今天上午开车去{lcoal}野炊')
def like(self, people):
print(f'{self.name}心里喜欢{people}')
# 在实例化对象的时候,类会自动的执行 __init__ 方法,把公共的参数传入
obj = People('alex')
obj.shop('淘宝') # 这样一来,以后调用类的方法,只需要的方法中需要的参数传入,公共的参数已经封装到了对象中
obj.car('郊区')
obj.like('wusir')
面向对象的编程思想是:在面向对象世界,一切都是对象。以上帝的角度去思考问题,事先的限定对象调用的权限,然后通过对象帮助我们执行功能。
总结:
- 在定义类的时候,需要 class 关键字 + 类名 + :
- 把一类的功能函数封装到同一个类中
- 在定义类中的方法,默认的需要一个 self 的参数,在调用的方法时,可以不给self传参(固定的写法)
- 类有一个特殊的方法:
__init__
方法,帮助在类中初始化公共值
3. 类中的成员
实例化对象是什么?
在python的基本的数据类型:list tuple dict set 都是封装数据的,其实它们本身就是一个个类,当我们学习了类,就可以定义自己的数据类型。在封装数据的前提是有一个自己类的对象
# 定义:list数据类型 = 实例化了列表的对象
lst = list()
# 定义:元组数据类型 = 实例化了元组的对象
toto = tuple()
# 定义我们自己的数据类型
class Foo:
def __init__(self,a,b,c): # 我们可以往自己的数据类型去封装一些变量
self.a = a
self.b = b
self.c = c
obj = Foo(1,2,3) # 实例化了Foo的对象
print(obj.a)
print(obj.b)
print(obj.c)
成员1. 对象变量 & 类变量
-
对象变量,又叫做实例化变量。是封装在对象的空间内。比如说小明的名字,年龄,都是对于自己特有的
实例变量只能通过对象使用
-
类变量:封装类中的变量。可以被类中方法使用,也可以被类的对象使用
# 比如开车:车的座位是固定的,马力是固定的
# 而驾驶的者是不确定的,今天是我,明天是你
class car:
# 类变量
seat = 4
power = '5.1T马力'
obj = car() # 实例化了car类的对象
obj.name = 'alex'
obj.age = 29
# 类变量可以被对象使用
print(obj.seat)
print(obj.power)
# 也可以被类调用
print(car.seat)
print(car.power)
# 重点:实例化变量,只能同对象使用。因为实例化存在于单独的一个对象内部
print(obj.name)
print(obj.age)
成员2. 方法
方法:把一类的函数功能封装到一个类,并且在类中函数u都有一个默认的参数self
class Foo:
'''
把ect 函数功能 和play 函数功能封装到了Foo类中
'''
def ect(self):
print('我要吃好吃的')
def play(self):
print('我要玩好玩的')
people = Foo()
people.ect()
people.play()
# obj.append(123)
'''AttributeError: 'People' object has no attribute 'append' 在People类中没有append方法'''
# 如果我在这类实例化对象调用了类中没有的方法,就会报错.
# 相当于你对字符串进行操作的时候,字符串本身的方法中就没有append方法一样
我们在类中写的函数就是方法. 也就是说, 以前我们写的叫函数. 现在它有了个新名字, 叫方法.
-
实例方法
类中定义的方法,通过实例化的对象去调用的方法
定义:需要一个self的参数
执行:只能通过对象+方法+()
class Foo: def show(self): print("我是show") obj = Foo() obj.show() # 实例化方法也可以通过类+方法()调用,但需要手动的传self Foo.show(obj) # 就是把对象传入给self
-
静态方法
静态方法 跟 实例方法的区别在于:静态方法不需要不需要固定参数 self
定义:需要一个 @staticmethod 装饰器
调用:类+静态方法+() / 对象+静态方法+()
class Foo: @staticmethod # 可以传参数 def static(): print('我是静态方法') obj = Foo() obj.static() Foo.static()
-
类方法
类方法 跟 实例方法的区别:类方法的固定参数是 cls ,会把当前类的内存地址传给cls
定义: 需要 @classmethod 装饰器
调用:通过类+类方法 + () / 在python中还是可以 对象+类方法+()
class Foo: @classmethod def inner(cls): # 可以传参数 print('我是类方法') obj = Foo() obj.inner() Foo.inner()
-
属性
实例方法 静态方法 类方法 都需要加()调用 ,而属性可以直接 对象.属性
定义:需要 @propety 装饰器
调用:只能通过对象+属性
class Foo: @property def pro(self): print('我是属性') obj = Foo() obj.pro
对于属性有三个装饰器 getter setter deleter 前提必须有属性方法,装饰器是对于属性方法的使用
class Foo: # 属性 @property def mod(self): print('我是属性') # getter setter deleter 装饰器需要要有属性方法,来对下面的方法进行装饰 @mod.getter # getter 和 @property 属性一样,是通过 对象.getter def getter(self): print('我是 @property.getter') @mod.setter # serter 是通过 对象.serter = a 是调用serter装饰的方法 def setter(self,a): print('我是 @property.setter',a) @mod.deleter # deleter 在 对象.deleter前加上del 会调用deleter装饰的方法 def deleter(self): print('我是 @property.delter') obj = Foo() obj.getter obj.setter = 10 del obj.deleter
成员3.私有变量和方法
类变量和实例变量的关系就是:类变量是共有的,而实例变量是该实例化对象的,就是 类是爹,爹() 制造了一个儿子,类中的所有的资源都可以的被本类的对象调用。 好比爹再有钱,但不能把所有东西都给儿子,比如:爹的年龄啊,他也有自己秘密,
私有内容禁止在本类外面被访问, 在python中所有的__
开头的内容都是私有的. 但不包括__init__
这种两头都是__
的.
对于私有变量/方法来说,只能在自己类中可以调用,但是不能类实例化的对象调用包括类的子类也不允许调用
class father:
# 私有变量:在变量前加双下划线 __
__age = 40
__name = '大帅比'
def __info(self):
print('我是私有的方法__info')
def show(self):
print('我是共有的方法show')
self.__info() #私有的方法/变量,只能在类的内部调用:通过self.方法/变量
print(self.__age,self.__name) # 这就self的作用.
# 到底为什么可以通过self调用私有的成员?self是什么?详见方法__new__
obj = father()
# 对象只能调用共有的成员(原则上)
obj.show()
# 可以强制调用
obj._father__info()
成员4.魔法方法(双下划线)
__init__
初始化方法:给对象的内部封装成员__new__
实例化方法 :实例化对象的时候会先调用new方法,再执行init方法
有人就问:我写的类并没有__new__
方法啊?
《有人就问:我写的类并没有__new__
方法啊?
答:在python3中默认的类都会继承objcet类,在objcet类就有new方法,给的类的对象进行实例化
为什么可以通过对象调用类中变量/ 方法?
代码从上到下的会先加载类名,指出在内存中有属于这个类的内存空间。在实例化对象的通过__new__
方法,给对象在内存空间开辟属于对象的空间,同时会把此类的对象和类建立联系,加载类的内部信息。对象.成员:会优先的从属于自己(对象)空间中找对应的成员,如果对象空间没有,就到对象类寻找此成员。如果类中也没有就会报错``AttributeError: 'People' object has no attribute 'append' 在People类中没有append方法'
self 到底是什么东西?
其实self就是我们实例化的对象,在实例化对象的时候,会自动执行new方法,并且new方法的返回值给了self
class foo:
def __init__(self, a, b):
# print('再执行我init方法')
self.a = a
self.b = b
def __new__(cls, *args, **kwargs):
'''
在类的实例化对象的,去调用objcet的__new__方法
我们这里主动的去调用objcet的__new__方法
cls 是什么? 在实例化的会主动的这个类的内存地址复制给cls
'''
# print('先执行我new方法') # 可以自己去试试
return object.__new__(cls)
def show(self):
print(self.a) # self.a = obj.a = 对象.a
print(self.b)
obj = foo(111,222)
print(obj.a)
print(obj.b)
obj.show()
# 我们也可以手动的传递self
obj1 = foo(111,222)
foo.show(obj1)
-
__del__
析构方法
在对象的生命周期结束时,__del__
会被主动的调用,__del__
定义的是当一个对象进行垃圾回收时候的行为。 -
__str__ / __ repr__
当打印该对象时,以字符串方式输出该对象的状态信息。
在类中是优先调用 str 方法,如果没有调用 repr方法
class foo(object):
def __str__(self):
return '我是__str__'
def __repr__(self): # __repr__ 和 __str__ 用法一样:在同一个类中,优先调用__str__
return '我是__repr__'
obj = foo()
print(obj)
v = obj
print(type(v)) # <class '__main__.foo'> 虽然看起来像字符串,但是显示的foo类
-
__getitem__ __setitem__ __delitem__
item 类型的方法,帮助我们像字典一样的执行字典的方法
__setitem__
实现了通过字典的key 和 value 封装到类中,它定义了你对属性进行赋值和修改操作时的行为。__getitem__
可以通过key查看value__delitem__
与__setattr__
很像,只是它定义的是你删除属性时的行为。
class foo(object):
def __setitem__(self, key, value): # obj[key] = value / obj.__setitem__(key,value) 执行 __setitem__
# key , value 可以是任意的数据类型
self.key= key
self.vaule = value
def __getitem__(self, item): # obj[item] / obj.__getitem__(item) 执行 __getitem__
if item == self.key and self.vaule != None:
return self.vaule
def __delitem__(self, key): # del obj[key] / obj.__delitem__(key) 执行 __delitem__
if key == self.key:
self.vaule = None
-
__enter__ __exit__
上下文管理的方法帮助我们现实一个打开文件的操作,with open() as f ,open() 本身就是一个类,我们效仿着在我们类中实现!
__neter__
需要 配合with 使用,方可自动的调用enter方法__exit__
在with语句结束的时候,会先执行exit方法。open类就是帮我们关闭文件操作
class foo(object):
def __init__(self, file, mode, encode):
self.file = file
self.mode = mode
self.encode = encode
def __enter__(self):
self.f = open(self.file,mode = self.mode,encoding=self.encode)
return self.f
def __exit__(self, exc_type, exc_val, exc_tb):
print('文件已关闭')
self.f.close()
with foo('a.txt','w','utf-8') as f:
f.write('我可以了,我又行了')
-
__iter__
对象迭代方法学会了类,我们就可以自定义数据类型,
__iter__
帮助我们定义一种可迭代数据类型
class foo(object):
def __init__(self,a,b,c):
self.a = [a,b,c]
def __iter__(self):
return iter(self.a)
obj = foo(1,2,3)
v = iter(obj)
print(v.__next__())
print(v.__next__())
print(v.__next__())
- 其他魔法方法
class foo(object):
def __init__(self, a, b): # 初始化方法
self.a = a
self.b = b
def __call__(self, *args, **kwargs): # obj() 对象加() = 类+()() 执行 __call__
print('__call__')
def __eq__(self, other): # obj == 另一个对象 执行 __eq__ 会把另一个对象 传给other
print(other)
def __len__(self): # len(obj) / obj.__len__() 执行 __len__ 并且 返回值必须是个整形
return self.a
def __hash__(self): # hash(obj) / obj.__hash__() 执行 __hash__ 并且 返回值必须是个整形
print('__hash__ 需要返回一个整数')
# return 1111
return self.a + self.b
4.三大特性--继承
4.1类的继承
继承,没错就是字面意思。儿子可以继承爹的财产,在类中子类可以继承父类的成员~~
面向对象在python2和python3的区别:
python2中有俩种类:
-
经典类,就是在我们之前写的类拿到Python2中就是经典类
-
新式类,在python2中只要继承了object类的类就是新式类,在python3中所有的类都是新式类,默认继承了object类
类的继承语法:在定义类名后+(),括号中写要继承的类
class foo(object): # foo类就继承了object类
def show(self):
pass
class base(foo): # base类继承foo类
pass
在类的继承中,base继承了foo类,相对来说:base类是foo的子类(基类),foo类就是base类的父类。子类中没有的成员可以到使用父类中方法、变量(共有的)
当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了
# 单继承
class foo(object):
__name = 'alex' # 私有成员只能在类中调用,对象不可以调用,子类和父类都不可以调用
def func(self):
print('我是foo.func')
class base(foo):
def show(self):
print('我是base.show')
# 如果子类base中有了func方法,有限调用自己类中的方法
def func(self):
print('我是base.show')
obj = base()
obj.show()
obj.func()
python中支持多继承:一个子类可以多个父类
# 多继承:实例1
class foo(object):
def func(self):
print('我是foo.func')
class mod:
def ineer(self):
print('我是mod.ineer')
# base类继承了俩个父类查找关系是:先找自己,在找foo,最后找mod
class base(foo,mod):
def show(self):
print('我是base.show')
# 实例2
class foo(object):
def func(self):
print('我是foo.func')
# mod类继承了foo类,相当于mod来说:mod是foo类的子类
class mod(foo):
def ineer(self):
print('我是mod.ineer')
# base类继承只是单纯继承了mod类,可以说:base是mod的子类
class base(mod):
def show(self):
print('我是base.show')
4.2 继承关系
对于单继承,可以按照子类继承的一层一层的查找父类的成员。简单的多继承:base继承了俩个父类,父类没有继承除了object类的其他类,就可以按照从左到右的查找可以的。
对于复杂的多继承,在python2 和python3 中又有区别:
在python2中,对于多继承的继承关系深度优先,说白了就是从一条走到头
在python3中,对于多继承的继承关系而是广度优先,就是子类继承顺序一层一层找
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则: (mor查找继承关系遵循c3算法)
1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类
class A(object):
pass
class B(A):
pass
class C(B):
pass
class D:
pass
class E(D):
pass
class F(B,E):
pass
class G(F):
pass
class Foo(C,G):
pass
print(Foo.mro()) # mro查看新式类的继承关系
# [<class '__main__.Foo'>, <class '__main__.C'>, <class '__main__.G'>, <class '__main__.F'>, <class '__main__.B'>, <class '__main__.A'>, <class '__main__.E'>, <class '__main__.D'>, <class 'object'>]
4.3 super方法
类的继承是优先使用自己本身的方法,如果本身有,就用不到父类的方法。
super方法功能:帮助我们去调用子类已有方法以外的父类方法,就可以使用super方法调用父类方法
super方法就是按照C3算法从继承关系寻找方法
class foo:
def show(self):
print('我是foo.show')
class base(foo):
def show(self):
print('我是base.show')
super().show() # 官方解释寻找超类
super(base,self).show() # 俩种写法
obj = base()
obj.show()
4.4 接口类 & 抽象类
接口类:
一:继承基类的方法,并且做出自己的改变或扩展(代码重用)
二:声明某个子类兼容于某基类,接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能
class interface(object):
def ineer(self):
raise NotImplementedError(self ,'这个类没有定义ineer方法')
class foo(interface):
pass
obj = foo()
obj.ineer()
抽象类
抽象类也是对派生类的约束的更严格,借助abc模块的 abstractmethod 的装饰器,只要在子类中没有实现抽象类被abs.abstractmethod的方法,就不允许子类实例化对象
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。
抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计
import abc
class abstarct(metaclass=abc.ABCMeta):
@abc.abstractmethod
def func(self):
pass
class foo(abstarct):
pass
# 只要父类中被@abc.abstarctmethod约束的方法,在子类中没有实现,就不允许子类实例化对象
obj = foo()
总结:
接口类和抽象类,是通过子类继承的语法,对子类进行约束子类的方法成员。
4.5 判断方法
- isinstance( _o , _c) 判断是不是这个类的对象
- issubclass( x , A_class) 判断一个类是不是另个类的子类
5 三大特性--多态
面向对象方法中一般是这样表述多态性:
向不同的对象发送同一条消息(!!!obj.func():是调用了obj的方法func,又称为向obj发送了一条消息func),不同的对象在接收时会产生不同的行为(即方法)。
也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。
比如:老师.下课铃响了(),学生.下课铃响了(),老师执行的是下班操作,学生执行的是放学操作,虽然二者消息一样,但是执行的效果不同
多态就是同一个对象,有多个形态,而在python世界,本身就支持多态
class foo:
def append(self):
pass
def func(arg):
aeg.append()
# 在调用func函数的参数只要有append方法就可以
obj = foo()
func(obj)
func([1,2,3]) # 列表也可以
在python中我们对于多态成为鸭子模型:鸭子模型 我们认为只要能呱呱叫的就是鸭子,对于 func 函数,只要参数对象有append方法就是鸭子
6 反射
这个性质展示了某对象是如何在运行期取得自身信息的。 根据字符串的形式操作对象的成员
-
hasattr( 对象,对象的成员) 以字符串 str 的形式判断对象是否有该成员
-
setattr( 对象,对象修改成员 ,值) 以字符串形式对对象成员修改
-
getattr( 对象 ,对象成员) 以字符串形式找到对象的成员并返回
-
delattr( 对象,对象成员) 以字符串形式删除对象的成员
class foo(object): def __init__(self,name,age): self.name = name self.age = age def func(self): print(self.name) obj = foo('alex',12) setattr(obj,'name','武大郎') print(getattr(obj,'age')) getattr(obj,'func')() # gerattr找到对象类方法可以执行 print(hasattr(obj, 'age'))
python中对于一切都是对象,比如 类实例化的对象,py文件实例化的一个类,一个py文件又属于一个包的对象
# fs.py
class mode(object):
def func(self):
print('mode.func')
# api.py
import fs
mode = getattr(fs,'mode')() # 通过 fs 找到 mode类 实例化
func = getattr(mode,'func') # mode 就是 fs中mode类的对象
func() # 执行对象方法
利用getattr 动态导入模块
import sys
import importlib # importlib中的import_module动态导入模块
dir = 'fs.mode.func'.split('.')
fs = importlib.import_module(dir[0])
if not hasattr(fs,dir[1]):
print('没有此内容')
sys.exit(0)
dir_mode = getattr(fs,dir[1])
obj = dir_mode()
if not hasattr(obj,'func'):
print('没有此内容')
sys.exit(0)
getattr(obj,'func')()
7 单例模式
在每次进行类的实例化对象的时候,都会调用 new方法给该对象在内存开辟空间,有些时候比如,造杯子模型:每次造杯子的时候先去库存看看有没有杯子了。没有就去造杯子,有的话就可以直接从库存拿。在实例化类的对象先去内存空间查找是否已经开辟好了空间。
模块导入的单例实例,模块在导入之前,会在内存中判断是否内存空间已经导入过了的步骤
# mode.py
print("mode")
# api.py
import mode
import mode # 只会打印一次mode
# 初级单例模式
class foo(object):
__instace = False
def __new__(cls, *args,**kwargs):
if not cls.__instace :
return cls
return object.__new__()
obj = foo()
obj1 = foo()
print(id(obj))
print(id(obj1))
8 报错处理
在写运行某一段代码时,有一行代码出错了,但是你还要接着运行你的代码,比如Stopiteration 这用到了报错处理
'''
报错处理语法
try:
可能报错某几行代码
except 报错名字:
except捕获try内代码的报错符合except的,执行except里的代码
1 单分支:针对try代码中某一个报错类型
2 双分支:
except NameError(报错类型1):
pass
except Stopitertion(报错类型2):
pass
双分支合并捕获:几个报错需要执行同一处理
except (Error1,Error2,Error3):
except Error1,Error2,Error3:
3 万能捕获: except Exception: (同 except :)
万能捕获是前面报错都不是,才执行except
注意:被一个except捕获,其他的except不执行
else:
当try内代码没有报错时,执行else
finally:
1 不论try代码是否报错,最后都会执行finally
2 即使在函数遇到retrun,会先执行finally,再retrun
应用:with open
'''
# 实例1 对迭代器处理
lst = [1,2,3,4]
lst_iter = lst.__iter__()
try:
print(lst_iter.__next__())
print(lst_iter.__next__())
# 用内置函数取值next()
print(next(lst_iter))
print(next(lst_iter))
print(next(lst_iter))
except StopIteration:
print('取完了')
# 实例2
try:
print(a)
except:
print('报错了')
# 实例3
try:
lst =[1,2,3,4]
lst.upper() # AttributeError: 'list' object has no attribute 'upper'
lst.append() # TypeError: append() takes exactly one argument (0 given)
print(v)
except (TyoeError,AttributeError):
print("报错了")
except :
print("我是万能")
# 这代码只会报一个错AttributeError,因为代码从上到下执行,报错就停止
# 实例4
def func(sep):
try:
sep.append(2)
return sep
finally:
print('error')
print(func([1]))
''' 先执行finally 再执行retrun
error
[1,2]
'''
自己写程序的时候,在和用户交换的阶段,如果用户恶意输入,但是又符合Python的语法,我们就可以自已主动抛出异常
raise 主动异常
raise语法让我们程序自动的报我们需要的错,并显示报错的提示,报错类型:Python内部 / 类名
try:
raise Exception("出错了")
except Exception as el:
print(el) # 出错了
自定义异常
在我们使用raise主动触发异常使用的 Exception 的类,所以我们也可以自定义异常类,需要继承 Exception 类
class MyError(Exception): # 继承Exception
def __init__(self,code):
self.code = code
try:
raise MyError("自己异常") # 在 raise 后类就在实例化对象的过程
except MyError as el: # 异常被MyError 捕获 实例化的对象 重命名 el
print(el)