约束和异常处理

⼀. 类的约束
  ⾸先, 你要清楚. 约束是对类的约束. 好比. 如今. 你是⼀个项⽬经理. 而后呢. 你给⼿下
的⼈分活. 张三, 你处理⼀下普通⽤户登陆, 李四, 你处理⼀下会员登陆, 王五, 你处理⼀下管
理员登陆. 那这个时候呢. 他们就开始分别取写他们的功能了. 可是呢. 你要知道, 程序员不⼀
定会有那么好的默契. 颇有可能三个⼈会写彻底三个不一样的⽅法. 就好比这样:java

class Normal: # 张三, 普通⼈登陆
 def login(self):
 pass
class Member: # 李四, 会员登陆
 def denglu(self):
 pass
 
class Admin: # 王五, 管理员登陆
 def login(self):
 pass

    而后呢, 他们把这样的代码交给你了. 你看了⼀眼. 张三和王五还算OK 这个李四写的是
什么⿁? denglu.......难受不. 可是好⽍能⽤. 还能凑合. 可是这时. 你这边要使⽤了. 问题就来
了.python

# 项⽬经理写的总⼊⼝
def login(obj):
 print("准备验证码.......")
 obj.login()
 print("进⼊主⻚.......")

    对于张三和王五的代码. 没有问题. 可是李四的. 你是否是调⽤不了. 那如何避免这样的
问题呢? 咱们要约束程序的结构. 也就是说. 在分配任务以前就应该把功能定义好. 而后分别
交给底下的程序员来完成相应的功能.
    在python中有两种办法来解决这样的问题. 程序员

  1. 提取⽗类. 而后在⽗类中定义好⽅法. 在这个⽅法中什么都不⽤⼲. 就抛⼀个异
      常就能够了. 这样全部的⼦类都必须重写这个⽅法. 不然. 访问的时候就会报错. 
  2. 使⽤元类来描述⽗类. 在元类中给出⼀个抽象⽅法. 这样⼦类就不得不给出抽象
      ⽅法的具体实现. 也能够起到约束的效果. 算法

    ⾸先, 咱们先看第⼀种解决⽅案: ⾸先, 提取⼀个⽗类. 在⽗类中给出⼀个⽅法. 而且在⽅
法中不给出任何代码. 直接抛异常. 数据库

class Base:
 def login(self):
 raise Exception("你没有实现login⽅法()")
class Normal(Base):
 def login(self):
 pass
class Member(Base):
 def denglu(self):
 pass
class Admin(Base):
 def login(self):
 pass
# 项⽬经理写的总⼊⼝
def login(obj):
 print("准备验证码.......")
 obj.login()
 print("进⼊主⻚.......")
n = Normal()
m = Member()
a = Admin()
login(n)
login(m) # 报错. 
login(a)

    在执⾏到login(m)的时候程序会报错. 缘由是, 此时访问的login()是⽗类中的⽅法. 可是⽗
类中的⽅法会抛出⼀个异常. 因此报错. 这样程序员就不得不写login⽅法了. 从⽽对⼦类进⾏
了相应的约束.
在本⽰例中. 要注意. 咱们抛出的是Exception异常. ⽽Exception是全部异常的根. 咱们⽆法通
过这个异常来判断出程序是由于什么报的错. 因此. 最好是换⼀个比较专业的错误信息. 最好
是换成NotImplementError. 其含义是. "没有实现的错误". 这样程序员或者项⽬经理能够⼀⽬
了然的知道是什么错了. 就比如. 你犯错了. 我就告诉你犯错了. 你也不知道哪⾥错了. 这时我
告诉你, 你xxx错了. 你改也好改不是?c#

 

    第⼆套⽅案: 写抽象类和抽象⽅法. 这种⽅案相对来讲比上⼀个⿇烦⼀些. 须要给⼤家先
