闭包与装饰器 变量的生命周期:函数调用完了就释放里面的变量 什么是闭包 • 在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。 • 闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。 • 在给定函数被多次调用的过程中,这些私有变量能够保持其持久性 def outer(x): a=300 def inner(): print(x+a) return inner d=outer(100) d()#输出为400 说明a与x并没有被释放 闭包了(外函数调用的变量并没有随着外函数调用完而释放) 闭包满足的条件: 1.必须要有内嵌函数 2.内函数必须引用外函数的变量 3.外函数必须返回内函数 闭包后,内函数会多一个非空的__closure__属性,保存的是未被释放的变量(外函数的__closure__里面为NONE) def outer(): tmp=[] def inner(name): tmp.append(name) print(tmp) return inner d1=outer() d1("d1") ['d1'] d1("d1") ['d1', 'd1'] d2=outer() d2("d2") ['d2'] d2("d2") ['d2', 'd2'] 每次调用外函数都会重新执行,创建一个新的tmp和list 虽然代码都一样 但是创建的对象是不一样的 只要inner函数还在 tmp也就一直在 闭包的好处 • 闭包不是必须的。 • 没了闭包,python的功能一点不会被影响 • 有了闭包,只是提供给你一种额外的解决方案 装饰器:是一种程序设计模式,主要用于给函数或类添加一些额外的功能 又不希望通过继承或者修改源代码的方式去实现,那就使用装饰器 (总的来说 就是不改变函数或类的源代码基础上,添加额外功能) 装饰器的本质就是闭包函数,它需要把一个callable(函数,类(也就是可以打括号运行的对象))对象作为参数传递进来 统计运行时间的装饰器 import time def runtime(func): def inner(): start=time.time() func() end=time.time() print(f"执行函数花了{end-start}s") return inner @runtime def func1(): time.sleep(2) print("func1.....") @runtime # 相当于 func1=runtime(func1) def func2(): time.sleep(1) print("func2.....") func1() 于是就相当于就是在运行inner函数 func2() 执行结果: func1..... 执行函数花了2.0078823566436768s func2..... 执行函数花了1.0153162479400635s @被称为修饰符 得到原函数的返回值 def runtime(func): def inner(): return func() return inner @runtime def func1(): return 3 r=func1() print(r) 若func有参数时: def runtime(func): def inner(*args,**kwargs):#可变长位置参数和可变长关键字参数 可传可不传 return func(*args,**kwargs) return inner @runtime def func1(a,b): print(a+b) func1(1,2) @runtime def func2(): return 3 print(func2()) 装饰器的应用: 添加额外功能: 计时 权限控制 日志记录 日志:记录软件运行过程中发生的事件 通过日志可以做什么? 1.程序调试(了解程序的运行情况是否正常) 2.排错(分析和定位故障) 3.用户行为分析 python里面的日志处理模块(logging 模块处理日志):这是内建模块 五个日志等级: 日志等级 数值表示 描述 1.DEBUG 10 最详细的日志信息,开发过程中用于诊断问题 2.INFO 20 详细日志信息仅次于debug 记录关键节点的信息 3.WARNING 30(默认) 当前不期望的事情发生 4.ERROR 40 发生错误问题导致某些功能不能正常使用 5.CRITICAL 50 发生严重错误导致程序不能继续运行 程序默认日志等级一般设置为warning 表示显示warining以上的日志 log_format="%(asctime)s - %(filename)s - %(levelname)s:%(message)s" logging.basicConfig(level=logging.DEBUG, format=log_format, filename="a.log") #表示把系统默认日志等级改为debug,且规定日志输出格式,且指定输出到a.txt文件里面(不输出到屏幕) logging.warning("waring") logging.error("error") logging.debug("debug") #DEBUG:root:debug 会显示默认的日志格式 logging日志系统的四大组件 日志器 logger 处理器 handler 过滤器 filter 格式器 formatter 更加灵活的记录 import logging #得到一个日志器 日志器用来记录日志 logger=logging.getLogger() #处理器 就是决定日志要发送到哪里 fh=logging.FileHandler("SC.log")#把等会记录的日志发送到sc.log 要是没有参数 则是在当前 ch=logging.StreamHandler()#把等会记录的日志输出到屏幕 #将handler绑到logger对象上才能使用 logger.addHandler(fh) logger.addHandler(ch) #直接输出括号里的内容 无任何格式 既到文件又到屏幕 logger.warning("this is warining...") #于是我们需要自己定义格式 formatter=logging.Formatter("%(asctime)s - %(filename)s - %(levelname)s:%(message)s") #绑定formatter到handler上 fh.setFormatter(formatter)#对于写入文件的处理器绑定格式 对于输到屏幕的不一样 logger.warning("warining...") 日志器层级关系 类似于继承 getLogger不传参数,我们称为根日志器 称作root logger (类似/) 传递sc 就是子日志器 (类似于/sc) 子日志器回继承父日志器的一切配置 logger2=logging.getLogger("sc") logger3=logging.getLogger("sc.a")#logger3继承sc 且名字叫做a 日志的轮转 按时间 日志文件名称接的是时间 按大小 一般日志文件名称接的是大小 Linux里面的logtotate.conf里面可以规定系统几天一轮转以及大小 python里面日志可以在handler里面的一个方法里面设置 ''' 装饰器需要统计运行时间 需要统计运行了什么函数,且把函数名放入日志文件 ''' #统计运行时间的装饰器 import time import logging import functools logger = logging.getLogger() fh = logging.FileHandler("SC.txt") ch = logging.StreamHandler() logger.addHandler(fh) logger.addHandler(ch) formatter=logging.Formatter("%(asctime)s - %(filename)s - %(levelname)s:%(message)s") fh.setFormatter(formatter) #统计运行时间的装饰器 def runtime(func): @functools.wraps(func) def inner(*args,**kwargs): start=time.time() result=func(*args,**kwargs) end=time.time() print(f"执行函数花了{end-start}s") logger.warning(f"执行函数花了{end-start}s") return result return inner def name(func): #保留元数据,将传进去的func的元数据全部复制给inner def inner(*args,**kwargs): result=func(*args,**kwargs) print(f"正在运行{func.__name__}函数") logger.warning(f"正在运行{func.__name__}函数") return result return inner #第一种情况 不行 # @runtime #add=runtime(name(add)) # @name #add=name(add) #name放在后面时间不对(时间包括了name里inner运行的时间) name放在前面名字不对 #所以只能保留源数据 @name #add=name(runtime(add)) @runtime #add=runtime(add) def add(a,b): time.sleep(2) print(1111) return a+b print(add(1,2)) 装饰器的用途就是不改变原代码的情况下 增加额外的功能 所以每个装饰器在原函数有返回值的情况下 最好运行一下func 并且return result