Python之 闭包和装饰器

Python闭包

什么是闭包?

# 定义一个函数
def test(number):

    # 在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量称之为闭包
    def test_in(number_in):
        print("in test_in 函数, number_in is %d" % number_in)
        return number+number_in
    # 其实这里返回的就是闭包的结果
    return test_in


# 给test函数赋值,这个20就是给参数number
ret = test(20)

# 这里的100其实给参数number_in
print(ret(100))

# 这里的200其实给参数number_in
print(ret(200))

运行结果:

in test_in 函数, number_in is 100
120
in test_in 函数, number_in is 200
220

将组成函数的语句和这些语句的执行环境打包在一起时,得到的对象称为闭包。(说人话)在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量合起来称之为闭包。

如果需要在一系列函数调用中保持某个状态,使用闭包是一种非常高效的方式。举个栗子:

def countdown(n):
    def next():
        # global适用于函数内部修改全局变量的值
        # nonlocal适用于嵌套函数中内部函数修改外部变量的值
        nonlocal n
        r = n
        n -= 1
        return r
    return next

next = countdown(5)
while True:
    v = next()
    print(v, end=" ")
    if not v:
        break

运行结果:

5 4 3 2 1 0 

在这段代码中,闭包用于保存内部计数器的值n,每次调用内部函数next()时,它都更新并返回这个计数器变量的前一个值。

Python装饰器

写代码要遵循开放封闭原则,虽然在这个原则是用的面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:

  • 封闭:已实现的功能代码块
  • 开放:对扩展开发

如果要在已经写好的程序上添加一些验证或者修饰内容怎么办?
这就需要装饰器了!!!

def makeBold(fn):
    def wrapped():
        return "<b>" + fn() + "</b>"
    return wrapped

def makeItalic(fn):
    def wrapped():
        return "<i>" + fn() + "</i>"
    return wrapped

# 如果对于@语法糖不理解的话,下面就是装饰器的执行过程
# def test1():
#    return "hello world-1"
# test1 = makeBlod(test1)
@makeBold
def test1():
    return "hello world-1"

@makeItalic
def test2():
    return "hello world-2"

@makeBold
@makeItalic
# 装饰器是有顺序的,由内到外的执行顺序,
# 即先执行@makeItalic,包装返回结果<i>hello world-3</i> 
# 再执行@makeBold,包装返回结果<b><i>hello world-3</i></b>  
def test3():
    return "hello world-3"

print(test1())
print(test2())
print(test3())

运行结果:

<b>hello world-1</b>
<i>hello world-2</i>
<b><i>hello world-3</i></b>  

装饰器(decorator)功能

  • 引入日志
  • 函数执行时间统计
  • 执行函数前预备处理
  • 执行函数后清理功能
  • 权限校验等场景
  • 缓存

装饰器示例

1.被装饰的函数无参数, 无返回值

def decroate_fun(func):
    def wrapper():
        print("函数%s 开始运行" % (func.__name__))
        # 装饰器进行装饰的时候就是在这里将func()换成被装饰的函数内容
        # 不如下面的foo函数被装饰,就将foo的内容 print("I am foo")替换到这里
        func()
        print("函数%s 结束运行" % (func.__name__))
        print("------------------------------------")
    return wrapper

@decroate_fun
def foo():
    print("I am foo")

foo()

运行结果:

函数foo 开始运行
I am foo
函数foo 结束运行
------------------------------------

2.被装饰的函数有参数无返回值

def decroate_fun(func):
    def wrapper(*args, **kwargs):
        print("函数%s 开始运行" % (func.__name__))
        func(*args, **kwargs)
        print("函数%s 结束运行" % (func.__name__))
        print("------------------------------------")
    return wrapper

@decroate_fun
def foo():
    print("I am foo")

@decroate_fun
def foo2(a, b):
    print("a + b =", a + b)

foo()
foo2(2, 3)

运行结果:

函数foo 开始运行
I am foo
函数foo 结束运行
------------------------------------
函数foo2 开始运行
a + b = 5
函数foo2 结束运行
------------------------------------

3.被装饰的参数有参数,有返回值

from time import time