引入⼀个抽象的概念. 什么是抽象呢? 想⼀下. 动物的吃. 你怎么描述? ⼀个动物到底应该怎
么吃? 是否是描述不清楚. 这⾥动物的吃就是⼀个抽象的概念. 只是⼀个动做的概念. 没有具
体实现. 这种就是抽象的动做. 换句话说. 咱们若是写⼀个⽅法. 不知道⽅法的内部应该到底
写什么. 那这个⽅法其实就应该是⼀个抽象的⽅法. 若是⼀个类中包含抽象⽅法. 那么这个类
⼀定是⼀个抽象类. 抽象类是不能有实例的. 好比. 你看看⼀些抽象派的画做. 在现实中是不
存在的. 也就⽆法创建实例对象与之相对应. 因此抽象类⽆法建立对象. 建立对象的时候会报
错.安全

    在python中编写⼀个抽象类比较⿇烦. 须要引入abc模块中的ABCMeta和
abstractmethod这两个内容. 来咱们看⼀个例⼦.函数

from abc import ABCMeta, abstractmethod
# 类中包含了抽象⽅法. 那此时这个类就是个抽象类. 注意: 抽象类能够有普通⽅法
class IGame(metaclass=ABCMeta):
 # ⼀个游戏到底怎么玩⼉? 你能形容? 流程能⼀样么?
 @abstractmethod
 def play(self):
 pass
 def turn_off(self):
 print("破B游戏不玩了, 脱坑了")
class DNFGame(IGame):
 # ⼦类必须实现⽗类中的抽象⽅法. 不然⼦类也是抽象类
 def play(self):
 print("dnf的玩⼉法")
# g = IGame() # 抽象类不能建立对象
dg = DNFGame()
dg.play()

  经过代码咱们能发现. 这⾥的IGame对DNFGame进⾏了约束. 换句话说. ⽗类对⼦类进
⾏了约束.

  下来. 继续解决咱们⼀开始的问题. 学习

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 denglu(self): # 这个就没⽤了
 pass
 def login(self): # ⼦类对⽗类进⾏实现
 pass
class Admin(Base):
 def login(self):
 pass
# 项⽬经理写的总⼊⼝
def login(obj):
 print("准备验证码.......")
 obj.login()
 print("进⼊主⻚.......")
n = Normal()
m = Member()
a = Admin()
login(n)
login(m)
login(a)

  总结: 约束. 其实就是⽗类对⼦类进⾏约束. ⼦类必需要写xxx⽅法. 在python中约束的
⽅式和⽅法有两种:测试

  1. 使⽤抽象类和抽象⽅法, 因为该⽅案来源是java和c#. 因此使⽤频率仍是不多的
  2. 使⽤⼈为抛出异常的⽅案. 而且尽可能抛出的是NotImplementError. 这样比较专
      业, ⽽且错误比较明确.(推荐)

⼆. 异常处理
  ⾸先, 咱们先说⼀下, 什么是异常? 异常是程序在运⾏过程当中产⽣的错误. 就比如. 你在
回家路上忽然天塌了. 那这个就属于⼀个异常. 总之就是不正常. 那若是程序出现了异常. 怎
么处理呢? 在以前的学习中咱们已经写过相似的代码了.
  咱们先制造⼀个错误. 来看看异常⻓什么样.

def chu(a, b):
 return a/b
ret = chu(10, 0)
print(ret)
结果: 
Traceback (most recent call last):
 File "/Users/sylar/PycharmProjects/oldboy/⾯向对象/day05.py", line 100, in
<module>
 ret = chu(10, 0)
 File "/Users/sylar/PycharmProjects/oldboy/⾯向对象/day05.py", line 98, in
chu
 return a/b
ZeroDivisionError: division by zero

  什么错误呢. 除法中除数不能是0. 那若是真的出了这个错. 你把这⼀堆信息抛给客户
么? 确定不能. 那如何处理呢?

def chu(a, b):
 return a/b
try:
 ret = chu(10, 0)
 print(ret)
except Exception as e:
 print("除数不能是0")
结果:
除数不能是0

  那try...except是什么意思呢? 尝试着运⾏xxxxx代码. 出现了错误. 就执⾏except后⾯的
