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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351

推荐阅读更多精彩内容