from abc import ABCMeta, abstractmethod
property(获取方法,修改方法,删除方法)
魔法方法是Python的对象天生拥有的一些神奇的方法,它们总被一对双下划线所包围。
这些方法会在特殊情况被Python所调用,可以通过重写这些方法定义自己想要的行为。而且这一切都是自动发生的。
不过除非你知道自己在干什么,明确自己这样做的目的,否则不建议修改这些方法。因为这些方法本身已经写好了一些规则,如果随意修改可能会引发一些麻烦。
__new__(cls[, ...])
创建对象__new__()
方法是一个对象实例化的时候调用的第一个方法,用来创建一个类对象。__new__()
方法中的参数是类而非对象,因为调用它的时候,对象还没有被创建出来。
__new__()
方法的返回值需要是一个类对象。事实上,返回值可以为任意,但是将无法实例化对象。
其基本用法如下:
class Person:
def __new__(cls, *args, **kwargs):
print('我是用来创建对象的方法')
return object.__new__(cls)
xiaoming = Person()
输出结果为:
我是用来创建对象的方法
还是要强调一次,除非你知道自己在做什么并且对自己的行为非常自信,否则不要轻易重写__new__()
方法。
__init__(self[, ...])
构造器__init__()
方法是对象实例化的时候,继__new__()
方法之后第二个被调用的方法,被称作初始化方法、构造方法或构造器。它的作用是当一个实例被创建时进行初始化,可以传入参数,设置实例属性等。
其用法如下:
class Person:
def __init__(self):
self.name = '小明'
print('用来初始化对象,被称为构造方法')
xiaoming = Person()
print(xiaoming.name)
输出的结果为:
用来初始化对象,被称为构造方法
小明
__del__(self)
析构器__del__()
方法会在实例被销毁的时候被调用执行,也被称作析构方法或析构器。
这个方法不建议被修改。有的时候如果修改了__del__()
方法可能会和Python中的内存回收机制冲突,从而产生属性删除不干净的情况。
其基本用法为:
class Person:
def __del__(self):
print('析构器,也叫析构方法,在对象被删除时,自动调用')
xiaoming = Person()
print('我没有删除对象呀~')
输出的结果为:
我没有删除对象呀~
析构器,也叫析构方法,在对象被删除时,自动调用
我们并没有使用del方法删除实例对象,但是__del__()
似乎也被执行了。这是因为当程序运行结束时,内存回收机制会自动删除对象,这时,__del__()
方法中的代码也会自动执行
如果我们手动删除对象,__del__()
方法就会在我们删除的时候自动执行了:
class Person:
def __del__(self):
print('析构器,也叫析构方法,在对象被删除时,自动调用')
xiaoming = Person()
del xiaoming
print('这次我把对象删除啦~')
输出的结果为:
析构器,也叫析构方法,在对象被删除时,自动调用
这次我把对象删除啦~
虽然我们没有在__del__()
写入任何与删除对象有关的代码,Python解释器仍然会把对象删除掉。如果再次调用对象时,会因找不到对象而报错。程序结束时也不会再次运行__del__()
中的代码,因为对象已经被删除。
__len__(self)
获取长度当使用len(obj)
函数调用的时候会自动执行__len__(self)
方法中的代码。其返回值必须是int类型,且不能为负:
class Person:
def __len__(self):
return 666
xiaoming = Person()
print(len(xiaoming))
输出的结果为:
666
__hash(self)__
消息摘要算法__hash__()
方法通过hash(obj)
方法调用。hash
是一种消息摘要算法,用于获取一个对象(非可变数据类型)的哈希值。对于相同的对象来说,哈希值是一样的,不同的对象哈希值则不同。哈希值可以用来判断对象是否有被篡改。
同样,不建议修改__hash__()
方法
__str__(self)
打印方法__str__()
方法会在使用print打印对象的时候被调用。我们可以自定义打印出来的内容:
class A:
pass
class B:
def __str__(self):
return '这时B对象'
a = A()
b = B()
print(a)
print(b)
输出的结果为:
<__main__.A object at 0x0000026D9B52B6D8>
这时B对象
__eq__(self, ogj)
比较当我们使用==
进行比较运算时,会调用==
左边的对象的__eq__()
方法,==
右边的对象会作为参数传入。两个对象比较之后将结果返回:
class A:
def __init__(self):
self.name = 'xiaoming'
self.age = 10
def __eq__(self, obj):
if self.name == obj.name and self.age == obj.age:
return True
else:
return False
class B:
def __init__(self):
self.name = 'xiaoming'
self.age = 10
a = A()
b = B()
print(a == b)
返回的结果为:
True
在上面的例子中,B类并没有写__eq__
方法,因为只会调用等号左边对象的__eq__
方法,等号右边的对象没有也无所谓。
异常(Exception)是一个事件,该事件可能会在程序执行过程中发生,影响程序正常执行。
通常情况下,出现异常的原因有两种:
在Python无法正常处理程序时,就会发生异常。
异常时Python对象,表示一个错误,一般继承自类Exception
异常和错误是有区别的。异常是我们可以预测,可以通过一些操作来避免的麻烦。而错误往往是无法预料和避免的问题。
当Python程序发生异常时,我们需要对它即使捕获和处理,否则程序会终止运行。
用户没有程序异常的概念,只有程序能用和不能用。在用户界面,不能有任何能造成程序终止的异常出现。
SyntaxError
语法错误IndentationError
缩进错误(空格数目不正确)NameError
使用一个尚为被赋予对象的遍历TypeError
传入对象类型与要求的不符合(int + str)IOError
输入/输出异常,基本上是无法打开文件(FileNotFoundError
也属于 IOError
)ImportError
无法引入模块或包,基本上是路径问题或名称错误IndexError
下标索引超出序列边界KeyError
试图访问字典里不存在的键程序员编写特定的代码,专门用来捕捉这个异常(这段代码与程序逻辑无关,只是为了处理异常)。如果捕捉成功,则会进入另外一个处理异常的分支,执行为指定异常定制的逻辑。这样即便出现了异常,程序也不会崩溃。
其实我们从前已经进行过异常处理的操作了。当时我们使用if
语句来实现的:
num = input('>>>') # 让用户输入
if num.isdecimal(): # 这是我们想要的结果,其他的条件都是在进行异常处理
print(int(num) + 10)
elif num.isspace():
print('如果输入的是空格,就执行这里的代码')
elif len(num) == 0:
print('如果输入内容为空,就执行这里的代码')
else:
print('其他所有情况,都会执行这里的代码')
这确实实现了我们预期的功能,但是却也存在很多问题:
这就需要使用Python中自带的异常处理方式,其基本结构为:
try:
被检测的代码块
except 异常类型:
被检测的代码块出现异常后,会执行这里的代码
如果你不想子啊异常发生时结束你的程序,只需在try里捕获它
其基本流程为:
例如,我们可以这样处理除数不能为零的错误:
try:
print(1/0)
# 注意:如果写明异常类型,异常类型要与上面发生的异常匹配,否则捕捉不到
except ZeroDivisionError:
print('0不能作为除数')
print('后续语句')
输出的结果为:
0不能作为除数
后续语句
except可以指定捕获的类型,捕获多种异常。
具体的操作方式是,使用多个except。需要注意的是,每个except后面仍然只能有匹配一个异常。
如果没有任何一个except能够捕获到异常,异常将会向外抛出,使程序终止。
我们可以这样处理多异常:
try:
print(1/0)
raise FileNotFoundError
except ZeroDivisionError:
print('0不能作为除数')
except FileNotFoundError:
print('异常2')
print('后续语句')
输出结果为:
0不能作为除数
后续语句
如果try的子句中有多处错误,只会引发第一个错误的异常,且子句中发生异常后面的代码将不会被执行。
except ... as ...
使用except...as...
语句可以查看异常,以及错误是否按要求被捕获到:
try:
print(1/0)
raise FileNotFoundError
except Exception as e: # 万能异常,稍后会有讨论
print(e)
输出的结果为:
division by zero
如果想要的效果是,无论出现什么异常,我们都要捕获,统一处理,我们就可以使用一个Exception来代指任意错误(事实上,Exception还不能代指Python中所有异常,不过对于我们来说,还写不出Exception指代不了的异常)。
万能异常往往可以和多分枝结合使用,这样就可以对不同的一场定制不同的处理逻辑。同时,也不会有任何错误被忽略而导致程序终止:
# 多分枝 + 万能异常
# 发生的异常中,有一些异常是需要不同的逻辑处理的,剩下的同意处理掉即可
dic = {
'1': 'a', '2': 'b', '3': 'c'}
try:
choice = int(input('请输入序号:'))
print(dic[choice])
except ValueError:
print('请输入数字...')
except KeyError:
print('你输入的选项超出范围')
except Exception as e:
print(f'遇到不明错误,错误原因:{e}')
try...except...else...
组合try代码中,只要出现了异常,就不会执行else语句;如果不出现异常,则会执行else中的语句。例如:
try:
a = 1
b = int(input('请输入数字:'))
print(a/b)
except ZeroDivisionError:
print('除数不能为0')
except ValueError:
print('只能输入数字!')
else:
print('程序运行正常,没有出错!')
当输入的数字为0时,输出的内容为:
请输入数字:0
除数不能为0
当输入的数字为2时,输出的内容为:
请输入数字:2
0.5
程序运行正常,没有出错!
try...except...finally...
组合不管try中的语句是否发生错误,也不管出现的错误是否被except捕获到,finally中的语句一定会被执行。
如果try中没有发生错误,程序顺利执行,如果有else先执行else中的子句,然后执行finally中的子句。
如果出现了错误,而且成功被捕获到,先执行except子句中的代码,然后执行finally中的代码。
如果错误没有被捕获到,在报错之前,会执行finally中的代码。
其示例如下:
a = input('请输入数字:')
try:
print(1/int(a))
except ValueError:
print('错误成功被捕获!')
finally:
print('无论如何都会打印的内容~')
用户输入1,程序正常运行,输出的结果为:
请输入数字:1
1.0
无论如何都会打印的内容~
用户输入a,程序异常被成功捕获,不会报错,输出的结果为:
请输入数字:a
错误成功被捕获!
无论如何都会打印的内容~
用户输入0,程序异常不能被捕获,程序报错,报错前finally中的代码仍然被执行,输出的结果为:
请输入数字:0
无论如何都会打印的内容~
Traceback (most recent call last):
File "C:/Users/Sure/PyProject/week07/day24/exercise.py", line 122, in <module>
print(1/int(a))
ZeroDivisionError: division by zero
finally的应用场景之一是,当我们进行文件操作时,使用finally关闭文件,确保文件操作内容被保存到硬盘中:
f = open('test', 'a', encoding='utf-8')
try:
"""各种操作"""
print(f.read())
"""期间发生了错误,如果不关闭文件,文件中的数据将因为未保存而丢失"""
finally:
f.close()
在函数中,finally会在return之前被执行:
def func():
try:
return 1
finally:
print('finally')
print(func())
输出的结果为:
finally
1
在循环中,finally还会在break之前被执行:
while True:
try:
print('还没有结束~')
break
finally:
print('finally')
输出的结果为:
还没有结束~
finally
总结起来就是,finally用来做一些收尾的工作。在一些重要操作环节之前,为避免出错而造成重要数据的损失,有必要做一些比如关闭链接之类的处理。这时候,就可以用finally作为最后一道防线来收尾。
有的时候,我们需要自己在代码中触发一些异常。
主动抛出异常的语法结构为:
raise 错误类型('错误描述')
我们在类的约束中,已经用到了这个方法。我们要求子类重写父类的方法,如果子类没有重写,则会报错:
class Girl:
def play(self):
raise Exception('子类必须重写play方法')
class Nurse(Girl):
pass
xiaoli = Nurse()
xiaoli.play()
程序运行报错,报错信息为:
Traceback (most recent call last):
File "C:/Users/Sure/PyProject/week07/day24/exercise.py", line 148, in <module>
xiaoli.play()
File "C:/Users/Sure/PyProject/week07/day24/exercise.py", line 144, in play
raise Exception('子类必须重写play方法')
Exception: 子类必须重写play方法
断言也是一种主动抛出异常的语句。
断言表示一种强硬态度,只要assert后面的代码不成立,直接报错,下面的代码将不会被执行。
断言的基本结构为:
assert 条件
例如:
name = 'xiaoming'
print(1)
print(1)
assert name == 'lalala'
print(1)
print(1)
print(1)
当输出两个1之后,程序报错,输出的内容为:
1
1
Traceback (most recent call last):
File "C:/Users/Sure/PyProject/week07/day24/exercise.py", line 154, in <module>
assert name == 'lalala'
AssertionError
我们之前提到过,Python中提供的错误类型可能并不能解决所有错误。只不过以目前来说,我们或许还不能发现那些异常。
如果以后在工作中,出现了某种无法用一直错误异常捕获到的异常(万能异常只能捕获Python中存在的异常),那么就可以尝试自定义异常。新定义的异常也需要继承自Exception类:
class HelloError(Exception):
def __init__(self, n):
self.n = n
try:
n = input('请输入数字:')
if not n.isdecimal():
raise HelloError(n)
except HelloError as hi:
print('HelloError: 请输入数字。\n你输入的是:%s' % hi.n)
else:
print('未发生异常')
输入的值为a,输出的内容为:
请输入数字:a
HelloError: 请输入数字。
你输入的是:a