Python中的装饰器

装饰器: 本质就是函数,为其他函数添加附加功能

装饰器原则:

  1. 不修改被装饰函数的源代码

  2. 不修改被装饰函数的调用方式

装饰器=高阶函数 + 函数嵌套 +闭包

1.高阶函数

定义:

  1. 函数接收的参数是一个函数名

  2. 函数的返回值是一个函数名

满足上述任意一个条件的函数即为高阶函数

高阶函数示例

 1 def foo():
 2     print("hello")
 3 
 4 
 5 # 函数名作为参数
 6 def func_1(func):
 7     print("heiheihei")
 8     func()
 9     print("world")
10 
11 
12 # 函数名作为返回值
13 def func_2(func):
14     print("return value: %s" % func)
15     return func
16 
17 # 函数名作为参数和返回值
18 def func_3(func):
19     print("from func_3")
20     return func
21 
22 func_1(foo)
23 print('----------------')
24 func_2(foo)
25 print('----------------')
26 f = func_3(foo)
27 f() # f是func_3的返回值,即为函数foo的函数地址,f()执行函数foo输出“hello”
28 
29 >>heiheihei
30 hello
31 world
32 ----------------
33 return value: <function foo at 0x00000276CAAA3E18>
34 ----------------
35 from func_3
36 hello

使用高阶函数实现在不改变函数调用方式的前提下,增加函数的功能

 1 import time
 2 
 3 # 为foo函数增加函数运行时间计时功能
 4 
 5 def foo():
 6     time.sleep(2)
 7     print("function foo running")
 8 
 9 def timer(func):
10     start_time = time.time()
11     func()
12     stop_time = time.time()
13 
14     print("running time %s" % (start_time - stop_time))
15 
16     return func
17 
18 foo = timer(foo)
19 
20 foo()
21 
22 >>function foo running
23 running time -2.0002999305725098
24 function foo running    
25 
26 # 以foo()的形式运行函数foo,实现了不改变函数调用方式运行函数
27 # 但运行过程中多执行了一次foo,
28 # 第一次执行是timer函数中的func()
29 # 第二次执行是timer函数返回了foo函数名,最后运行foo()再次运行了foo原函数

根据以上示例可以得出单纯使用高阶函数无法完全实现在不改变函数调用方式的基础下增加函数的功能

2. 函数嵌套

定义:在函数里面嵌套定义函数

 1 def country(name):
 2     print('country is %s' %name)
 3     def province():
 4         print('guangdong')
 5         def city():
 6             print('shenzhen')
 7         city()
 8     province()
 9 
10 country('china')
11 
12 >>country is china
13 guangdong
14 shenzhen

3. 闭包

定义:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)

 1 def country(name):
 2     print('country is %s' %name)
 3     def province():
 4         print('province is %s' % name)
 5         def city():
 6             print('city is %s' % name)
 7         city()
 8     province()
 9 
10 country('china')

province()函数就是一个闭包

4. 使用高阶函数+函数嵌套+闭包实现为函数增加功能

import time

def timer(func):
    def inner():
        start_time = time.time()
        func()
        stop_time = time.time()
        print("running time is %s" % (stop_time- start_time))
    return inner

def foo():
    print("foo function")
    time.sleep(2)

f = timer(foo) # f就是timer函数的返回值--inner函数的函数指针

f() # f加括号执行f函数

>>foo function
running time is 2.000217914581299

上述示例实现了为foo函数增加函数运行时间计时的功能,如果将f改为foo,则实现了不改变函数的调用方式同时增加了函数的功能,如下所示

 1 import time
 2 
 3 def timer(func):
 4     def inner():
 5         start_time = time.time()
 6         func()
 7         stop_time = time.time()
 8         print("running time is %s" % (stop_time- start_time))
 9     return inner
10 
11 def foo():
12     print("foo function")
13     time.sleep(2)
14 
15 foo = timer(foo) 
16 
17 foo() 
18 
19 >> foo function
20 running time is 2.000906229019165
21 
22 # 最后执行的foo函数相比于最初定义的foo函数,实现了增加函数运行时间计时的功能

到此基本实现了在不改变函数的调用方式以及函数源码的前提下,实现了增加函数功能,但还需要做一步函数赋值的操作,如果其他函数需要怎加同样的功能,还要对其他函数做赋值操作,这是很麻烦的。

