Python day14:闭包函数与装饰器

一、闭包函数
闭包函数=函数嵌套定义+函数对象+名称空间与作用域

1、闭:指的是该函数是定义在一个函数内部的函数
2、包:值得是该函数访问了一个来自于外层函数的变量

为函数体传参的方法:

'''方案一:直接使用参数的形式传递'''
def wrapper(x):
    print(x)

wrapper(111)
wrapper(222)
'''方案二:把函数体想要的参数包给它(即使用闭包的概念)'''
def outter(x):
    x = 111

    def wrapper():  # wrapper = 闭包函数的内存地址
        print(x)

    return wrapper  # 一定不要加括号
    return 闭包函数的内存地址

f1 = outter(111)  # f = 闭包函数的内存地址
f2 = outter(222)  # f = 闭包函数的内存地址

乍一看会感觉使用闭包来传参数非常的麻烦,我们之前使用函数,需要参数都是直接传给他,方便也快捷,但是在某些场景下我们定死了某个函数无法直接传参数,那就必须通过其他方式传参数,即闭包的方式,下面介绍的装饰器就是闭包的使用

二、无参装饰器
1、什么是装饰器
器:工具
装饰:为被装饰者添加额外的功能

2、为何要有装饰器
软件一旦上线运行之后,就应该遵循开放封闭原则:
1、开放指的是对拓展新功能开放
2、封闭指的是对修改源代码封闭
这种情况下,我们在写新功能时,若需要用到新的参数,就无法直接通过原函数来传了,必须通过其他方式来传参,目前我们想到的方法,是两种传参的另一种的方式,闭包函数

定义装饰器的目的:
定义装饰器就是为了在遵循1和2的前提下来为其他函数添加新功能的

ps:
不修改被装饰对象指的是定义与调用都不能修改
所以下述行为都违反了开放封闭原则:
①、修改被装饰对象定义时的源代码
②、修改被装饰对象的调用方式

3、如何用装饰器:

'''无参装饰器基本格式'''
def inner(func):
    def wrapper(*args,**kwargs):
        res = func(*args,**kwargs)
        return func
    return wrapper()

4、无参装饰器的应用场景以及构建装饰器步骤
需求:有一个index函数,此时我们要在index函数基础上添加一个计算运算时间的功能,因为是公司项目,我们不能修改原函数的调用方式,也不能修改源代码。

一、被装饰器对象index如下
import time

def index(x,y):
    time.sleep(1)
    print("index---->",x,y)

index(1,2)

二、为index添加计算运算时间功能
方案1问题:修改了源代码
import time

def index(x,y):
    start = time.time()
    time.sleep(1)
    print("index---->",x,y)
    end = time.time()
    print(end-start)

index(1,2)

方案2问题:需要找到所有调用index的位置
import time

def index(x,y):
    time.sleep(1)
    print("index---->",x,y)


start = time.time()
index(1,2)
end = time.time()
print(end-start)

方案3问题:函数被写死了只能算index的时间
import time

def index(x,y):
    time.sleep(1)
    print("index---->",x,y)

def wrapper():
    start = time.time()
    index(1,2)
    end = time.time()
    print(end-start)

wrapper()

方案4:直接传index进去,改变了index的调用方式,现在需要用wrapper来调用index,我们需要再想一种传参的方式
import time

def index(x,y):
    time.sleep(1)
    print("index---->",x,y)

def wrapper(func):
    start = time.time()
    func(1,2)
    end = time.time()
    print(end-start)

wrapper(index)

方案5问题:此时利用闭包函数,将index包给了wrapper,再重命名为index,我们在调用index时其实是调用wrapper,但有出现了问题,\
原函数在调用的时候需要传参数如index(1,2),而wrapper接收不了参数

import time

def index(x,y):
    time.sleep(1)
    print("index---->",x,y)

def outter(func):
    # func = index()
    def wrapper():
        start = time.time()
        func(1,2)
        end = time.time()
        print(end-start)
    return wrapper

index = outter(index)
index()

方案6
import time
from functools import wraps

def timmer(func):
    def wrapper(*args,**kwargs):
        start = time.time()
        res = func(*args,**kwargs)
        end = time.time()
        print(end-start)
        return res
    return wrapper

