zoukankan      html  css  js  c++  java
  • Python函数篇(5)-装饰器及实例讲解

    1.装饰器的概念

      装饰器本质上就是一个函数,主要是为其他的函数添加附加的功能,装饰器的原则有以下两个:

    • 装饰器不能修改被修饰函数的源代码
    • 装饰器不能修改被修改函数的调用方式  

      装饰器可以简单的理解为:高阶函数+嵌套函数+闭包


    2.高阶函数

      高阶函数我在前面的博客中已经讲过了,在这里我再简单的说一下吧。
      高阶函数:如果一个函数接收的参数是一个函数名,或者返回值是函数名,只要满足任意一个条件,这个函数就称为高阶函数。

    • 接收的参数是一个函数名
    def foo():
        print("the result from foo")
    def bar(func):
        func()
        print("the result from bar")
    bar(foo)  
    

      在上面的例子中,我定义了两个函数,foo()和bar(),在调用bar()函数的时候,我将foo()作为一个参数传给了bar(),运行就会得到foo()的结果,这样的函数就可以成为高阶函数。

    • 返回值是一个函数名
    def foo():
        print("the result from foo")
    def bar(func):
        return func
    bar(foo)
    

      调用bar()函数 返回的就是foo()的内存地址,这也可以称为高阶函数。


    3.函数嵌套

      函数嵌套:函数嵌套我在前面也已经讲过了,其实就是函数的内部再声明函数,很好理解的一个概念,举个例子:如下

    def foo():
        print("the result from foo")
        def bar():
            print("the result from bar")
        bar()
    foo()
    

      在上面这个例子中,我在函数foo()的内部又写了一个bar()函数,并在下面调用了该函数,这种方式就叫做函数嵌套,可以嵌套很多层,只要注意函数缩进问题


    4.闭包

      在Python中闭包的表现形式可以理解为:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。
      单从上面的定义可能很难理解,我下面用一个简单的程序说明一下:

    def foo():
        a=1
        b=2
        def bar():
            c=3
            return a+b+c
        return bar
    print(foo()())
    

      在上面这个例子中,bar()就是foo()的一个内部函数,在bar()的局部作用于可以直接使用foo()的局部变量a,b,简单的说,这种内部函数可以访问外部函数变量的行为,就叫做闭包。


    4.装饰器实例

      为了能让大家更好的理解装饰器,我会分步做出这个实例。

    • 装饰器的基本实现

      先定义一个函数name(),3秒后打印名字

    import time
    def name():
        time.sleep(2)
        print("my name is 尼古拉斯赵四")
    name()
    

      现在有一个需求,我想要统计这个函数一个运行了多少秒,在不修改源代码的情况下,就需要给这个函数写一个装饰器来完成这个需求。

    def timmer(func):       #定义一个形参,就是为了接受name()这个函数,注意前面文章就已经强调过的:函数即变量---func=name
        def wapper():       #定义函数wapper(),用来接收name的参数
            start_time=time.time()
            func()          #实质上就是在运行test()       
            stop_time=time.time()
            print("name()函数一共运行了%s 秒"%(stop_time-start_time))
        return wapper       
    
    import time
    @timmer             #使用装饰器的方法,通过@+作为装饰器的那个函数名
    def name():
        time.sleep(2)
        print("my name is 尼古拉斯赵四")
    name()
    运行结果:
    my name is 尼古拉斯赵四
    name()函数一共运行了2.0002381801605225 秒
    

      上面这个例子就是一个简单的装饰器,没有修改原函数的调用方法和返回值,装饰器timmer中用到了高阶函数+函数嵌套+闭包的知识,ok,完美。

    • 在装饰器中添加参数
        上面的例子是完美的实现了所需要的功能,但是问题来了,如果原函数是现在这样呢?
    def name(my_name,my_age):
        time.sleep(2)
        print("my name is %s,my age is %s"%(my_name,my_age))
    name("尼古拉斯赵四",18)
    

      我需要随机传入两个值,打印出他的姓名和年龄,如果原函数这样调用,使用上面的装饰器肯定会出错,那就需要在装饰器函数中做如下修改:def wapper(my_name,my_age)func(my_name,my_age)这两行加入相同的参数,也是可以的,但如果name函数我再修改呢,不传入两个参数了 ,传3个 或更多,每次都要去修改岂不是很麻烦,这就需要做一些改变了 。

    def timmer(func):      
        def wapper(*args,**kwargs):         #这里用*args,**kwargs代替,这样 ,不管原函数传入多少个参数,都可以匹配
            start_time=time.time()
            func(*args,**kwargs)             #同样,接受任意多个参数 (如果不懂这个什么意思,翻看我前面函数篇的博客,有讲到)
            stop_time=time.time()
            print("name()函数一共运行了%s 秒"%(stop_time-start_time))
        return wapper
    

    • 装饰器添加返回值
        参数问题解决了,下面我的原函数又变了
    def name(my_name,my_age):
        time.sleep(2)
        print("my name is %s,my age is %s"%(my_name,my_age))
        return "尼古拉斯  你真年轻"
    print(name("尼古拉斯赵四",18))
    运行结果:
    my name is 尼古拉斯赵四,my age is 18
    尼古拉斯  你真年轻
    

      在这个函数中,我需求是在运行完函数返回"尼古拉斯 你真年轻"这句话,还是用上面的装饰器返回值会是my name is 尼古拉斯赵四,my age is 18 name()函数一共运行了2.000795364379883 秒 None,返回值是None而不是想要的结果,大家可以试一下,所以要加如下修改:

    def timmer(func):
        def wapper(*args,**kwargs):         
            start_time=time.time()
            res=func(*args,**kwargs)            #将func()运行结果赋值给变量res
            stop_time=time.time()
            print("name()函数一共运行了%s 秒"%(stop_time-start_time))
            return res                          #返回res,其实就是在返回name()
        return wapper
    

      上面就详细写了装饰器的实现过程,就先写到这,后续我会丰富装饰器并做出一个通俗易懂的例子供大家参考,稍后一些时间也会发布在我的博客里,感兴趣的到时候可以看一下,希望可以帮助大家对装饰器有更好的理解。

  • 相关阅读:
    按单生产案例
    【转】linux中执行外部命令提示" error while loading shared libraries"时的解决办法
    【转】WARNING! File system needs to be upgraded. You have version null and I want version 7. Run the '${HBASE_HOME}/bin/hbase migrate' script. 的解决办法
    根据Rowkey从HBase中查询数据
    【转】在一个Job中同时写入多个HBase的table
    sqoop 使用
    给VMware下的Linux扩展磁盘空间(以CentOS6.3为例)
    chrome 版本 29.0.1547.76 m 解决打开新标签页后的恶心页面的问题
    tomcat7+jdk的keytool生成证书 配置https
    如何打包和生成你的Android应用程序
  • 原文地址:https://www.cnblogs.com/Chen-Zhipeng/p/8035938.html
Copyright © 2011-2022 走看看