zoukankan      html  css  js  c++  java
  • day24 内置方法,异常机制

    day24 内置方法,异常机制

    今日内容

    1. 内置方法(魔法方法)
    2. 异常处理

    昨日回顾

    1. 约束
      1. 在父类建立一种约束
      2. 抽象类
        • from abc import ABCMeta, abstractmethod
        • abc:abstract base class,抽象基类
        • ABCMeta:抽象类的元类,用来构造类
        • abstractmethod:装饰器,用来将普通的实例方法转换为抽象方法
        • 如果子类中没有重写被abstractmethod装饰过的方法,会报错
    2. 类方法
      1. 用classmethod修饰
      2. 第一个参数为类对象,一般用cls表示
      3. 实例对象和类对象都可以调用类方法,但一般用类对象调用
      4. 类方法是酱类本身作为对象进行操作的方法
    3. 静态方法
      1. 使用staticmethod修饰的方法
      2. 没有class和cls参数,不能调用类或实例的属性和方法
      3. 类和实例方法都可以调用静态方法
      4. 静态方法是独立的单纯的函数,仅仅托管于类的名称空间中
    4. property
      1. property是一种特殊的属性,访问它时会执行一段功能,然后返回值
      2. property是装饰器,装时后的方法使用起来与属性类似,不需要加括号
      3. 只有@property是只读,加上@setter定义可读可写,加上@deleter定义可读可写可删除
      4. 属性一般有三种访问方式:获取、修改、删除
      5. property(获取方法,修改方法,删除方法)
    5. 反射(自省)
      1. 通过字符串的形式操作对象中的属性
      2. 四个实现字形的函数:hasattr,getattr,setattr,delattr
      3. 反射可以应用于对象、类和模块

    今日内容详细

    内置方法(魔法方法)

    魔法方法是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)是一个事件,该事件可能会在程序执行过程中发生,影响程序正常执行。

    通常情况下,出现异常的原因有两种:

    1. raise语句抛出的异常
    2. Python解释器自己检测到异常并引发

    在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('其他所有情况,都会执行这里的代码')
    

    这确实实现了我们预期的功能,但是却也存在很多问题:

    1. 使用if的方式,我们职位第一段代码加上了异常处理,但这些if,跟代码逻辑毫无关联,会严重降低代码的可读性
    2. 这只是我们代码中的一个小逻辑。如果类似地逻辑还有很多,每一次都要判断这些内容,会让我们的代码特别冗长
    3. if判断的异常处理方法只能针对某一段代码,对于不同的代码段的相同类型的错误,需要写重复的if来进行处理

    这就需要使用Python中自带的异常处理方式,其基本结构为:

    try:
        被检测的代码块
    except 异常类型:
        被检测的代码块出现异常后,会执行这里的代码
    
    

    如果你不想子啊异常发生时结束你的程序,只需在try里捕获它

    其基本流程为:

    • 首先,执行try子句(在关键字try和except之间的部分)
    • 如果没有异常发生,except子句将不会被执行
    • 如果在try自居执行的过程中出现了异常,那么改子句中出错部分之后的代码将不会被执行
    • 如果出现的异常与except关键字后面指定的异常类型相同,就会执行对应的except子句,然后继续执行后续代码
    • 如果出现的异常与except关键字后面指定的代码不同,将会抛出错误,程序终止

    例如,我们可以这样处理除数不能为零的错误:

    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: 请输入数字。
    你输入的是:%s' % hi.n)
    else:
        print('未发生异常')
        
    输入的值为a,输出的内容为:
    请输入数字:a
    HelloError: 请输入数字。
    你输入的是:a
    
    
  • 相关阅读:
    js图片滑动展示
    那些好像失败了却很有趣的奇怪产物——傅里叶变换图片篇
    啊,满足了我对javaBean的所有幻想,记录一个神器:Lombok!
    十几行代码将mock生成的json数据转为sql的insert语句
    python之三目运算符的替代品?
    【python爬虫】每天统计一遍up主粉丝数!
    大项目之网上书城(十二)——完成啦
    大项目之网上书城(十一)——前台完成
    大项目之网上书城(十)——自动登录
    vs2019 创建vue项目
  • 原文地址:https://www.cnblogs.com/shuoliuchina/p/11678759.html
Copyright © 2011-2022 走看看