def login(func):
    def wrapper(*args,**kwargs):
        name = 'yang'
        pwd = '123'
        inp_name = input("请输入您的用户名:").strip()
        inp_pwd = input("请输入您的密码").strip()
        if inp_name == name and inp_pwd == pwd:
            print("登录成功")
            res = func(*args,**kwargs)
            return res
    return wrapper

@timmer
@login
def index(x,y):
    time.sleep(1)
    print("index---->",x,y)

index(1,2)

三、有参装饰器
对于不再需要新参数的装饰器,两层就可以解决了,但是当我们的装饰器需要新的参数,如在登录的时候,我们要判断我们用户名与密码的来源,此时需要外部将来源传进来。而两层的装饰器中,第一层,是为了给原函数传参数,他的参数不能动,而第二层,因为装饰器语法@的原因,也已经定死了此处只能传一个函数名,也不能传参数,所以我们需要构造第三层来接受外部的参数,而在内部的函数可以在不改动自己依然可以获取到外层的函数.

'''有参装饰器模版'''
def outter2(x,y,z,a,b):
    def outter1(func):
        def wrapper(*args,**kwargs):
            res=func(*args,**kwargs)
            return res
        return wrapper
    return outter1

def outter(engine = 'file'):
    def deco2(func2):
        def wrapper2(*args,**kwargs):
            inp_name = input('username>>>: ').strip()
            inp_pwd = input('password>>>: ').strip()

            if engine == "file":
                print('基于file的认证')
                if inp_name == "egon" and inp_pwd == "123":
                    print('login successful')
                    res2=func2(*args,**kwargs)
                    return res2
                else:
                    print('username or password error')
            elif engine == 'mysql':
                print('基于mysql的认证')
            elif engine == 'ldap':
                print('基于ldap的认证')
            else:
                print('未知的engine')
        return wrapper2
    return deco2

@outter(engine='mysql')  # @deco2 # index=deco2(index)
def index(x,y):
    print('index=>',x,y)

index(1,2)  # index=>wrapper

四、叠加多个装饰器的运行步骤(记住结论既可)
2.1 加载顺序:自下而上
2.2 执行顺序:自上而下运行内层的wrapper函数
结论:

1、无参装饰器的模板
def outter(func):
    def wrapper(*args,**kwargs):
        res=func(*args,**kwargs)
        return res
    return wrapper


2、叠加多个装饰器
**2.1 加载顺序:自下而上**
**2.2 执行顺序:自上而下运行内层的wrapper函数**

def deco1(func1):  # func1 = wrapper2的内存地址
    def wrapper1(*args,**kwargs):
        print('wrapper1====>')
        res1=func1(*args,**kwargs)
        return res1
    return wrapper1

def deco2(func2):  # func2 = wrapper3的内存地址
    def wrapper2(*args,**kwargs):
        print('wrapper2====>')
        res2=func2(*args,**kwargs)
        return res2
    return wrapper2

def deco3(func3):  # func3 = 最原始的那个被装饰函数的内存地址
    def wrapper3(*args,**kwargs):
        print('wrapper3====>')
        res3=func3(*args,**kwargs)
        return res3
    return wrapper3

        # index=wrapper1的内存地址
@deco1  # deco1(wrapper2的内存地址)=>wrapper1的内存地址
@deco2  # deco2(wrapper3的内存地址)=>wrapper2的内存地址
@deco3  # deco3(最原始的那个被装饰函数的内存地址)=>wrapper3的内存地址  
def index(x,y):
    print('index=>',x,y)

index(1,2)

"""
wrapper1====>'
wrapper2====>
wrapper3====>
index=>1,2
"""


3 案例
import time

def deco1(func1):
    def wrapper1(*args,**kwargs):
        start_time = time.time()
        res1=func1(*args,**kwargs)
        stop_time = time.time()
        print(stop_time - start_time)
        return res1
    return wrapper1


def deco2(func2):
    def wrapper2(*args,**kwargs):
        inp_name = input('username>>>: ').strip()
        inp_pwd = input('password>>>: ').strip()
        if inp_name == "egon" and inp_pwd == "123":
            print('login successful')
            res2=func2(*args,**kwargs)
            return res2
        else:
            print('username or password error')
    return wrapper2

@deco2
@deco1
def index(x,y):
    time.sleep(1)
    print('index=>',x,y)

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