def time_fun(func):
    """测试程序运行时间的装饰器"""
    def wrapper(*args, **kwargs):
        # 记录程序开始的时间
        start = time()
        # 对于装饰有返回值的函数,很多人的第一反应是在这里return func(*args, **kwargs)
        # 但是像测试运行时间这样的装饰器,需要在函数执行完毕后还要执行一步,所以取个变量result 记录函数返回值
        result = func(*args, **kwargs)
        # 计算函数运行时间,并显示
        print("函数%s 运行时间为%s" % (func.__name__, time()-start))
        print("------------------------------------")
        return result
    return wrapper

@time_fun
def foo():
    print("I am foo")

@time_fun
def foo2(a, b):
    print("a + b =", a + b)

@time_fun
def foo3(a, b):
    print("现在在foo3里面")
    return a * b


foo()
foo2(2, 3)
ret = foo3(3, 4)
print(ret)

运行结果:

I am foo
函数foo 运行时间为3.9577484130859375e-05
------------------------------------
a + b = 5
函数foo2 运行时间为5.9604644775390625e-06
------------------------------------
现在在foo3里面
函数foo3 运行时间为2.6226043701171875e-06
------------------------------------
12

4.装饰器有参数

当我们需要添加不同的装饰内容,但是格式相同的时候,因为我们不能为每个内容写一个装饰器,这时就需要另外一种方法——装饰器工厂——给装饰器传参数。

"""
有一项任务,要求函数运行时间测试函数要求记录是谁测的
比如: 输出结果:[Jack测试:]函数foo3 运行时间为1秒
"""

from time import time


def timefunc_factory(name):
    def time_fun(func):
        """测试程序运行时间的装饰器"""
        def wrapper(*args, **kwargs):
            # 记录程序开始的时间
            start = time()
            # 对于装饰有返回值的函数,很多人的第一反应是在这里return func(*args, **kwargs)
            # 但是像测试运行时间这样的装饰器,需要在函数执行完毕后还要执行一步,所以取个变量result 记录函数返回值
            result = func(*args, **kwargs)
            # 计算函数运行时间,并显示
            print("[%s测试:]函数%s 运行时间为%s秒" % (name, func.__name__, time()-start))
            print("------------------------------------")
            return result
        return wrapper
    return time_fun


@timefunc_factory("Jack")
def foo4(a, b):
    print("现在在foo4里面")
    return a * b

result = foo4(2, 4)
print(result)

运行结果:

现在在foo3里面
[Jack测试:]函数foo4 运行时间为2.2649765014648438e-05秒
------------------------------------
8

注意点:

# 定义一个简单的装饰器
def decroate_func(func):
    def wrapper():
        pass
    return wrapper

@decroate_func
def test1():
    pass

@decroate_func
def test2():
    pass

# 打印函数test1和test2的名字
print(test1.__name__)
print(test2.__name__)

运行结果:

wrapper
wrapper

不难发现,当装饰器decroate_func装饰函数时,会将函数的名字(__name__)以及其他属性(__doc__等)修改为装饰器中的内部函数wrapper函数的相关属性。当多个函数被修饰时,会造成函数的调用出现二义性(即有好多个wrapper函数,程序不知道要调用哪个函数)。

解决方法一(只作为了解,不建议使用):

根据自己的需求,手动修改(__name__)等属性

def decroate_func(func):
    def wrapper():
        pass
    # 在装饰器返回内部函数之前,将wrapper的__name__属性修改为func的__name__
    # 其他属性也是这样修改
    wrapper.__name__ = func.__name__
    return wrapper

@decroate_func
def test1():
    pass

@decroate_func
def test2():
    pass

print(test1.__name__)
print(test2.__name__)

运行结果:

test1
test2

解决方法二:

使用Python自带的functools模块中定义的@wraps(func)装饰器可以将属性从func传递给要定义的包装器函数。
这种方法将所有的属性都进行了修改,操作简单,而且不用担心哪个属性忘记修改了

from functools import wraps

def decroate_func(func):
    @wraps(func)
    def wrapper():
        pass
    return wrapper

@decroate_func
def test1():
    pass

@decroate_func
def test2():
    pass

print(test1.__name__)
print(test2.__name__)

运行结果:

test1
test2
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值