闭包与装饰器

变量的生命周期:函数调用完了就释放里面的变量

什么是闭包
• 在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。
• 闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。
• 在给定函数被多次调用的过程中,这些私有变量能够保持其持久性

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