zoukankan      html  css  js  c++  java
  • 8、Python之装饰器

    一、装饰器

         首先我们先从装饰器的定义来理解什么是装饰器,装饰器就是为一个函数添加额外的功能,但必须满足两个条件1、不改变函数的源代码 2、不改变函数的调用方式。举个栗子,你是一个帅气的小伙子,然后我给你带个耳钉,穿上一件漂亮的旗袍,再带个假发。这时候的你看上去就像个女人,我没有本质上改变你(不改变函数的源代码)(如果你去泰国的话,本质上就改变了),这时候有人叫你的名字你也会答应(不改变函数的调用方式),这样的一个过程就称为装饰,具有这种装饰其它函数的函数就叫做装饰器。

        通过上面的定义,我们总结装饰器的特性如下:

    • 装饰器本质上就是一个函数
    • 装饰器不改变装饰函数的源代码
    • 装饰器不改变装饰函数的调用方式

    二、忘记是为了更好的相遇

        让我们先忘记什么是装饰器吧,我们先定义一个函数:

    1 def fun_01(x):           #定义函数fun_01
    2     print("print %s in fun_01")
    3 
    4 fun_01(10)  #调用函数fun_01

    上面的程序分为两部分,一部分为fun_01函数的定义,另一部分为fun_01函数的调用。下面我们定义一个这样的函数:

    1 import  time
    2 def deco(f):
    3     print("当前系统时间%s" % time.time())
    4     return f

    这是一个高阶函数,形参需要传递一个函数“变量”(函数名即变量,函数体即变量值)然后打印出系统当前时间,最后将这个“变量”返回。下面我们编写以下代码:

    1 def fun_01(x):           #定义函数fun_01
    2     print("print %s in fun_01"% x)
    3 
    4 fun_01 = deco(fun_01)  #调用函数fun_01
    5 fun_01(10)

    执行结果为当前系统时间Fri Jan 26 11:29:19 2018    print 10 in fun_01

    我们先通过一个图来理解一下程序的运行过程:

    程序一定是分2个部分:定义执行,上图展示了定义两个函数在内存中的存储形式。当执行fun_01 = deco(fun_01)时,fun_01的值作为实参传递给deco函数的形参,这边值得需要说一下的是:deco是“变量”而deco()就是执行函数

    所以deco(fun_01)就是把fun_01的值传递给deco(f)中的f然后执行函数。打印出当前系统时间Fri Jan 26 11:29:19 2018,接着函数将f返回,实际上f=fun_01,所以搞了一圈fun_01里面的值压根没变。这时候我们重新回忆一下装饰器的

    定义:1、不修改函数的源代码(没有修改fun_01函数的代码)2、不改变函数的调用方式(调用方式还是fun_01(10))。当然多了一行fun_01 = deco(fun_01)请先忘记它吧!这样看上去貌似deco就是一个装饰器嘛!too young too simple!

    如果我想给fun_01装饰这样一个功能,就是统计执行fun_01方法花了多长时间,怎么搞?是不是傻了?没关系,且听我娓娓道来!

          我现在将deco这个方法稍微改造一下:

     1 def timer(f):
     2     def deco(x):
     3         before = time.time()
     4         f(x)
     5         after = time.time()
     6         print("执行函数%s一共用了%s的时间" % (f,after-before))
     7     return deco
     8 
     9 def fun_01(x):           #定义函数fun_01
    10     print("print %s in fun_01"% x)
    11 
    12 fun_01 = timer(fun_01)  #调用函数fun_01
    13 fun_01(10)

     我给deco函数外面又加了一个函数timer,timer函数是一个嵌套函数,timer函数返回一个deco的“变量”。然后我们再次运行程序,结果为

    print 10 in fun_01

    执行函数<function fun_01 at 0x0399C108>一共用了0.0的时间

     下面我只能用我强大的绘图能力来描述整个事件的发生过程了。

    执行完fun_01 = timer(fun_01)语句后fun_01指向了新的代码块,然后执行fun_01(10)语句,执行fun_01指向的的那段代码,即为我看到执行结果。到此为止,一个新的术语正式诞生:装饰器。timer就是一个装饰器,同时我们先发现装饰器其实就是由:高阶+嵌套组成的特殊函数。python中对于装饰器的调用有专门的语法,及在装饰函数定义前面加上@装饰器,将上面的代码修改如下:

     1 def timer(f):
     2     def deco(x):
     3         before = time.time()
     4         f(x)
     5         after = time.time()
     6         print("执行函数%s一共用了%s的时间" % (f,after-before))
     7     return deco
     8 @timer
     9 def fun_01(x):           #定义函数fun_01
    10     print("print %s in fun_01"% x)
    11 
    12 fun_01(10)

    两段代码的区别在于使用@timer替代了fun_01 = timer(fun_01),其实@timer对于python解释器而言,就是执行的fun_01 = timer(fun_01)语句。因此,如果我们要装饰某个函数时,先写一个装饰这个函数的装饰器,然后再这个函数的定义前面加上@装饰器,其他地方无需改动,这时候被装饰的函数就具有了新的功能。

    三、更好的装饰器

        有时候我们在给一类函数装饰的时候,可能会根据情况进行装饰,比如同时给连接oracle数据库的方法和连接mysql数据库的方法写一个装饰器,我们可能会首先判断这个方法是连接的oracle数据库还是mysql,然后再决定如何打印日志。

    这时候我们需要写一个更加牛B的装饰器,代码如下(但愿你能理解):

     1 import  time
     2 def logger(method):
     3     def deco(f):
     4         def innerdeco(*args,**kwargs):
     5             if method == "mysql":
     6                 print("mysql-调用的是%s的方法" % f)
     7             elif method == "oracle":
     8                 print("oracle-调用的是%s的方法" % f)
     9             f(*args, **kwargs)
    10         return innerdeco
    11     return deco
    12 
    13 @logger(method="mysql")
    14 def connectMysql(ip,port): #连接mysql
    15     print("连接mysql数据库,ip为%s,端口为%s" % (ip,port))
    16 @logger(method="oracle")
    17 def connectOracle(ip,port,service): #连接service
    18     print("连接oracle数据库,ip为%s,端口为%s ,服务名为%s" % (ip,port,service))
    19 
    20 
    21 # p = connectMysql
    22 # connectMysql = logger("mysql")
    23 # connectMysql = connectMysql(p)
    24 connectMysql("192.168.12.1",8080)
    25 connectOracle("192.168.12.1",8080,"testmu")

    如果这段代码你能看明白,说明你对装饰器已经了如指掌了。代码中21,22,23行的代码等价于@logger(method="mysql")。

  • 相关阅读:
    Ubuntu安装配置samba
    AFNetworking 和 ASIHTTPRequest
    github代码托管
    Java使用poi包读取Excel文档
    Eclipse-设置保存时自动给变量加final
    Eclipse-设置格式化代码时不格式化注释
    Map的内容按字母顺序排序
    Mysql--mysqldump命令 备份数据库
    Mysql--alter命令
    Java IO 文件
  • 原文地址:https://www.cnblogs.com/win0211/p/8359289.html
Copyright © 2011-2022 走看看