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  

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

原文地址:https://www.cnblogs.com/LZXlzmmddtm/p/11171808.html