使用装饰器可以避免这样的麻烦,只要在添加功能的目标函数前添加一行代码:@功能函数。实现方法如下所示

 1 import time
 2 
 3 def timer(func):
 4     def inner():
 5         start_time = time.time()
 6         func()
 7         stop_time = time.time()
 8         print("running time is %s" % (stop_time- start_time))
 9     return inner
10 
11 @timer
12 def foo():
13     print("foo function")
14     time.sleep(2)
15 
16 foo()
17 
18 >>foo function
19 running time is 2.000242233276367

5. 获取被装饰函数的返回值

 1 import time
 2 
 3 def timer(func):
 4     def inner():
 5         start_time = time.time()
 6         ret = func() # 执行func函数得到函数返回值,赋值给ret
 7         stop_time = time.time()
 8         print("running time is %s" % (stop_time- start_time))
 9         return ret # inner的返回值为ret
10     return inner # timer的返回值为inner函数名
11 
12 @timer
13 def foo():
14     print("foo function")
15     time.sleep(2)
16     return "return value foo"
17 
18 ret = foo()
19 # 首先假设foo = A
20 # 执行foo() <==> 首先执行A = timer(foo),然后执行A()
21 # timer(foo)的返回值为inner,即A = inner
22 # 执行A() <==> 执行inner()
23 # 执行inner()过程中执行inner函数体中的所有代码,得到最初定义的foo函数的返回值“return value foo”
24 # 即执行A()得到返回值“return value foo”
25 # ret = A() 将A()的返回值赋值给ret, 即使用ret=foo()可以得到最初定义的foo函数的返回值
26 print(ret)
27 
28 >>foo function
29 running time is 2.0001463890075684
30 return value foo

6. 被修饰函数有参数

 1 import time
 2 
 3 def timer(func):
 4     def inner(*args, **kwargs):
 5         start_time = time.time()
 6         ret = func(*args, **kwargs) # 执行func函数得到函数返回值,赋值给ret
 7         stop_time = time.time()
 8         print("running time is %s" % (stop_time- start_time))
 9         return ret # inner的返回值为ret
10     return inner # timer的返回值为inner函数名
11 
12 @timer
13 def foo(name):
14     print("usr name is %s" % name)
15     time.sleep(2)
16     return "return value foo"
17 
18 ret = foo("Jack")
19 # 首先假设foo = A
20 # 执行foo() <==> 首先执行A = timer(foo),然后执行A()
21 # timer(foo)的返回值为inner,即A = inner
22 # 执行A() <==> 执行inner()
23 # 执行inner()过程中执行inner函数体中的所有代码,
24 # 执行到func函数时,即执行被修饰函数foo(name),foo有参数,则func也需要参数
25 # func的参数需要从A()的参数中获得,即inner的参数就是func的参数
26 # 由于参数是不定的,可以为函数添加参数 *args, **kwargs
27 
28 print(ret)
29 
30 >> usr name is Jack
31 running time is 2.0003693103790283
32 return value foo

7. 带参数装饰器

 1 import time
 2 
 3 def type_check(*args, **kwargs):
 4     file_type = kwargs["file_type"]
 5     print("file type %s" %file_type)
 6 
 7     def timer(func):
 8         def inner(*args, **kwargs):
 9             start_time = time.time()
10             ret = func(*args, **kwargs) # 执行func函数得到函数返回值,赋值给ret
11             stop_time = time.time()
12             print("running time is %s" % (stop_time- start_time))
13             return ret # inner的返回值为ret
14         return inner # timer的返回值为inner函数名
15 
16     return timer
17 
18 @type_check(file_type = "mysql")
19 def foo(name):
20     print("usr name is %s" % name)
21     time.sleep(2)
22     return "return value foo"
23 
24 ret = foo("Jack")
25 # type_check加括号首先执行type_check(),获取type_check的参数
26 # foo <==> A = type_check(), A得到timer返回值
27 # 执行foo() <==> timer()
28 # 相比于无参装饰器,有参装饰器在无参装饰器的基础上套一层函数来传参
29 
30 print(ret)
31 
32 >>file type mysql
33 usr name is Jack
34 running time is 2.00007963180542
35 return value foo

8. 多个装饰器

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

推荐阅读更多精彩内容