• 7.11 闭包函数与装饰器


    补充:

      callable 代表可调用的,加括号可以执行。(函数或者类)

      import this  查看python之禅

    一。闭包函数

      所谓闭包函数,就是定义在函数内部的函数,也就是函数定义的嵌套。而在其内部函数中可以引用外部函数作用域的名字。

      闭包的直接问题是传参。

      一般的传参方法都是将参数直接传递给函数,函数内部就可以引用,如:

    def foo(oop):
        print(oop)
    foo(123)
    #输出结果>>>123

      foo收到参数123后直接打印,而在闭包函数中需要从内函数调用外函数的参数,进行运算,如下:

    def outter():
        x=1
        y=2
        def inner():
            print(x+y)
            return x+y
        return inner
    res=outter()
    res()
    #输出结果>>>3

      像内函数inner()就调用了外函数的x与y参数。这里复习一下闭包函数的运行过程。

      当outter被调用时,执行return inner,返回的是inner的内存地址,所以outter()就是outter函数的返回值,所以res就被赋予inner的内存地址,加上()后才能执行print(x+y)。

      res仅仅代表着1与2的值,在运行时并不能对其进行传参,所以一下代码等同于以上代码:

    def outter(x,y):
        def inner():
            print(x+y)
            return x+y
        return inner
    res=outter(1,2)
    res()
    res=outter(3,4)
    res()
    #输出结果>>>3  7

      x,y是形参,在创建和传参时就相当于使得x=1,y=2,在外函数中始终有效,所以outter变成了一个有参函数,

      可以对res进行重新赋值改变其return的值。

      对于闭合函数的应用,res可以反复使用而不需要重复对其传参,当参数改变的时侯可以重新对outter进行传参,这种 方便更多的体现在爬虫中(了解):

    import requests
    def outter(url):
        def inner():
            res=requests.get(url)#把url网站请求赋值给res
            print(res.text)
            print(res)
        return inner
    res=outter('https://i.cnblogs.com/EditPosts.aspx?opt=1')
    res()

      res.text代表的就是输出网站所有内容,当不使用以上办法时,需要每次使用时都要对同一函数进行相同的赋值,而闭包函数只需要res即可,对于爬虫来说非常方便。

    二。装饰器

      装饰器就是可以给被装饰的对象添加新的功能。

      装饰器满足开放封闭原则,开放就是对外扩展原则,可以对原先的功能进行扩展,封闭:不能修改原来的函数。

      做成一个装饰器必须要满足两个条件:

      1,不改变被装饰对象的源码。

      2,不改变装饰对象的调用方式。

      复习:外函数返回的值是内函数的内存地址,内存地址+()可执行代码,带着这个概念和两个条件,开始接触装饰器:

      补充:

      inport time 模块,time.time()是时间戳,可以返回现在时间距离1970-1-1日的相差秒数(1970.1.1是unix系统诞生的日子)

      time.sheep(3)

      有一个函数,index,需要判断其程序的运行时间,如何使用装饰器对其进行功能的拓展。

      首先用time模块可以对其进行功能上的实现:

    import time
    
    def index():
        time.sleep(3)
        print('我是登录程序')
    
    time1=time.time()
    index()
    time2=time.time()
    print(time2-time1)
    #输出结果>>>
    #我是登录程序
    #3.0007030963897705

      然而并没有使用函数进行封装,所以使用函数版:

    def get_time():
        time1=time.time()
        index()
        time2=time.time()
        print(time2-time1)
    get_time()

      get_time函数只能对index函数进行功能拓展,所以并没有实现装饰器功能,调用的时候没有使用原来的调用名index,所以,为了达到这一目的,可以使用闭包函数,将get_time()的内存地址返回给外函数,再将外函数的值传给原函数,就可以使用原函数的名字调用get_time函数了。

    def outter():
        def get_time():
            time1=time.time()
            index()
            time2=time.time()
            print(time2-time1)
        return get_time
    res=outter()
    res()

      这样一个针对index的函数的装饰器差不多构成了,但还是不满足调用原函数实现拓展功能的目标,而且这里不能直接将get_time的内存地址赋给内函数传给原函数,否则执行index=get_time()函数时会将get_time里的函数都执行一遍,给外函数就不会出现这个问题。

      对于上代码中只是针对index做的装饰器,换做其他的函数就不可用,所以要将装饰器中的index()当成参数传入,其形参就定义再外函数中被传入。

    def register():
        time.sleep(3)
        print('我是注册程序')
    
    def outter(func):
        def get_time():
            time1=time.time()
            func()
            time2=time.time()
            print(time2-time1)
        return get_time
    res=outter(index)
    index=res
    index()
    register=outter(register)
    register()
    #输出结果>>>我是登录程序
    #3.000358819961548
    #我是注册程序
    #3.0000927448272705

      这样将函数名代表的内存地址传入内函数中,加上()就可以被执行,将外函数返回的内函数内存地址赋值给原函数名,再加()就可以执行,效果和装饰器一致,所以,outter满足了无参函数的装饰器所有规定。

      既然有无参函数,就有有参函数,当有参函数被传入的时后,其参数不能被内函数接受,所以,要装饰有参函数,必须要将参数以某种形式传入,这里选择了可以接受多余参数的*和** 做为形参。

    def print_name(name):
        time.sleep(3)
        print('我是%s'%name)
      return 'name' def outter(func): def get_time(
    *args,**kwargs): time1=time.time() func(*args,**kwargs) time2=time.time() print(time2-time1) return get_time print_name('lzx') print_name=outter(print_name) print_name('lzx') #输出结果>>>我是lzx #我是lzx #3.0001375675201416

      这样的装饰器,有一个小小的问题,当用户需要返回print_name的返回值时,会出现不一样的情况

    print(print_name('lzx'))
    print_name=outter(print_name)
    print_name('lzx')
    print(print_name('lzx'))
    #输出结果>>>我是lzx
    #name
    #我是lzx
    #3.0016050338745117
    #我是lzx
    #3.0001797676086426
    #None

      在装饰器之前打印次函数的返回值时时name原返回值,但是当装饰之后只会返回none,那返回值去哪了返回的是谁的返回值呢,这里需要短暂的分析

      当程序运行时,编译好outter函数后,再将其返回的get_time内存地址返回给print_name也就是原函数,再运行原函数时实际就是运行内函数get_time所以再执行第三步时其实是返回的get_time的返回值,而其中真正的返回值只要函数func拥有,所以为了实现完美装饰器功能,必须将get_name的函数返回值与原函数的返回值一致。

    def register():
        time.sleep(3)
        print('我是注册程序')
    def print_name(name):
        time.sleep(3)
        print('我是%s'%name)
        return 'name'
    
    def outter(func):
        def get_time(*args,**kwargs):
            time1=time.time()
            res=func(*args,**kwargs)
            time2=time.time()
            print(time2-time1)
            return res
        return get_time
    # print(print_name('lzx'))
    print_name=outter(print_name)
    # print_name('lzx')
    print(print_name('lzx'))
    #输出结果>>>我是lzx
    #3.0002002716064453
    #name

      如上便是一个完整功能的装饰器。

      装饰器语法糖

      在每次需要调用装饰器时,都要执行原函数=装饰器(原函数)这样的操作,很麻烦,所以可以使用装饰器语法糖

    def outter(func):
        def get_time(*args,**kwargs):
            time1=time.time()
            res=func(*args,**kwargs)
            time2=time.time()
            print(time2-time1)
            return res
        return get_time
    
    @outter
    def register():
        time.sleep(3)
        print('我是注册程序')
    
    register()
    #输出结果>>>我是注册程序
    #3.000058889389038

      @outter对于register相当于register=outter(register),将紧挨着的那个对象当作参数赋值给它,和上述语法差不多。

       装饰器模板

    def outter(func):
        def inner(*args,**kwargs):
            print('执行被装饰函数之前 你可以做的操作')
            res = func(*args,**kwargs)
            print('执行被装饰函数之后 你可以做的操作')
            return res
        return inner

      这便是装饰器的模板

      有参装饰器

      当装饰器中需要调用额外的参数时,如何将参数传递呢,在内函数中,参数时提供给原函数使用,如果改变,势必要改变原函数,与装饰器原则不符,而外函数的参数是传递原函数,如果增加,在使用语法糖时会报错(参数不足),所以,需要在外函数外再报一个函数:

    def outter2(choose):
        def outter(func):
            def get_time(*args,**kwargs):
                if choose==1:
                    time1=time.time()
                    res=func(*args,**kwargs)
                    time2=time.time()
                    print(time2-time1)
                    return res
                elif choose==2:
                    return 0
            return get_time
        return outter
    
    @outter2(1)
    def register():
        time.sleep(3)
        print('我是注册程序')
    #输出结果>>>我是注册程序
    #3.0003597736358643

      语法糖是可以传入参数的,当传如1时会执行语句,传入0 时什么都不执行,这个参数可以传入多个,所有多余参数都可以写入。

      装饰器修复:

      当用户对被装饰后的函数去内存地址和备注时,并不会返回原来的内存地址,而是返回内函数的,所以为了以假乱真,可以使用from functools import wraps,再再外函数里使用@wrap(func)语句就可以返回原来的内存地址及注释

    from functools import wraps
    def outter2(choose):
        def outter(func):
            @wraps(func)
            def get_time(*args,**kwargs):
                if choose==1:
                    time1=time.time()
                    res=func(*args,**kwargs)
                    time2=time.time()
                    print(time2-time1)
                    return res
                elif choose==2:
                    return 0
            return get_time
        return outter

      多个装饰器

       当多个装饰器装饰一个函数时他们的顺序是规定 的

    @outter1  # index = outter1(wapper2)
    @outter2  # wrapper2 = outter2(wrapper3)
    @outter3  # wrapper3 = outter3(最原始的index函数内存地址)
    def index():
        print('from index')

      其执行顺序是

    加载了outter3

    加载了outter2

    加载了outter1

    执行了wrapper1

    执行了wrapper2

    执行了wrapper3

    from index  

      所以它们的执行规则应该是装饰顺序为从下往上,执行顺序为从上往下。

  • 相关阅读:
    cookie,请求报文,
    ser,ver
    关于 通知的 死循环,
    这读取的好蛋疼,为什么一写 一读就有问题了,不一致了,
    缓存小姐 挡拆,网络请求 不同步 惹的祸,
    viewdidload ,viewwillappear,
    提示输入 用户名 ,密码,--》转
    前端面试
    npm与cnpm
    vue与node和npm关系
  • 原文地址:https://www.cnblogs.com/LZXlzmmddtm/p/11171808.html
Copyright © 2020-2023  润新知