zoukankan      html  css  js  c++  java
  • python中的闭包与装饰器

    #原创,转载请留言联系

    装饰器的本质就是闭包,所以想知道装饰器是什么,首先要理解一下什么是闭包。

    • 闭包

    1. 外部函数返回内部函数的引用。
    2. 内部函数使用外部函数的变量或者参数。

    def outer(outer_num):
        def inner(inner_num):
            a = outer_num+inner_num
            print(a)
        return inner
    
    f1 = outer(1)
    f1(2)
    
    输出:
    3

    1.outer函数返回inner函数的引用,f1=outer(1),实质就是f1=inner,而且还有,它会传入一个变量outer_num=1。这个变量是outer函数的局部变量,但是他是inner函数的“全局变量”。所以这个变量在执行完f1=outer(1)后,并不会被回收。(我们都知道普通函数里面的变量在运行完函数后会被回收。全局变量不会。)

    2.inner函数使用了outer函数的变量。

    所以上面那个是一个闭包!

    拓展:如果要在内部函数修改外部函数的变量,不是用global,而是用nonlocal。

    def outer(num):
        def inner():
            nonlocal num
            num += 1
            print(num)
        return inner
    
    f1 = outer(1)
    f1()
    f1()
    f1()
    
    输出:
    2
    3
    4

    说了这么多,闭包有什么用处呢?

    当你做一个项目时,当已经实现功能的代码不允许修改了!但是又想添加新功能时,这时候闭包的作用就出来了!

    假设有一个这样的基础函数:

    def transer():
        """实现转账的函数"""
        print("正在转账...")

    这是一个实现转账功能的函数,但是还没有身份验证等步骤,我们需要完善这个项目。但是又不能修改这个函数的源代码,这时候应该怎么办呢?没错,就是闭包。

    def transer():
        """实现转账的函数"""
        print("正在转账...")
    
    def outer(func):
        def vifi():
            """实现身份验证的函数"""
            print("正在验证身份...")
            func()
        return vifi
    
    f1 = outer(transer)
    f1()
    
    输出:
    正在验证身份...
    正在转账...

    完美解决!

    上面的代码又可以稍微更改成这样:

    def outer(func):
        def vifi():
            """实现身份验证的函数"""
            print("正在验证身份...")
            func()
        return vifi
    
    @outer    # 等价于outer(transer)
    def transer():
        """实现转账的函数"""
        print("正在转账...")
    
    transer()
    
    输出:
    正在验证身份...
    正在转账...

    @outer就是装饰器!在不改变函数的定义和调用的前提下,给函数扩展功能,这就是装饰器。

    ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    上面装饰器装饰的函数没有接收参数,也没有返回值。当装饰的函数接收情况或者有返回值时怎么操作呢?

    1.当被装饰的函数接收参数没有返回值的情况,装饰器应该怎么改?

    def outer(func):
        def inner(num):  # 这里要写num形参
    """我是装饰的功能"""
    func(num) # 这里也要写num形参 return inner @outer def normal(num):
    """我是被装饰的函数""" num
    += 1 print(num) normal(5) 输出: 6

    2.当被装饰的函数没有接收参数返回值的情况,装饰器应该怎么改?

    def outer(func):
        def inner():
    """我是装饰的功能"""
    return func() # 这里要return函数,return的值返回给inner函数,才能接受到。 return inner @outer def normal():
    """我是被装饰的函数"""
    return "我是返回值" result = normal() print(result) 输出: 我是返回值

    3.当被装饰的函数既接受参数又返回值的情况,装饰器应该怎么改?

    def outer(func):
        def inner(num):
    """我是装饰的功能"""
    return func(num) # 前面两者综合 return inner @outer def normal(num):
    """我是被装饰的函数""" num
    += 1 print(num) return "我是返回值" result = normal(5) print(result) 输出: 6 我是返回值

    综合上面各种情况,我们可以写一个万能装饰器,这样的话,就不用根据被装饰函数有没接收参数,有没返回值,而改来改去了。

    def outer(func):
        def inner(*args,**kwargs):
            """我是装饰的功能"""
            result = func(*args,**kwargs)
            return result
        return inner
    
    @outer
    def normal(*args,**kwargs):
        """我是被装饰的函数"""
        print(args,kwargs)
        return 'something'
    
    i = normal(5)
    print(i)
    
    输出:
    (5,) {}
    something

    这个万能装饰器,无论被装饰函数有没接收参数,有没返回值都可以正常运行了!

     ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    当有多个装饰器装饰一个函数的时候,装饰的顺序是怎样的呢?

    def w1(func):
        def inner():
            return '<b>'+func()+"</b>"
        return inner
    
    def w2(func):
        def inner():
            return '<t>' + func() + "</t>"
        return inner
    
    @w1
    @w2
    def transer():
        return 'hello-world'
    
    
    print(transer())
    
    输出:
    <b><t>hello-world</t></b>

    可以看出,虽然执行顺序是从外到里,但是装饰的顺序是从里到外的。

    ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    如果我们有一个需求,希望装饰函数时额外传一个参数flag,当这个参数是1时,就进行装饰。如果是0时,就不进行装饰。应该怎么操作呢?

    有一个错误的做法经常很多人犯,如下:

    def outer(func):
        def vifi(flag):
            """实现身份验证的函数"""
            if flag == 1:
                print("正在验证身份...")
            func()
        return vifi
    
    @outer
    def transer():
        """实现转账的函数"""
        print("正在转账...")
    
    transer(1)
    print("
    ")
    transer(0)
    
    输出:
    正在验证身份...
    正在转账...
    
    
    正在转账...

    诶???结果不是正确了吗?不是实现这个功能了吗?但是,装饰器的定义是,在不改变函数的定义和调用的前提下,给函数扩展功能!

    这样的写法,不是已经改变了函数的调用了吗?transer函数本来没有形参的,然后你无缘无故给他一个参数......

    正确的写法应该是这样!通过装饰器工厂实现对装饰器传递额外的参数!

    def outouter(flag):
        def outer(func):
            def vifi():
                """实现身份验证的函数"""
                if flag == 1:
                    print("正在验证身份...")
                func()
            return vifi
        return outer
    
    @outouter(1)
    # @outouter(0)
    def transer():
        """实现转账的函数"""
        print("正在转账...")
    
    transer()
    
    输出:
    正在验证身份...
    正在转账...

    正确的做法是在闭包再加一层。用来接受参数。这种做法就叫装饰器工厂!

    ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    类装饰器。

    不仅仅函数能写装饰器,类也可以,但是不常用。

    class Foo(object):
        def __init__(self,func):
            self.func = func
        def __call__(self, *args, **kwargs):
            print("正在验证身份...")
            self.func()
    
    
    """Foo本质是transer=Foo(transer)。
    左边的transer是调用__call__魔方方法。
    右边的是创建对象,把transer这个函数名传进去,__call__魔方方法才能使用"""
    @Foo
    def transer():
        print("正在转账...")
    
    transer()
    
    输出:
    正在验证身份...
    正在转账...

    ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    说在最后:

    装饰器是Python中一个很重要的东西,经常会用到,而且还意想不到的好用。

    装饰器常应用于:

    1. 引入日志
    2. 函数执行时间统计
    3. 执行函数前预备处理
    4. 执行函数后清理功能
    5. 权限校验等场景
    6. 缓存
  • 相关阅读:
    windows 2008 r2 开启互访和网络发现
    uchome 模拟发布动态和通知遇到的问题
    远程无法连接win2003的mssql2000服务器
    cidaemon.exe进程占用CPU资源的解决办法
    asp.net如何生成图片验证码
    SQL Server中截取日期型字段的日期部分和时间部分
    刷新项目失败。无法从服务器中检索文件夹信息。
    CS0016: 未能写入输出文件“c:\WINDOWS\Microsoft.NET\***.dll”错误处理
    Computer Browser服务启动后自动停止
    FCK使用 体会
  • 原文地址:https://www.cnblogs.com/chichung/p/9623081.html
Copyright © 2011-2022 走看看