闭包
- 闭包需要满足什么条件?(面试常问)
1.函数中嵌套一个函数
2.外层函数的返回值是内嵌函数的函数名
3.内嵌函数对外部作用域有一个非全局变量的引用
a .外层函数定义的变量
b.外层函数的入参
如果内嵌函数引用了一个全局变量,则就不是闭包 - 闭包的作用
a.闭包可以实现对数据的锁定,提高稳定性。闭包函数比普通函数会多出一个__closure__属性,其中定义了一个元组来存放多个cell对象,而每个cell对象分别保存了这个闭包中所有的外部变量
b.因为闭包对数据进行锁定,所以里面的属性不存在被修改的可能,所以使用起来更加安全,无需特地去对属性进行私有化 - 闭包在Python中十分常见,比如说装饰器。当你需要实现一个带参数的装饰器时,这个装饰器函数就是个闭包
# 闭包的例子
def func(a): # 传入外部变量
def wrapper(): # 1.内嵌函数
print(a) # 2.引用外部变量
return wrapper # 3.返回内嵌函数的名称
f = func(100) # 这里接收了返回的wrapper函数对象
f() # 相当于执行wrapper()函数,所以会打印出a的值:100
cells = f.__closure__
print(cells) # (<cell at 0x00000000004EEE58: int object at 0x0000000054EF7870>,)
print(cells[0].cell_contents) # 元组中的第一个cell的值:100
装饰器
在说装饰器之前,首先说下“开放封闭原则”,它可以说是面向对象原则的核心思想
- 什么是开放封闭原则?
软件实体应该是可扩展,但是不可修改的。函数对扩展是开放的,对修改是封闭的。通俗地讲,就是函数中已经实现的代码是不能修改的,但是可以对函数添加新的功能
为什么要先说“开放封闭原则”呢?因为装饰器就能让开发者很好地遵守这个原则
装饰器的作用
可以在不更改函数的内部代码,甚至不更改函数的调用方法的情况下,对函数进行功能的扩展函数的类型
1.普通装饰器
2.带参的装饰器
3.通用装饰器
4.类实现装饰器
5.装饰器装饰类
6.三个内置的装饰器@装饰器
在执行当前的py文件时,就已经执行了。并不是其装饰的函数/类被创建时才执行
装饰器常见的应用场景
1.鉴权校验(登录/权限)
2.给函数计算运行时间
3.测试环境的准备和恢复
4.UI自动化测试异常时的截图
...1、普通装饰器(闭包实现)
""" 1. 普通装饰器(闭包实现)"""
def decorator(func): # 可传入函数
def wrapper():
print('装饰器函数')
func() # 调用传入的函数
return wrapper # 返回内嵌函数
# @decorator;相当于 func1 = decorator(func)
@decorator
def func():
print("原功能函数")
func()
'''
打印结果:
装饰器函数
原功能函数
'''
"""1.1、应用场景:UI自动化报错截图"""
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('http://www.baidu.com')
def screen_shot(func):
def wrapper():
try:
func()
except Exception:
print('error')
browser.save_screenshot("Error.png") # 截图
return wrapper
@screen_shot
def search():
browser.find_element_by_xpath('//*[@id="kw"]').send_keys('海贼王')
browser.find_element_by_xpath('//*[@id="xxx"]').click() # xpath路径写错
search = search()
- 带参数的函数和元组拆包
def decorator(func):
print('---------decorator被调用-------------')
# 这里要接收参数,使用不定长参数来接收
def wrapper(*args):
print('这个是装饰器函数')
# 这里也要接收参数
func(*args)
return wrapper # 注意,这里没()
@decorator
def add_num(a, b):
print('a+b:', a + b)
add_num(1,2)
## 元组拆包
def func1(a, b, c):
print(a, b, c)
tu = (11, 22, 33)
# func1(tu) # 会报错
func1(*tu) # *会把元组进行拆包成三个元素,然后当成参数传过去
- 通用装饰器(就是能接收任意参数的装饰器)
def decorator(func):
print('---------decorator被调用-------------')
# 可接收任意形式的参数
def wrapper(*args, **kwargs):
print('这个是装饰器函数')
# 这里也一样
func(*args, **kwargs)
return wrapper
@decorator
def add_num(a, b, c):
print('a+b+c:', a + b + c)
add_num(1,2,3)
- 类实现装饰器
1.类实现装饰器主要是依赖两个魔术方法
a.__init__:类被初始化时调用它
b.__call__:在使用类名()
时,调用它
class MyDecorator(object):
print('MyDecorator类执行')
def __init__(self):
print('__init__方法执行')
def __call__(self, *args, **kwargs):
print('__call__方法执行')
my = MyDecorator() # 打印:MyDecorator类执行 __init__方法执行
my() # 这样使用类对象,就和使用函数对象一样;打印出:这个是__call__方法
- 类实现装饰器例子
class MyDecorator(object):
print('MyDecorator类执行')
# 接收被装饰函数
def __init__(self, func):
print('__init__方法执行')
self.func = func
# 需要被装饰的函数,放在__call__方法中
def __call__(self, *args, **kwargs):
print('__call__方法执行')
func = self.func(*args, **kwargs) # 调用被装饰函数
return func
@MyDecorator # 1.首先add_num = MyDecorator(add_num(a, b));2.add_num(a, b)被这个类的__init__方法接收
def add_num(a, b):
print('a+b:', a + b)
add_num(2, 4) # 3.执行这里时,MyDecorator类中的__call__方法会被调用
- 装饰器装饰类
1.有三种装饰器可以装饰类
a.类装饰器装饰类
b.函数装饰器装饰类
c.非闭包函数装饰类
2.装饰器装饰类,必须返回被装饰类对象的名称【重要】
a.如果是类装饰器,在__call__方法中返回
b.如果是函数装饰器,在内嵌装饰函数中返回
c.如函数装饰器 - 类装饰器装饰类的例子
3.装饰器装饰类或函数,不仅可以对功能进行扩展,还可以对其添加新的属性(变量)
a.可通过setattr(func, key, value)
进行添加,func是函数/方法,key是属性名称,value是属性值
"""类装饰器装饰类"""
class MyDecorator(object):
def __init__(self, func):
print('__init__方法开始执行')
self.func = func
def __call__(self, *args, **kwargs):
print('__call__方法开始执行')
func = self.func() # 被装饰函数被调用
return func # 需要返回被装饰类,否则返回None,那样就无法调用被装饰类中的方法
@MyDecorator # Hero = MyDecorator(Hero),初始化MyDecorator,执行它的__init__方法
class Hero(object):
def func(self):
print('Hero的func方h法')
h = Hero() # 执行MyDecorator的__call__方法,并返回被装饰后的Hero对象
h.func() # 由于返回了Hero对象,所以能调用它的方法
- 函数装饰器装饰类的例子(对属性进行扩展)
"""函数装饰器装饰类"""
def decorator(func):
print('---------decorator被调用-------------')
def wrapper(*args, **kwargs):
print('这个是装饰器函数')
setattr(func, 'name', '被添加的属性name') # 可以给被装饰对象添加属性
obj = func(*args, **kwargs)
return obj # 装饰器装饰类,需要把对象返回去,不然返回值是None
return wrapper # 注意,这里没()
@decorator # 1.把类当成参数;Hero = decorator(Hero)
class Hero(object):
def func2(self):
print('---func2----')
h = Hero() # 2.接收装饰过Hero类的wrapper()函数,并且wrapper()函数返回的是装饰后的Hero类
h.func2() # 3.因为返回了Hero类,所以能调用Hero的函数
print(h.name) # 4. 打印:被添加的属性name
- 非闭包函数装饰器装饰类
使用这种方式,在使用装饰器@xxx时,就已经调用了装饰器扩展的新功能(闭包是在调用时才调用)
所以,这种方式一般是用于给类添加新属性时使用;ddt就是这样做的
"""
非闭包函数装饰类
"""
def decorator1(cls):
setattr(cls, 'name', '装饰器加入的name属性')
print('装饰器扩展的新功能')
return cls
@decorator1 # 把类当成参数;Hero = decorator1(Hero),并执行了扩展的新功能
class Hero(object):
def func2(self):
print('---func2----')
h = Hero()
print(h.name) # 装饰器加入的name属性
- 被装饰器装饰的函数,其属性(比如name、doc等)会被改变的解决方法:
@wraps(func)
装饰器会把入参函数复制给被装饰的wrapper
函数,这样返回的函数就能保留入参时的函数信息
import time
from functools import wraps
def decorator(func):
@wraps(func) # 使得被装饰的函数的name不被改变
def wrapper(*args, **kwargs):
print(time.time())
func(*args, **kwargs)
return wrapper
@decorator
def fun1():
print(F"func.name:=>{fun1.__name__}")
if __name__ == '__main__':
# fun1() # 未加上装饰器:func.name:=>fun1
# fun1() # 加上装饰器后,本来的类名会改变:func.name:=>wrapper
fun1() # 加上wraps装饰器:func.name:=>fun1