装饰器
首先说一下装饰器
import time def timer(func): def inner(): start = time.time() func() print(time.time() - start) return inner @timer #==> func1 = timer(func1) def func1(): print('in func1') func1()
上边一段代码就是简单的装饰器,,装饰器的作用是在不改变原函数的内容以及调用方式返回值的情况下,给函数添加功能(个人的理解),具体的书写方式就是先写外部函数,然后写内部函数,在内部函数内执行想要添加功能的原函数,在原函数执行前后可以进行功能的添加。
以上虽然是一个装饰器但还是不完整,如果原函数是带参数和返回值的,我们就改变了原函数的调用方式和返回值,这就违背了装饰器的初衷,应该将原函数的参数传递进去
import time def timer(func): def inner(*args,**kwargs): start = time.time() re = func(*args,**kwargs) print(time.time() - start) return re return inner @timer #==> func1 = timer(func1) def func1(a,b): print('in func1') @timer #==> func2 = timer(func2) def func2(a): print('in func2 and get a:%s'%(a)) return 'fun2 over' func1('aaaaaa','bbbbbb') print(func2('aaaaaa'))
import time def timer(func): def inner(*args,**kwargs): start = time.time() re = func(*args,**kwargs) print(time.time() - start) return re return inner @timer #==> func2 = timer(func2) def func2(a): print('in func2 and get a:%s'%(a)) return 'fun2 over' func2('aaaaaa') print(func2('aaaaaa'))
刚刚那个装饰器已经非常完美了,但是正常我们情况下查看函数的一些信息的方法在此处都会失效
def index(): '''这是一个主页信息''' print('from index') print(index.__doc__) #查看函数注释的方法 print(index.__name__) #查看函数名的方法
为了不让他们失效,我们还要在装饰器上加上一点来完善它:
from functools import wraps def deco(func): @wraps(func) #加在最内层函数正上方 def wrapper(*args,**kwargs): return func(*args,**kwargs) return wrapper @deco def index(): '''哈哈哈哈''' print('from index') print(index.__doc__) print(index.__name__)
装饰器的主要功能和装饰器的固定结构
装饰器的主要功能:
在不改变函数调用方式的基础上在函数的前、后添加功能。
装饰器的固定格式:
from functools import wraps def deco(func): @wraps(func) #加在最内层函数正上方 def wrapper(*args,**kwargs): return func(*args,**kwargs) return wrapper
带参数的装饰器
假如你有成千上万个函数使用了一个装饰器,现在你想把这些装饰器都取消掉,你要怎么做?
一个一个的取消掉? 没日没夜忙活3天。。。
过两天你领导想通了,再让你加上。。。
def outer(flag): def timer(func): def inner(*args,**kwargs): if flag: print('''执行函数之前要做的''') re = func(*args,**kwargs) if flag: print('''执行函数之后要做的''') return re return inner return timer @outer(False) def func(): print(111) func()
多个装饰器装饰同一个函数
有些时候,我们也会用到多个装饰器装饰同一个函数的情况。
这种情况下装饰顺序按照就近原则
MD5
MD5是一种不可逆的加密算法. 它是可靠的. 并且安全的. 在python中我们不需要手写
这一套算法. 只需要引入一个叫hashlib的模块就能搞定MD5的加密工作
import hashlibobj = 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
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("失败")
异常
异常处理
首先, 我们先说⼀下, 什么是异常? 异常是程序在运行过程中产生的错误. 就好比. 你在
回家路上突然天塌了. 那这个就属于⼀个异常. 总之就是不正常. 那如果程序出现了异常. 怎
么处理呢? 在之前的学习中我们已经写过类似的代码了.
我们先制造⼀个错误. 来看看异常长什么样.
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 = genderdef 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: 性别不对. 这⾥是男澡堂⼦
搞定了. 这样我们就能收放自如了. 当测试代码的时候把堆栈信息打印出来. 但是当到了
线上的生产环境的时候把这个堆栈去掉即可
日志
首先, 你要知道在编写任何一款软件的时候, 都会出现各种各样的问题或者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, "我是⾃定义") # ⾃定义. 看着给分
最后, 如果你系统中想要把日志文件分开. 比如. 一个大项目, 有两个子系统, 那两个子系
统要分开记录日志. 方便调试. 那怎么办呢? 注意. 用上面的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系统')