代码. 在这个过程当中. 当代码出现错误的时候. 系统会产⽣⼀个异常对象. 而后这个异常会向
外抛. 被except拦截. 并把接收到的异常对象赋值给e. 那这⾥的e就是异常对象. 那这⾥的
Exception是什么? Exception是全部异常的基类, 也就是异常的跟. 换句话说. 全部的错误都
是Exception的⼦类对象. 咱们看到的ZeroDivisionError 其实就是Exception的⼦类. 那这样
写好像有点⼉问题撒. Exception表示全部的错误. 太笼统了. 全部的错误都会被认为是Exception.
当程序中出现多种错误的时候, 就很差分类了, 最好是出什么异常就⽤什么来处理. 这样就更加合理了.


因此在try...execpt语句中. 还能够写更多的except

try:
 print("各类操做....")
except ZeroDivisionError as e:
 print("除数不能是0")
except FileNotFoundError as e:
 print("⽂件不存在")
except Exception as e:
 print("其余错误")

  此时. 程序运⾏过程当中. 若是出现了ZeroDivisionError就会被第⼀个except捕获. 若是出
现了FileNotFountError就会被第⼆个except捕获. 若是都不是这两个异常. 那就会被最后的
Exception捕获. 总之最后的Exception就是咱们异常处理的最后⼀个守⻔员. 这时咱们最常
⽤的⼀套写法. 接下来. 给出⼀个完整的异常处理写法(语法):

try:
 '''操做'''
except Exception as e:
 '''异常的⽗类,能够捕获全部的异常'''
else:
 '''保护不抛出异常的代码, 当try中⽆异常的时候执⾏'''
finally:
 '''最后老是要执⾏我'''

  解读: 程序先执⾏操做, 而后若是出错了会走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
# 若是调⽤⽅不处理异常. 那产⽣的错误将会继续向外抛. 最后就抛给了⽤户
# add("你好", "我叫赛利亚")
# 若是调⽤⽅处理了异常. 那么错误就不会丢给⽤户. 程序也能正常进⾏
try:
 add("胡辣汤", "滋滋冒油的⼤腰⼦")
except Exception as e:
 print("报错了. ⾃⼰处理去吧")

     当程序运⾏到raise. 程序会被中断. 并实例化后⾯的异常对象. 抛给调⽤⽅. 若是调⽤⽅
不处理. 则会把错误继续向上抛出. 最终抛给⽤户. 若是调⽤⽅处理了异常. 那程序能够正常
的进⾏执⾏.

    说了这么多. 异常也知道如何抛出和处理了. 可是咱们如今⽤的都是⼈家python给的异
常. 若是某⼀天. 你写的代码中出现了⼀个⽆法⽤现有的异常来解决问题. 那怎么办呢? 别着
急. python能够⾃定义异常.

     ⾃定义异常: 很是简单. 只要你的类继承了Exception类. 那你的类就是⼀个异常类. 就这
么简单. 好比. 你要写⼀个男澡堂⼦程序. 那这时要是来个女的. 你怎么办? 是否是要抛出⼀个
性别异常啊? 好. 咱们来完成这个案例:

# 继承Exception. 那这个类就是⼀个异常类
class GenderError(Exception):
 pass
class Person:
 def __init__(self, name, gender):
 self.name = name
 self.gender = gender
def nan_zao_tang_xi_zao(person):
 if person.gender != "":
 raise GenderError("性别不对. 这⾥是男澡堂⼦")
p1 = Person("alex", "")
p2 = Person("eggon", "")
# nan_zao_tang_xi_zao(p1)
# nan_zao_tang_xi_zao(p2) # 报错. 会抛出⼀个异常: GenderError
# 处理异常
try:
 nan_zao_tang_xi_zao(p1)
 nan_zao_tang_xi_zao(p2)
except GenderError as e:
 print(e) # 性别不对, 这⾥是男澡堂⼦
except Exception as e:
 print("反正报错了")

  ok搞定. 可是, 若是是真的报错了. 咱们在调试的时候, 最好是能看到错误源⾃于哪⾥?
怎么办呢? 须要引入另⼀个模块traceback. 这个模块能够获取到咱们每一个⽅法的调⽤信息.
⼜被成为堆栈信息. 这个信息对咱们拍错是颇有帮助的.

import traceback
# 继承Exception. 那这个类就是⼀个异常类
class GenderError(Exception):
 pass
