约束和异常处理
本节主要内容:
1. 类的约束
2. 异常处理理
3. 自定义异常
4. MD5加密
5. 日志
一、类的约束
首先,要清楚。约束是对类的约束。每个人和每个人写代码用的方法名和类名都不一样假如你处理一个用户登录,有三个不同级别的登陆要求分别分给三个人写你要知道程序员不一定有那么好的默契。很有可能三个人会写不同的方法,比如就像是这样:
class Normal: # 张三, 普通⼈登录 def login(self): pass class Member: # 李四, 会员登录 def denglu(self): pass class Admin: # 王五, 管理员登录 def login(self): pass
写出了这样的代码,李四写的方法名什么鬼???denglu拼音看着就难受。但是功能实现了,但是你这边要测试了。问题来了
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)
login(a)
你会发现除了李四的代码其他的都可以调用。如何避免这样的问题呢?我们要约束下程序的结构。在分配任务之前就应该把功能定义好。然后分别交给底下的程序员来完成相应的功能。
在python中有两种约束的方法来解决这样的问题
1.提取父类。然后再父类中定义好方法。在这个方法中什么都不用做,抛出异常就可以了。所有的子类必须重写这个类否则就会报错抛异常。
class Base: def login(self): raise NotImplementedError("需要重写该方法") class Normal(Base): def login(self): print("普通人登陆") class Member(Base): def login(self): print("会员登陆") class Admin(Base): def denglu(self): print("管理员登陆") #整合所有的方法 def test(obj): obj.login() n = Normal() m = Member() a = Admin() test(n) test(m) #报错 test(a)
在执⾏到login(m)的时候程序会报错. 原因是, 此时访问的login()是⽗类中的⽅法. 但是⽗
类中的⽅法会抛出⼀个异常. 所以报错. 这样程序员就不得不写login⽅法了. 从⽽对⼦类进⾏
了相应的约束.
在本⽰例中. 要注意. 我们抛出的是Exception异常. ⽽Exception是所有异常的根. 我们⽆法通
过这个异常来判断出程序是因为什么报的错. 所以. 最好是换⼀个比较专业的错误信息. 最好
是换成NotImplementError. 其含义是. "没有实现的错误". 这样程序员或者项⽬经理可以⼀⽬
了然的知道是什么错了. 就好比. 你犯错了. 我就告诉你犯错了. 你也不知道哪⾥错了. 这时我
告诉你, 你xxx错了. 你改也好改不是?
2.使用元类来描述父类。在元类中给出一个抽象方法。这样子类就不得不给出抽象方法的具体实现 也就是重写。这样也可以去起到约束的效果。
抽象类是不能实例的,也就无法建立实例对象与之相应。所以抽象类无法创建对象。创建对象的时候会报错。
在python中编写一个抽象类比较麻烦。需要引入abc模块中的ABCMeta和abstractmethod这两个内容。
这里有一个例子:
from abc import ABCMeta, abstractmethod class Animal(metaclass=ABCMeta): @abstractmethod def chi(self):pass #抽象方法 def dong(self): #抽象类中也可以有实例方法 print("会动") class Cat(Animal): def chi(self): #给抽象方法的具体实现调用 print("猫吃鱼") cat = Cat() cat.chi() cat.dong()
通过代码我们能发现。这里的Animal对Cat进行了约束,换句话说。父类对子类进行了约束。
继续解决登陆的问题:
from abc import ABCMeta,abstractmethod #需要先导包 class Base(metaclass=ABCMeta): #定义抽象类 @abstractmethod def login(self):pass #定义抽象方法 class Normal(Base): def login(self): #给抽象方法具体实现 print("普通用户") class Member(Base): def login(self): #给抽象方法具体实现 print("会员用户") class Admin(Base): def denglu(self): #报错,这里用的是denglu并非login print("管理员用户") def test(obj): obj.login() n = Normal() test(n) m = Member() test(m) a = Admin() #报错,这里用的是denglu并非login test(a)
总结:约束,其实就是父类对子类的约束。子类必须要写xxx方法。在python中约束的方式和方法有两种:
1.使用抽象类和抽象方法,由于该方案来源是java和c#。所以使用频率还是很少的
2.使用人为抛出异常的方案,并且尽量抛出的是NotlmplementError。这样会比较专业,而且错误比较明确(推荐)
二、异常处理
首先,我们先了解下什么是异常。异常就是程序运行过程中产生的错误。那如果程序出现了异常。怎么处理呢?在之前我们已经写过类似的代码了。
下面我们先制造一个错误。来看看异常什么样子。
def chu(a, b): return a/b ret = chu(10, 0) print(ret)
除法中除数不能为零。如果真的出了这个错误你不能把这一堆信息抛给客户,要不然肯定给客户打死,那如何处理呢?
def chu(a, b): return a/b try: ret = chu(10, 0) print(ret) except Exception as e: print("除数不能是0") 结果: 除数不能是0
try.....except是什么意思呢?就是尝试运行代码。如果出现错误。就执行except后面的代码。
系统产生一个异常对象(面向对象一切皆对象),这个异常对象会外抛。被except拦截。并把接收的异常对象赋值给e,Exception是所有异常的父类或基类,也就是异常的根。但是这样写好些有点太笼统了。所有的错误都会被认为是Exception。当程序出现多种错误的时候就不好分类了,最好是出什么异常就用什么来处理。这样更加合理所以在try.....except语句中还可以写更多的except
try: print("各种操作....") except ZeroDivisionError as e: print("除数不能是0") except FileNotFoundError as e: print("⽂件不存在") except Exception as e: print("其他错误")
解读: 程序先执⾏操作, 然后如果出错了会走except中的代码. 如果不出错, 执⾏else中
的代码. 不论处不出错. 最后都要执⾏finally中的语句. ⼀般我们⽤try...except就够⽤了. 顶多
加上finally. finally⼀般⽤来作为收尾⼯作.
上⾯是处理异常. 我们在执⾏代码的过程中如果出现了⼀些条件上的不对等. 根本不符
合我的代码逻辑. 比如. 参数. 我要求你传递⼀个数字. 你非得传递⼀个字符串. 那对不起. 我
没办法帮你处理. 那如何通知你呢? 两个⽅案:
方案一:直接返回即可。
方案二:抛出一个异常。告诉你,我不好惹
所以以后的代码中如果出现了类似的问题。直接抛一个异常出去。怎么抛呢?我们要用到raise关键字
def add(a, b): ''' 给我传递两个整数. 我帮你计算两个数的和 :param :param a: :param :param b: :return :return: ''' if not type(a) == int and not type(b) == int: # 当程序运⾏到这句话的时候. 整个函数的调⽤会被中断. 并向外抛出⼀个异常. raise Exception("不是整数, 老子不能帮你搞定这么复杂的运算.") return a + b # 如果调⽤⽅不处理异常. 那产⽣的错误将会继续向外抛. 最后就抛给了⽤户 # 如果调⽤⽅处理了异常. 那么错误就不会丢给⽤户. 程序也能正常进⾏ try: add(10, "20") except Exception as e: print("报错了. ⾃⼰处理去吧")
当程序运行到raise。程序会被中断。并实例化后面的异常对象。抛给调用方。如果调用方不处理,那么错误会继续向上抛出。最终抛给用户。如果用户调用放处理了异常那么程序可以正常执行。
异常知道如何抛出和处理了。但是我们现在用的都是人家python给的异常。如果 有一天,你写的代码中出现了一个无法用现有的异常来解决问题。那怎么办?python可以自定义异常。
自定义异常,非常简单。只要你的类继承了Exception类。那你的类就是一个异常类。就这么简单。比如。你要写一个男澡堂子程序。如果这是来个女的。是不是要抛出一个异常,一个性别异常。
class GenderError(Exception): pass class Person: def __init__(self,name,gender): self.name = name self.gender = gender def nan_zao_tang(person): if person.gender != "男": raise GenderError("性别不对,这里是男澡堂") p1 = Person("wusir","男") p2 = Person("小红","女") #抛出异常:GenderError("性别不对,这里是男澡堂") #处理异常 try: nan_zao_tang(p1) nan_zao_tang(p2) except GenderError as e: print(e) except Exception as e: print("反正就是报错了")
这就搞定了,但是,但是如果真的报错了。我们最好能看到错误源自哪里,怎么办呢?需要引入另一个模块traceback。这个模块可以获取到我们每个方法的调用信息。
又被称为堆栈信息,这个信息对我们排查错误很有帮助的。
import traceback class GenderError(Exception): pass class Person: def __init__(self,name,gender): self.name = name self.gender = gender def nan_zao_tang(person): if person.gender != "男": raise GenderError("性别不对,这里是男澡堂") p1 = Person("wusir","男") p2 = Person("小红","女") #抛出异常:GenderError("性别不对,这里是男澡堂") #处理异常 try: nan_zao_tang(p1) nan_zao_tang(p2) except GenderError as e: val = traceback.format_exc() print(e) print(val) except Exception as e: print("反正就是报错了") 结果: 性别不对,这里是男澡堂 Traceback (most recent call last): File "D:/python_workspace_s18/day 20 约束 异常处理 MD5 日志处理/练习.py", line 57, in <module> nan_zao_tang(p2) File "D:/python_workspace_s18/day 20 约束 异常处理 MD5 日志处理/练习.py", line 49, in nan_zao_tang raise GenderError("性别不对,这里是男澡堂") GenderError: 性别不对,这里是男澡堂
这就搞定了,这样我们就能收放自如了。当测试代码的时候把堆栈信息打出来。但是当到了线上的生产环境的时候把这个堆栈去掉即可。
四、MD5加密
MD5是一种不可逆的加密算法。它是可靠的,并且安全的。在python中我们不需要手写这一套算法。只需要引入一个叫hashlib的模块就能搞定MD5的加密工作
import hashlib obj = hashlib.md5() obj.update("python".encode("utf-8")) s = obj.hexdigest() print(s) 结果: 23eeeb4347bdd26bfc6b7ee9a3b755dd
那这样的密文就安全了嘛?其实不安全。当我们用这样的密文去找一个所谓的MD5的解密工具。是有可能解密成功的。
这就很尴尬了。MD5不是不可逆嘛?注意这里有一个叫撞库的问题,就是。由于MD5的原始算法已经存在很久了。那就有一些人用一些简单的排列组合来计算MD5。然后当出现相同的MD5密文的时候就很容易反推出原来的数据是什么。所以并不是MD5可逆,而是有些别有用心的人把MD5的常见数据已经算出来并保留起来了。
那如何应对呢?加盐就行了。在使用MD5的时候。给函数的参数传一个byte即可。
import hashlib obj = hashlib.md5(b"adfaf;lkjfakl;djfiowueriojksafjk") #在这里加点小料 obj.update("python".encode("utf-8")) s = obj.hexdigest() print(s)
结果:
0971516bb92c3a2f821f561bf34a67fc
此时你再去任何网站去试,累死他也解不开!!
接下来就是MD5的应用:
import hashlib obj = hashlib.md5(b"ajfkljakldjfkljalkjfklajf") #加点小料 obj.update("123456".encode("utf-8")) s = obj.hexdigest() print(s) #先运算从密码的密文是什么 def my_md5(s): obj = hashlib.md5(b"ajfkljakldjfkljalkjfklajf") #加点小料 obj.update(s.encode("utf-8")) #加密的必须是字节 miwen = obj.hexdigest() return miwen username = input("请输入用户名:") password = input("请输入密码:") if username == "python" and my_md5(password) == "5ae2b66c95540260b97b75aa4b4e35f9": #判断是不是密码的密文 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) # 当前配置表示 10以上的分数会被写⼊⽂件 # 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 == 0: raise FileNotFoundError("⽂件不在啊") elif i % 3 == 1: raise KeyError("键错了") elif i % 3 == 2: raise JackError("杰克Exception") 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(依赖FileHandler) 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系统')