python 装饰函数 和 闭包 基础总结。

一、 装饰函数

(1)概念:

装饰器本质上是一个 Python 函数或类,它可以让其他函数或类在不需要做任何代码修改的前提下增加额外功能,装饰器的返回值也是一个函数/类对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景,装饰器是解决这类问题的绝佳设计。有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码到装饰器中并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能

使用方法:

  1. 先定义一个装饰器(帽子)
  2. 再定义你的业务函数或者类(人)
  3. 最后把这装饰器(帽子)扣在这个函数(人)头上

(2)实例:

  1. 日志打印器
# 这是装饰器函数,参数 func 是被装饰的函数
def logger(func):
    def wrapper(*args, **kw):
        print('主人,我准备开始执行:{} 函数了:'.format(func.__name__))

        # 真正执行的是这行。
        func(*args, **kw)

        print('主人,我执行完啦。')
    return wrapper
@logger  # =》 add = logger(add)
def add(x, y):
    print('{} + {} = {}'.format(x, y, x+y))

执行:
在这里插入图片描述
2. 时间计时器

# 这是装饰函数
def timer(func):
    def wrapper(*args, **kw):
        t1=time.time()
        # 这是函数真正执行的地方
        func(*args, **kw)
        t2=time.time()

        # 计算下时长
        cost_time = t2-t1 
        print("花费时间:{}秒".format(cost_time))
    return wrapper
import time

@timer
def want_sleep(sleep_time):
    time.sleep(sleep_time)

want_sleep(10) #花费时间:10.000298261642456秒
  1. 带参数的函数装饰器
def say_hello(contry):
    def wrapper(func):
        def deco(*args, **kwargs):
            if contry == "china":
                print("你好!")
            elif contry == "america":
                print('hello.')
            else:
                return

            # 真正执行函数的地方
            func(*args, **kwargs)
        return deco
    return wrapper

# 小明,中国人
@say_hello("china")
def xiaoming():
    pass

# jack,美国人
@say_hello("america")
def jack():
    pass

python 装饰函数 和 闭包 基础总结。_第1张图片
4. 不带参数的类装饰器
基于类装饰器的实现,必须实现 call 和 __init__两个内置函数。
init :接收被装饰函数
call :实现装饰逻辑。

class logger(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print("[INFO]: the function {func}() is running..."\
            .format(func=self.func.__name__))
        return self.func(*args, **kwargs)

@logger
def say(something):
    print("say {}!".format(something))

say("hello")#[INFO]: the function say() is running...
            #say hello!
  1. 带参数的类装饰器
    上面不带参数的例子,你发现没有,只能打印INFO级别的日志,正常情况下,我们还需要打印DEBUG WARNING等级别的日志。这就需要给类装饰器传入参数,给这个函数指定级别了。

带参数和不带参数的类装饰器有很大的不同。

init :不再接收被装饰函数,而是接收传入参数。
call :接收被装饰函数,实现装饰逻辑。

class logger(object):
    def __init__(self, level='INFO'):
        self.level = level

    def __call__(self, func): # 接受函数
        def wrapper(*args, **kwargs):
            print("[{level}]: the function {func}() is running..."\
                .format(level=self.level, func=func.__name__))
            func(*args, **kwargs)
        return wrapper  #返回函数

@logger(level='WARNING')
def say(something):
    print("say {}!".format(something))

say("hello")
#[WARNING]: the function say() is running...
#say hello!

  1. 内置装饰器:property

  1. 如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数。(即再嵌套一个decorator函数):
def log(text):
    def decorator(func):
        @functools.wraps(func)#把原始函数的__name__等属性复制到wrapper()函数中
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

@log('execute')  #now = log('execute')(now)
def now():
    print('2015-3-25')

now()
#execute now():
#2015-3-25

首先执行log(‘execute’),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数。

二、闭包

(1)概念:

在一个内部函数中,对外部作用域的变量进行引用,(并且一般外部函数的返回值为内部函数),那么内部函数就被认为是闭包。
维基百科上的解释是:

在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,
是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,
即使已经离开了创造它的环境也不例外。
所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。

在这里插入图片描述

add访问了外部函数start的变量,并且函数返回值为add函数(python可以返回函数
python 装饰函数 和 闭包 基础总结。_第2张图片
闭包,顾名思义,就是一个封闭的包裹,里面包裹着自由变量,就像在类里面定义的属性值一样,自由变量的可见范围随同包裹,哪里可以访问到这个包裹,哪里就可以访问到这个自由变量。

再通过Python的语言介绍一下,一个闭包就是你调用了一个函数A,这个函数A返回了一个函数B给你。这个返回的函数B就叫做闭包。你在调用函数A的时候传递的参数就是自由变量(当函数A的生命周期结束之后,自由变量依然存在,因为它被闭包引用了,所以不会被回收。)。

(2)常见问题

  1. 闭包无法修改外部函数局部变量(即add函数无法修改start函数定义的变量)

  2. 闭包使得局部变量函数外被访问成为可能

  3. 闭包避免了使用全局变量

  4. 闭包允许将函数与其所操作的某些数据(环境)关连起来。

  5. 装饰器就是一种的闭包的应用,只不过其传递的是函数

  6. 闭包的最大特点是可以将父函数的变量与内部函数绑定,并返回绑定变量后的函数(也即闭包)。(类似类)

  7. python循环中不包含域的概念。

python 装饰函数 和 闭包 基础总结。_第3张图片
loop在python中是没有域的概念的,flist在向列表中添加func的时候,并没有保存i的值,而是当执行f(2)的时候才去取,这时候循环已经结束,i的值是2,所以结果都是4。

解决办法:
在func外面再定义一个makefun函数,func形成闭包。python 装饰函数 和 闭包 基础总结。_第4张图片
参考:
https://foofish.net/python-closure.html
https://zhuanlan.zhihu.com/p/22229197

你可能感兴趣的:(python)