8.闭包和装饰器

闭包
  • 闭包需要满足什么条件?(面试常问)
    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)  # *会把元组进行拆包成三个元素,然后当成参数传过去
普通装饰器.png

  • 通用装饰器(就是能接收任意参数的装饰器)
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__方法会被调用
类实现装饰器.png

  • 装饰器装饰类
    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
函数装饰器装饰类.png

  • 非闭包函数装饰器装饰类
    使用这种方式,在使用装饰器@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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容