标签:参数错误 处理 return http 位置 内存 原则 不难 lis
学习装饰器需理解以下预备知识:
函数本质上也是一种变量,函数名即变量名,函数体就变量对应的值;函数体可以作为值赋给其他变量(函数),也可以通过函数名来直接调用函数。调用符号即()。
函数内部可以嵌套定义一层或多层函数,被嵌套的内部函数可以在外层函数体内部调用,也可以作为返回值直接返回
在一个嵌套函数中,内部被嵌套的函数可以调用外部函数非全局变量并且不受外部函数声明周期的影响(即可以把外部函数非全局变量视为全局变量直接调用)。
把一个函数作为参数传递给另外一个函数,并且把函数名作为返回值以便调用函数。
python中变量定义的过程如下:
1. 在内存中分配一块内存空间;
2. 将变量的值存放到这块内存空间;
3. 将这块内存空间的地址(门牌号)赋值给变量名(即变量名保存的是内存空间地址)
总结:变量保存的不是变量对应的真实值,而是真实值所被存放的内存空间地址,这也就意味着变量的调用需要通过调用内存空间地址(门牌号)来实现。将变量延伸到函数,函数和函数的参数都属于变量,调用函数进行参数传递时,是对函数和参数两个变量的同时调用,符合变量赋值及调用的机制(间接引用而非直接调用变量对应的值)。参数的传递实质上是一种引用传递,即通过传递对实参所在内存空间地址的指向来完成传递过程。
这一机制可通过id()来验证:
1 list1 = [‘Python‘, ‘PHP‘, ‘JAVA‘] 2 list2 = list1 3 print(id(list1), ‘========‘, id(list2)) 4 print(‘‘) 5 6 7 def foo1(x): 8 print(id(foo1)) 9 print(id(x)) 10 print(foo1) 11 12 13 def foo2(): 14 pass 15 16 foo1(foo2()) 17 print(‘---------‘) 18 print(id(foo1)) 19 print(id(foo2())) 20 print(foo1) 21 22 输出: 23 7087176 ======== 7087176 #普通变量调用,内存地址指向相同 24 25 7082192 26 1348178992 27 <function foo1 at 0x00000000006C10D0> 28 --------- # 函数调用前后,不仅函数名指向的内存地址相同,实参和形参的内存地址也相同 29 7082192 30 1348178992 31 <function foo1 at 0x00000000006C10D0>
设想这样一个现实场景:自己开发的应用在线上稳定运行了一年,后面随着业务的发展发现原有的通过某些函数定义的部分功能需要扩展一下新功能,恰好现有的功能又作为公共接口在很多地方被调用。
可能的实现方式:
1. 调整代码,重新定义需要修改功能对应的函数
这需要伤筋动骨了,重点是需要确保代码的一致性,另外有可能重新定义后原来的函数调用方式没法装载新功能。要知道这是在线上稳定运行的系统呀!
2. 把新功能封装成可接收函数作为参数、同时调用原函数的高阶函数,然后通过嵌套函数来调用返回高阶函数
顾名思义,装饰器是用来装饰的,它本身也是一个函数,只不过接收其它函数作为参数并装饰其它函数,为其它函数提供额外的附加功能。最直接的定义是,装饰器其实就是一个接收函数作为参数,并返回一个替换函数的可执行函数(详情参照下文论述)。
上文已经大概提到,装饰器是装饰其他函数的,为其他函数提供原本没有的附加功能。引用一段比较详细的文字:装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。
装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
定义和使用装饰器需遵循以下原则:
先来逐个梳理以下要点:
1 import time 2 3 4 def timmer(func): #外层函数传递被装饰的函数 5 def warpper(*args,**kwargs): 6 start_time=time.time() 7 func() #保持原样调用被装饰的函数 8 stop_time=time.time() 9 print(‘the func run time is %s‘ %(stop_time-start_time)) 10 return warpper #把内层实际调用执行被装饰的函数以及封装额外装饰功能的函数名(内存空间地址)作为返回值返回,以便后续调用执行 11 12 @timmer 13 def test1(): 14 time.sleep(3) 15 print(‘in the test1‘) 16 17 test1() 18 19 程序输出: 20 in the test1 #原生方式调用执行被装饰的函数 21 the func run time is 3.000171661376953 #附加了额外的程序执行时间统计功能
请注意这里定义的warpper函数的参数形式,非固定参数意味着实际传入的被装饰的函数可以有参数也可以没有参数。
现在我们把上述双层嵌套函数改装成一个函数来试试:
1 import time 2 def timmer(func): 3 start_time=time.time() 4 func() 5 stop_time=time.time() 6 print(‘the func run time is %s‘ %(stop_time-start_time)) 7 return timmer #去掉内层嵌套函数warpper,直接返回timmer自身 8 9 10 @timmer 11 12 13 def test1(): 14 time.sleep(3) 15 print(‘in the test1‘) 16 test1() 17 18 程序输出: 19 in the test1 20 Traceback (most recent call last): 21 the func run time is 3.000171661376953 22 File "E:/Python_Programs/S13/day4/deco.py", line 20, in <module> 23 test1() 24 TypeError: timmer() missing 1 required positional argument: ‘func‘
请注意我们改编装饰器后程序虽然可以运行,但已然报错了,提示最后一行在调用test1这个被装饰的函数时少了一个位置参数func:改编后的程序的返回值
是timmer本身,而我们在定义timmer函数时已经为其定义了一个参数func,因此报出缺少参数错误。
关于这个参数错误,我们同样可以通过修改内层嵌套函数的参数形式来佐证一下:
1 import time 2 def timmer(func): 3 def warpper(x): #这里故意为内层函数定义一个参数 4 print(x) 5 start_time=time.time() 6 func() 7 stop_time=time.time() 8 print(‘the func run time is %s‘ %(stop_time-start_time)) 9 return warpper 10 11 @timmer 12 def test1(): 13 time.sleep(3) 14 print(‘in the test1‘) 15 16 test1() 17 18 程序输出: 19 Traceback (most recent call last): 20 File "E:/Python_Programs/S13/day4/deco2.py", line 18, in <module> 21 test1() 22 TypeError: warpper() missing 1 required positional argument: ‘x‘
可以看出我们修改内层函数的参数定义后会报相同的错误,而且直接导致程序不能运行了!我们第一个演示程序中内层函数的参数是非固定参数,可有可无,因此运行OK。
还记得上文强调的装饰器的原则么?一是不改变对被装饰函数的调用执行方式(就要原生态调用);而是不改变被装饰函数的源代码。这改编后的装饰器的问题就在于不能满足第一条原生态调用被
装饰函数的条件了。要修复这个问题,我们只能返回一个不带任何参数或者说可以不带参数的函数作为返回值,而现状是装饰器函数本身已经被固化了,必须且只能传入func一个参数以便将被装饰
的函数传递给装饰器,因此我们不得不引入一个不带参数的内嵌函数,用它来完成需要的装饰并作为返回值以便后续调用。
于是装饰器就变成两层嵌套函数,外层(第一层)函数负责把需要被装饰的函数作为参数传递到装饰器内部,并定义整个装饰器的返回值,内层(第二层)函数负责执行具体的装饰功能,而外层定
义的返回值就是内层实际原生态调用被装饰函数和执行额外装饰功能的内层嵌套函数。两层嵌套分工明确又相得益彰,仔细推敲下这设计模式真是太nb了!
这也是很多地方说高阶函数+嵌套函数=>装饰器的原因。
在此也附上网上某大神的解答:
1 def deco1(func): #外层函数把被装饰的函数作为参数引入 2 def wrapper(): 3 print(‘Begin----‘) 4 func() #内嵌函数开始调用执行被装饰的函数,调用方式是原生态的 5 print(‘End-----‘) 6 return wrapper # 此处返回的函数即为替换函数,包含了对原函数调用执行和增加额外装饰功能的逻辑,注意这里返回的是函数名(门牌号) 7 8 9 @deco1 10 def test1(): 11 print(‘This is for test‘) 12 13 test1() #这里的test1在执行时会被替换为装饰器中的wrapper函数,并非原本意义上定义的test1函数了,原本意义上定义的test1函数对应于与wrapper中的func()
#这里通过调用符号()来调用执行被替换后的test1函数,请注意装饰器中的返回值是wrapper即替换函数的内存空间地址(门牌号),通过调用符号()即可获取
函数体(对变量test1进行赋值处理) 14 15 程序输出: 16 Begin---- 17 This is for test 18 End-----
1 __author__ = ‘Beyondi‘ 2 #!/usr/bin/env python 3 #! -*- coding:utf-8 -*- 4 5 6 def dec2(func): #外层函数只能处理被装饰的函数这一个参数 7 def wrapper(*args, **kwargs): #被装饰的函数的参数,一定要在内嵌函数中引入处理 8 print(‘Begin to decorate...‘) 9 ret = func(*args, **kwargs) 10 print(‘Arguments are %s %s‘ % (args, kwargs)) 11 return ret 12 return wrapper 13 14 15 @dec2 16 def test2(x, y, z): 17 print(‘aaa‘) 18 return 2 19 20 test2(‘a‘, ‘b‘, z=‘c‘) #实际调用时被装饰的函数参数传递方式不变 21 22 程序输出: 23 Begin to decorate... 24 aaa 25 Arguments are (‘a‘, ‘b‘) {‘z‘: ‘c‘} 26
1 def deco(limit): #装饰器自带的参数需要再定义一个外部函数来引入 2 def dec2(func): #接收处理被装饰的函数变量 3 def wrapper(*args, **kwargs): #处理被装饰的函数传递的参数,逻辑不变 4 print(‘Begin to decorate...‘) 5 # print(args) 6 func(*args, **kwargs) 7 if len(args) >= limit: #装饰器自带的参数开始派上用场 8 print(‘Arguments OK‘) 9 else: 10 print(‘Arguemts error‘) 11 return wrapper 12 return dec2 13 14 15 @deco(2) #通过语法糖进行装饰时,需要把装饰器自带的参数传递进去,改变这个实参会影响程序最后的输出结果 16 def test2(x, y, z): 17 print(‘aaa‘) 18 return 2 19 20 test2(‘a‘, ‘b‘, z=‘c‘) 21 22 程序输出: 23 Begin to decorate... 24 aaa 25 Arguments OK # 程序输出结果符合预期
以上程序表明,给装饰器本身引入参数可实现更灵活强大的装饰效果。需要注意的是装饰器自己的参数一定要在装饰器的最外层定义引入,此时真正的装饰器
就是最里层嵌套的函数了。这也是为什么讲装饰器至少是需要双层嵌套的高阶函数。
标签:参数错误 处理 return http 位置 内存 原则 不难 lis
原文地址:http://www.cnblogs.com/linupython/p/6772402.html