class Person:
 def __init__(self, name, gender):
 self.name = name
 self.gender = gender
def nan_zao_tang_xi_zao(person):
 if person.gender != "":
 raise GenderError("性别不对. 这⾥是男澡堂⼦")
p1 = Person("alex", "")
p2 = Person("eggon", "")
# nan_zao_tang_xi_zao(p1)
# nan_zao_tang_xi_zao(p2) # 报错. 会抛出⼀个异常: GenderError
# 处理异常
try:
 nan_zao_tang_xi_zao(p1)
 nan_zao_tang_xi_zao(p2)
except GenderError as e:
 val = traceback.format_exc() # 获取到堆栈信息
 print(e) # 性别不对. 这⾥是男澡堂⼦
 print(val)
except Exception as e:
 print("反正报错了")
结果: 
性别不对. 这⾥是男澡堂⼦
Traceback (most recent call last):
 File "/Users/sylar/PycharmProjects/oldboy/⾯向对象/day05.py", line 155, in
<module>
 nan_zao_tang_xi_zao(p2)
 File "/Users/sylar/PycharmProjects/oldboy/⾯向对象/day05.py", line 144, in
nan_zao_tang_xi_zao
 raise GenderError("性别不对. 这⾥是男澡堂⼦")
GenderError: 性别不对. 这⾥是男澡堂⼦

  搞定了. 这样咱们就能收放⾃如了. 当测试代码的时候把堆栈信息打印出来. 可是当到了
线上的⽣产环境的时候把这个堆栈去掉便可.

四. MD5加密
  想⼀个事情. 你在银⾏取钱或者办卡的时候. 咱们都要输入密码. 那这个密码若是就按照
咱们输入的那样去存储. 是否是很不安全啊. 若是某⼀个程序员进入到了银⾏的数据库. ⽽银
⾏的数据库⼜存的都是明⽂(不加密的密码)密码. 这时, 整个银⾏的帐户⾥的信息都是很是非
常不安全的. 那怎么办才安全呢? 给密码加密. 而且是不可逆的加密算法. 这样. 即便获取到了
银⾏的帐户和密码信息. 对于⿊客⽽⾔都⽆法进⾏破解. 那咱们的帐号就相对安全了不少. 那
怎么加密呢? 最常⻅的就是⽤MD5算法.
  MD5是⼀种不可逆的加密算法. 它是可靠的. 而且安全的. 在python中咱们不须要⼿写
这⼀套算法. 只须要引入⼀个叫hashlib的模块就能搞定MD5的加密⼯做

import hashlib
obj = hashlib.md5()
obj.update("alex".encode("utf-8")) # 加密的必须是字节
miwen = obj.hexdigest()
print(miwen) # 534b44a19bf18d20b71ecc4eb77c572f

  那这样的密⽂安全么? 实际上是不安全的. 当咱们⽤这样的密⽂去找⼀个所谓的MD5解密
⼯具. 是有可能解密成功的.

 

 

这就尴尬了. MD5不是不可逆么? 注意. 这⾥有⼀个叫撞库的问题. 就是. 因为MD5的原
始算法已经存在好久了. 那就有⼀些⼈⽤⼀些简单的排列组合来计算MD5. 而后当出现相同
的MD5密⽂的时候就很容易反推出原来的数据是什么. 因此并非MD5可逆, ⽽是有些别有
⽤⼼的⼈把MD5的常⻅数据已经算完并保留起来了.

  那如何应对呢? 加盐就⾏了. 在使⽤MD5的时候. 给函数的参数传递⼀个byte便可. 

 

import hashlib
obj = hashlib.md5(b"fjlksajflkjasfsalwer123dfskjf") # 加盐
obj.update("alex".encode("utf-8")) # 加密的必须是字节
miwen = obj.hexdigest()
print(miwen) # 99fca4b872fa901aac30c3e952ca786d
 

此时你再去任何⽹站去试. 累死他也解不开密. 

那MD5如何应⽤呢? 

import hashlib
def my_md5(s):
 obj = hashlib.md5(b"fjlksajflkjasfsalwer123dfskjf")
 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) # 当前配置表示 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系统')
相关文章
相关标签/搜索