• 约束和异常处理


    约束和异常处理

    一:类的约束

    约束的是对类的约束,通过对类的约束,来规范多个代码进行相同操作功能的统一。
    
    例:三个人分别写三个登录功能(普通登录、会员登录、管理员登录),会有不同的方法。
        class Normal:
            def login(self):
                pass
        class Member:
            def denglu(self):
                pass
        class Admin:
            def login(self):
                pass
        三个功能都可以使用,但不能进行统一使用,因为第二个的方法和其他两个方法不一样。
        为了避免这样的事情发生,我们就可以通过约束,在分配任务之前,把相应的功能程序统一。
        
        
    在python中有两种方法可以解决:
        1.提取父类,然后再父类中定义好方法,在这个方法中,抛一个异常就可以,这样所有的子类
          都必须重写这个方法,否则就会报错。
        2.使用元类来描述父类,在元类中给出一个抽象方法,这样子类就必须给出抽象方法的具体实现,从而起到约束的效果。
            
    方案一: 提取父类在父类中给出方法,在方法中不给出任何代码,直接抛出异常。
        class Base:
            def login(self):
                raise Exception('没有login方法()')
        class Normal:
            def login(self):
                pass
        class Member:
            def denglu(self):
                pass
        class Admin:
            def login(self):
                pass
    
        # 总入口
        def login(obj):
            print('准备验证...')
            obj.login()
            print('进入主页...')
        
        n = Normal()
        m = Member()
        a = Admin
        
        login(n)
        login(m)  # 报错 => AttributeError: 'Member' object has no attribute 'login'
        login(a)
    
    程序执行到login(m)时报错,原因现在访问的login()是父类中的方法,
    父类中的方法会抛出一个异常,执行不到login()就会报错,从而就对子类进行了相应的约束。 
    我们要抛出一个异常 Exception,它是所有异常的根。我们无法通过这个异常来判断除程序报错的原因,
    所以需要一个比较专业的错误信息来提示,NotlmplementError,他的含义:‘没实现的错误’,
    这样就可以知道是什么错了。
        
    
    方案二:写抽象类和抽象方法。抽象类只是大概其的归纳出一个类,但是没有具体的描述类。
            抽象方法是在抽象类的基础上去描述出一个方法,一样没有办法具体的描述这个抽象方法。
            因为无法具体的描述,也就无法真实的创建抽象类,如果创建对象就会报错。
            
            
    在python中编写一个抽象类比较麻烦,需要引入abc模块中的ABCMeta和adstractmethod这两个内容。
    
        from abc import ABCMeta,abstractmethod
        
        # 类中包含抽象方法,此时这个类就是抽象类。注: 抽象类可以有普通方法。
        class IGame(metaclass=ABCMeta):
            # 创建一个游戏,但是没有具体的描述,所以没办法知道规则    
            @abstractmethod
            def play(self):
                pass
            def turn_off(self):
                print('没法玩')
    
        class LOL(IGame):
            # 子类必须实现父类的抽象方法,把游戏具体定义到某个游戏,否则子类也一样是抽象类,不能具体实现。
            def play(self):
                print('LOL都会玩')
    
        lol = LOL()
        lol.play()        
        # 在这里我们用IGame对LOL进行了约束,也就是父类对子类进行了约束。
            
                
    接下来我们用方案二的方法进行方案一
        from abc import ABCMeta , abstractmethod
    
        class Base(metaclass=ABCMeta):
            @abstractmethod
            def login(self):
                pass
    
        class Normal(Base):
            def login(self):
                pass
        
        class Member(Base):
            def demglu(self):
                pass
        
        class Admin(Base):
            def lgoin(self):
                pass
    
    
        # 总入口
        def login(obj):
            print('准备验证...')
            obj.login()
            print('进入主页...')
    
        n = Normal()
        m = Member() # 报错 => Can't instantiate abstract class Member with abstract methods login
        a = Admin
        
        login(n)
        login(m) 
        login(a)
                   
                
    总结:约束,就是父类对子类进行约束,子类必须写下xxx方法,在python中约束的方式和方法有两种:
        1.使用抽象类和抽象方法,由于该方案来源于Java和C#,所以使用频率很少。
        2.使用人用抛出异常的方案,并且尽量抛出的是NotlmplementError,这样比较专业,而且错误比较明确。(推荐)
    

    二:异常处理

    异常是程序在运行过程中产生的错误。
    
    先制造一个错误,来看看什么是异常
        def func(a,b):
            return a/b
            
        ret = func(10,0)
        print(ret)
                    
    结果:Traceback (most recent call last):
          File "E:/每日练习及作业/练习.20.py", line 96, in <module>
            ret = func(10,0)
          File "E:/每日练习及作业/练习.20.py", line 95, in func
            return a/b
          ZeroDivisionError: division by zero   # 除法中除数不能0       
          
                                 
    如何处理:
        def func(a,b):
            return a/b
        try:
            ret = func(10,0)
            print(ret)
        except Exception as e:
            print('除数不能是0')  # 除数不能是0
                       
    try 和 except 是什么意思呢?可以尝试运行xxx代码,如果出现错误,就执行except后面的代码,在执行过程中如果代码痴线错误。
    系统会产生一个异常对象,然后这个异常会向外抛,然后被except拦截,并把接受到的异常赋值给e。这里的e就是异常对象,
    这里的Exception是所有一类的基类,异常的根。也就是说所有的错误都是Exception的子类对象,
    我们看到报错的中ZeroDivisionError,都是Exception的子类。Exception表示所有的错误,就像抽象对象太过笼统了。如果出现
    错误都会被认为是Exception,当程序出现多种错误的时候,不好分类单一的进行处理。最好是出现什么错,就用什么来处理,
    这样就比较合理了,所以在try..except语句中,还可以写更多的except。
        try:
            print('各种操作')
        except ZeroDivisionError as e:
            print('除数不能是0')
        except FileNotFoundError as e:
            print('文件不存在')
        except Exception as e:
            print('其他错误') 
        
    此时程序运行过程中,如果出现了ZeroDivisionError就会被第一个except捕捉到,如果出现了FileNotFoundError就会被第二个
    except捕获,如果两个异常都不是,那就会被最后的Exception捕获。
    
    完整的处理异常写法(语法):
         try:
            ...操作...
         except Exception as e :
            '''异常的父类,可以捕获所有的异常'''
         else:
            '''保护部抛出异常的代码,当try中无异常的之后执行'''
         finally:
            '''最后是要执行我'''   
            
                
     解读: 程序先执⾏操作, 然后如果出错了会走except中的代码. 如果不出错, 执⾏else中的代码. 不论处不出错.
     最后都要执⾏finally中的语句. ⼀般我们⽤try...except就够⽤了. 顶多加上finally. finally⼀般⽤来作为收尾⼯作.             
                
                
    上⾯是处理异常. 我们在执⾏代码的过程中如果出现了⼀些条件上的不对等. 根本不符合我的代码逻辑. 比如. 参数. 
    我要求你传递⼀个数字. 你非得传递⼀个字符串. 那对不起. 我没办法帮你处理. 那如何通知你呢? 两个⽅案.
         ⽅案⼀. 直接返回即可. 我不管你还不⾏么?
         ⽅案⼆. 抛出⼀个异常. 告诉你. 我不好惹. 乖乖的听话.
        第⼀种⽅案是我们之前写代码经常⽤到的⽅案. 但这种⽅案并不够好. ⽆法起到警⽰作⽤. 所
        以. 以后的代码中如果出现了类似的问题. 直接抛⼀个错误出去. 那怎么抛呢? 我们要⽤到raise关键字 
         
        例:                         
            def add(a, b):
                '''
                给我传递两个整数,用来计算和
                :param a: 
                :param b: 
                :return: 
                '''
                if not type(a) == int and not type(b) == int:
                    # 当程序运⾏到这句话的时候. 整个函数的调⽤会被中断. 并向外抛出⼀个异常.
                    raise Exception("不是整数, 朕不能帮你搞定这么复杂的运算.")
                return a + b
    
            # 如果调⽤⽅不处理异常. 那产⽣的错误将会继续向外抛. 最后就抛给了⽤户
            # add("你好", "我叫赛利亚")
            
            # 如果调⽤⽅处理了异常. 那么错误就不会丢给⽤户. 程序也能正常进⾏
            try:
                add("胡辣汤", "滋滋冒油的⼤腰⼦")
            except Exception as e:
                print("报错了. ⾃⼰处理去吧")
                
        当程序运⾏到raise. 程序会被中断. 并实例化后⾯的异常对象. 抛给调⽤⽅. 如果调⽤⽅不处理. 则会把错误继续向上抛出.
        最终抛给⽤户. 如果调⽤⽅处理了异常. 那程序可以正常的进⾏执⾏.        
                
                
    自定义异常:如果你的类继承了Exception类,那你的类就是一个异常类。
        例:一个男澡堂子,来了一个女的,该怎么办?
                        
            # 继承Exception,那这个类就是异常类
            class GenderError(Exception):
                pass
            class Person:
                def __init__(self ,name,gender):
                    self.name = name
                    self.gender = gender
            
            def zao_tang_zi(person):
                if person.gender != '男':
                    raise GenderError('对不起,这里是男人的天堂')
            
            p1 = perseon('alex','男')
            p2 = perseon('容嬷嬷','女')
            
            # nan_zao_tang_xi_zao(p1)
            # nan_zao_tang_xi_zao(p2) # 报错. 会抛出⼀个异常: GenderError
            
            # 处理异常
            try:
                zao_tang_zi(p1)
                zao_tang_zi(p2)
            except GenderError as e:
                print(e)  # 性别不对,这里是男人的天堂
            except Exception as e:
                print('"反正报错了')           
                
    如果真的报错了,看清错误源在哪!现在需要引入另一个模块tracebake,这个模块可以帮我们获取到每个方法的调用信息,
    又被称为堆栈信息,这个信息对我们拍错很有帮助。
       
       import traceback
        # 继承Exception,那这个类就是异常类
        class GenderError(Exception):
            pass
        class Person:
            def __init__(self ,name,gender):
                self.name = name
                self.gender = gender
        
        def zao_tang_zi(person):
            if person.gender != '男':
                raise GenderError('对不起,这里是男人的天堂')
        
        p1 = Person('alex','男')
        p2 = Person('容嬷嬷','女')
        
        # nan_zao_tang_xi_zao(p1)
        # nan_zao_tang_xi_zao(p2) # 报错. 会抛出⼀个异常: GenderError
        
        # 处理异常
        try:
            zao_tang_zi(p1)
            zao_tang_zi(p2)
        except GenderError as e:
        
            val = traceback.format_exc()  # 获取到堆栈信息
            print(e)  # 性别不对,这里是男人的天堂
            print(val)         
                 
        结果:对不起,这里是男人的天堂
            Traceback (most recent call last):
              File "E:/每日练习及作业/练习.20.py", line 166, in <module>
                zao_tang_zi(p2)
              File "E:/每日练习及作业/练习.20.py", line 155, in zao_tang_zi
                raise GenderError('对不起,这里是男人的天堂')
            GenderError: 对不起,这里是男人的天堂       
                
     这样我们就能收放自如了. 当测试代码的时候把堆栈信息打印出来. 但是当到了线上的⽣产环境的时候把这个堆栈去掉即可.             
    

    三:MD5加密

    想一个事情. 你在银行取钱或者办卡的时候. 我们都要输入密码. 那这个密码如果就按照
    我们输入的那样去存储. 是不是很不安全啊. 如果某一个程序员进入到了银⾏的数据库. 而银
    行的数据库⼜存的都是明⽂(不加密的密码)密码. 这时, 整个银⾏的账户⾥的信息都是非常非
    常不安全的. 那怎么办才安全呢? 给密码加密. 并且是***不可逆的***加密算法. 这样. 即使获取到了
    银行的账户和密码信息. 对于黑客而言都无法进⾏破解. 那我们的账号就相对安全了很多. 那
    怎么加密呢? 最常用的就是⽤MD5算法.
     MD5是一种不可逆的加密算法. 它是可靠的. 并且安全的. 在python中我们不需要⼿写
    这⼀套算法. 只需要引入一个叫hashlib的模块就能搞定MD5的加密⼯作                    
        
     例:
        import hashlib
    
        obj = hashlib.md5()
        obj.update('容嬷嬷'.encode('utf-8'))  # 加密必须是字节
        miwen = obj.hexdigest()
        print(miwen)   # 093ee3f85a0e4b5732d68c177269e59f            
                
     注意. 这里有⼀个叫撞库的问题. 就是. 由于MD5的原
        始算法已经存在很久了. 那就有一些⼈用一些简单的排列组合来计算MD5. 然后当出现相同
        的MD5密⽂的时候就很容易反推出原来的数据是什么. 所以并不是MD5可逆, 而是有些别有
        用心的⼈把MD5的常数据已经算完并保留起来了.
        那如何应对呢? 加盐就行了. 在使用MD5的时候. 给函数的参数传递一个byte即可.           
       
     例:
        import hashlib
    
        obj = hashlib.md5(b'inbbuybhbghvvtcbhjbxecghhghggvg2154ijuferfjfuaejrng')  # 加盐,随意加,假的越多越安全
        obj.update('容嬷嬷'.encode('utf-8'))  # 加密必须是字节
        miwen = obj.hexdigest()
        print(miwen)   # e4d17c1d7e2c4744abce5cedc23114f0             
           
                
     例:MD5如何应用          
        import hashlib
    
        def my_md5(s):
            obj = hashlib.md5(b'okmnjuigjtuhfbr5248fogutj')
            obj.update(s.encode('utf-8'))   # 加密必须是字节
            miwen = obj.hexdigest()
            return miwen
        
        # alex: 99fca4b872fa901aac30c3e952ca786d
        username = input("请输⼊用户名:")
        password = input("请输⼊密码:")
        # 数据存储的时候.
        # username: my_md5(password)
        # 假设现在的用户名和密码分别是
        # wusir: 99fca4b872fa901aac30c3e952ca786d ==> wusir: alex
        
        
        # 用户登录
        if username == "wusir" and my_md5(password) =="99fca4b872fa901aac30c3e952ca786d":
            print("成功")
        else:
            print("失败"   
                
     所以. 以后存密码就不要存明⽂了. 要存密⽂. 安全, 并且. 这里加的盐不能改来改去的.否则, 整套密码就都乱了.           
    

    四:日志

    首先, 你要知道在编写任何一款软件的时候, 都会出现各种各样的问题或者bug. 这些问
    题或者bug一般都会在测试的时候给处理掉. 但是多多少少的都会出现一些意想不到的异常
    或者错误. 那这个时候, 我们是不知道哪里出了问题的. 因为很多BUG都不是必现的bug. 如果
    是必现的. 测试的时候肯定能测出来. 最头疼的就是这种不必现的bug. 我这跑没问题. 客户那
    ⼀用就出问题. 那怎么办呢?我们需要给软件准备一套日志系统. 当出现任何错误的时候. 我
    们都可以去日志系统里去查. 看哪里出了问题. 这样在解决问题和bug的时候就多了一个帮手.
    那如何在python中创建这个日志系统呢? 很简单. 
        1. 导入logging模块.
        2. 简单配置一下logging
        3. 出现异常的时候(except). 向日志里写错误信息.                 
    例:           
        # filename: 文件名
        # format: 数据的格式化输出. 最终在日志⽂件中的样⼦
        # 时间-名称-级别-模块: 错误信息
        # datefmt: 时间的格式
        # level: 错误的级别权重, 当错误的级别权重⼤于等于leval的时候才会写入文件
        logging.basicConfig(filename='x1.txt',
                            format='%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s',
                            datefmt='%Y-%m-%d %H:%M:%S',
                            level=0)    # level 设置级别. 当你的信息的级别>=level的时候才会写入日志文件, 默认30
        # CRITICAL = 50
        # FATAL = CRITICAL
        # ERROR = 40
        # WARNING = 30
        # WARN = WARNING
        # INFO = 20
        # DEBUG = 10
        # NOTSET = 0
        logging.critical("我是critical") # 50分. 最贵的
        logging.error("我是error") # 40分
        logging.warning("我是警告") # 警告 30
        logging.info("我是基本信息") # 20
        logging.debug("我是调试") # 10
        logging.log(2, "我是⾃定义") # 自定义. 看着给分           
                
                
    简单的做个测试,应用一下
        class JackError(Exception):
            pass
        for i in range(10):
            try:
                if i % 3 == 1:
                    raise FileNotFoundError('文件不存在')
                elif i % 3 == 2:
                    raise KeyError('键错了')
            except FileNotFoundError:
                val = traceback.format_exc()
                logging.error(val)
            except KeyError:
                val = traceback.format_exc()
                logging.error(val)
            except JackError:
                val = traceback.format_exc()
                logging.error(val)
            except Exception:
                val = traceback.format_exc()
                logging.error(val)            
                        
    最后, 如果你在系统中想要把⽇志⽂件分开. 比如. 一个大项目, 有两个⼦系统, 那两个⼦系
    统要分开记录⽇志. 方便调试. 那怎么办呢? 注意. ⽤上面的basicConfig是搞不定的. 我们要
    借助⽂件助⼿(FileHandler), 来帮我们完成日志的分开记录        
             
         import logging
    
        # 创建一个操作日志的对象logger(依赖FinleHandler)
        file_handler = logging.FileHandler('l1.log', 'a', encoding='utf-8')
        file_handler.setFormatter(logging.Formatter(fmt="%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s"))
        logger1 = logging.Logger('s1', level=logging.ERROR)
        logger1.addHandler(file_handler)
        logger1.error('我是A系统')
        
        # 再创建⼀个操作⽇志的对象logger(依赖FileHandler)
        file_handler2 = logging.FileHandler('l2.log', 'a', encoding='utf-8')
        file_handler2.setFormatter(logging.Formatter(fmt="%(asctime)s - %(name)s -%(levelname)s -%(module)s: %(message)s"))
        logger2 = logging.Logger('s2', level=logging.ERROR)
        logger2.addHandler(file_handler2)
        logger2.error('我是B系统')
  • 相关阅读:
    Tree
    a letter and a number
    A problem is easy
    connect设置超时的方法
    C++客户端访问Java服务端发布的SOAP模式的WebService接口
    gSoap的“error LNK2001: 无法解析的外部符号 _namespaces”解决方法
    先序序列和后序序列并不能唯一确定二叉树
    二叉树的非递归遍历
    web service,soap ,http,tcp,udp
    byte[]数组和int之间的转换
  • 原文地址:https://www.cnblogs.com/zhao-peng-/p/9795884.html
Copyright © 2020-2023  润新知