Python进阶话题杂谈(十四)装饰器

装饰器是Python中最难理解的语法之一,但较之其他冷门语法又相对较常用。但有必要指出的是,这里所说的较常用,指的是Python自带的一些装饰器,如@property、@abstractmethod等,自定义装饰器在实际中是较少使用的。

1 高阶函数

函数,作为一段代码的抽象,其本质上也对应于一个表示函数代码起点的内存地址。每当调用一个函数时,程序挂起并跳转到函数所在的地址开始执行,结束后再退回到调用点,继续执行接下来的语句。由此可见,就数据结构而言,函数与数组的原理类似,可以用一个指针来保存函数的起始位置。故在C语言中有“函数指针”类型,且由于函数可以用指针来传递,函数指针就可以作为另一个函数的参数,从而出现了“以函数作为参数的函数”,这样的函数就称为高阶函数。

在Python中,由于对象引用的存在,函数也同样作为一类对象(函数对象),可以通过其引用传递,故在Python中将函数作为参数传递,与其他数据结构的传递是一样的。Python中就有一些常用的高阶函数,如sort、max、min等带有key参数的函数,map、reduce、filter这样的带有函数参数的函数等等。这样的函数都以函数作为参数,并在函数内部调用传入的函数参数。

2 无参装饰器

Python的装饰器被定义为一个高阶函数,这个高阶函数接受一个函数作为其唯一函数,并返回另一个函数作为其唯一返回值,这样的特殊函数就称为装饰器。装饰器函数内部应包含一个完整的函数定义过程,并将这个新的函数作为返回值返回。在此函数内部应包含原函数的调用语句,以及其他额外添加的语句。装饰器定义完成后,就可以使用“@”符号对其他函数进行装饰。

当使用装饰器时,本质上是进行了如下过程:

@staticmethod

def xxx():

   pass

等同于:

def xxx():

   pass

xxx = staticmethod(xxx)

可见,装饰器语法等同于使用装饰器函数处理原函数,并赋值回原函数的过程。

对于装饰器内部定义的新函数,其作为返回值返回后,应保持与原函数一致的函数参数声明,这样才能保证参数正确传递。而装饰器内部定义的函数由于具有通用性,是不能像普通函数一样定义有限多个形参的,故这个问题的解决方案为:使用不定长形参声明,并在函数调用时使用参数解包。

下例定义了一个常见的计时用装饰器:

import time

import numpy as np

def Timer(func):

   def newFunc(*args, **kwargs):

       sTime = time.time()

       returnTuple = func(*args, **kwargs)

       eTime = time.time()

       print('Time: %.6f' % (eTime - sTime))

       return returnTuple

   return newFunc

@Timer

def Test(timesNum):

   for i in range(timesNum):

       np.random.rand(100, 100).dot(np.random.rand(100, 100))

Test(10)

上述代码定义了一个名为Timer的装饰器函数,这个函数接受一个函数作为参数,并在内部定义了一个声明为def newFunc(*args, **kwargs)的通用函数,在这个函数内部,以解包参数*args, **kwargs调用了装饰器传入的函数,并在调用前后保存了调用时间。最终输出消耗时间,并返回函数的返回值。上述这个函数定义,最终作为装饰器函数的返回值返回。在装饰器外部,这个返回的函数将覆盖原函数。所以,在调用被Timer装饰的Test函数时,函数不仅会执行Test函数的内容,还会执行Timer装饰器中所定义的附加内容,即虽然调用的是Test函数,但实际执行的是以Test函数作为参数的newFunc函数。

3 有参装饰器

有参装饰器是“返回装饰器函数的函数”,本人目前并不了解这种语法的具体应用场景,故这里只对有参装饰器做简单的语法上的讨论。

仍然以上文中的计时函数为例,有参装饰器可以使用参数修改装饰器的行为,如定义时间缩放:

import time

import numpy as np

def Timer(mulNum):

   def innerTimer(func):

       def newFunc(*args, **kwargs):

           sTime = time.time()

           returnTuple = func(*args, **kwargs)

           eTime = time.time()

           print('Time: %.6f' % ((eTime - sTime) * mulNum))

           return returnTuple

       return newFunc

   return innerTimer

@Timer(1000)

def Test(timesNum):

   for i in range(timesNum):

       np.random.rand(100, 100).dot(np.random.rand(100, 100))

Test(10)

上述代码中,Timer是一个有参装饰器,其参数用于定义时间缩放值。Timer应返回一个装饰器,故整个装饰器的定义被放在了Timer内部。而innerTimer为装饰器函数,其最终被Timer返回。innerTimer应接受一个函数作为参数,并返回一个新的函数,故在innerTimer内部定义了一个新的函数作为返回值,这个函数的定义中包含了对传入的函数参数func的调用,同时包含了计时语句,并最终利用时间,以及有参装饰器的参数mulNum共同计算输出值。innerTimer装饰器函数返回在其内部定义的函数newFunc,而Timer装饰器函数返回innerTimer装饰器函数,并最终将此装饰器交给被装饰的原函数。

综上,有参装饰器本质上是一个“返回(无参)装饰器函数的任意函数”,而无参装饰器函数是一个“接受函数作为唯一参数,并返回一个新的函数的函数”。有参装饰器只要求返回值是一个无参装饰器即可,而无参装饰器函数将修饰被其装饰的原函数,从而将原函数覆盖为一个装饰后的新函数。从而使得后续代码中所有对原函数的调用,实际调用的都是修饰后的新函数。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容

  • 〇、前言 本文共108张图,流量党请慎重! 历时1个半月,我把自己学习Python基础知识的框架详细梳理了一遍。 ...
    Raxxie阅读 18,787评论 17 410
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,103评论 18 139
  • 9837fd914689阅读 124评论 1 1
  • 2016年的春节假期终于过去了。丙申年开始了。猴年。C记FY16上半年的业绩不错。大家的心情也不错。今年,ACI有...
    taoza阅读 201评论 0 0