赞
踩
参考《流畅的python》第七章
先看下面四个示例, 并自己运行调试下, 再通过这四个示例理解下装饰器是什么
#示例1
#定义一个装饰器函数
def decorate(func):
def inner():
print("inner func")
return inner
#装饰target函数
@decorate
def target():
print("target func")
target()
# 输出
# inner func
#示例2
def decorate(func):
def inner():
print("inner func")
return inner
def target():
print("target func")
#装饰target函数
target = decorate(target)
target()
#示例3
class decorate:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print("inner func:%d"%args[0])
@decorate
def target(i):
print("target func:%d"%i)
target(1)
#示例4
class decorate:
def __init__(self, func):
self.func = func
def __call__(self, i):
print("inner func:%d"%i)
def target(i):
print("target func:%d"%i)
target = decorate(target)
target(1)
示例1 和示例 2 是通过高阶函数定义一个装饰器
示例3 和示例4 是通过类来定义一个装饰器
通过示例1 和 示例2 对比, 示例3和示例4对比, 可以看出装饰器是一种闭包用法的语法糖形式
通过上面四个例子, 我们来理解下装饰器的定义:
装饰器是可调用的对象, 其参数是另一个函数(被装饰的函数)。
装饰器可能会处理被装饰的函数, 然后把它返回, 或替换成另一个函数或可调用对象
通过上面的四个示例可以了解装饰器的实现是依赖闭包运作的, 学习闭包之前, 简单了解下变量作用域
还是通过三个示例来看下
#示例5
a=3 #全局变量a
b=3
def test(a): #参数a, 局部变量
print(a)
print(b)
test(1)
# 输出
# 1 #局部变量a的值
# 3 #全局变量b的值
#示例6
a=3
b=3
def test(a):
print(a)
print(b)
b=1 #给b赋值
test(1)
# 输出报错
# UnboundLocalError: local variable 'b' referenced before assignment
#示例7
a=3
b=3
def test(a):
global b
print(a)
print(b)
b=1
test(1)
print(b)
# 输出
# 1
# 3 #全局变量b的值
# 1 #全局变量b的值在test函数中改变
从上面3个示例可以看出:
从这三个示例中, 可以对变量作用域有个基本认识了, 接下来基于此看下闭包是什么, 以及怎么用
通过下面三个示例, 来了解下闭包是什么, 自由变量, nonlocal来声明自由变量
# 示例8 def make_numstr(): __strs=[] # 自由变量 def numstr(s): __strs.append(s) print(",".join(__strs)) return numstr ns = make_numstr() ns("one") ns("two") ns("three") print("局部变量表:") print(ns.__code__.co_varnames) print("全局变量表:") print(globals()) print("自由变量表:") print(ns.__code__.co_freevars) print("自由变量__strs中的值:") print(ns.__closure__[0].cell_contents) # 输出 # one # one,two # one,two,three # 局部变量表: # ('s',) # 全局变量表: # {......, 'make_numstr': <function make_numstr at 0x000001BA3DB42E18>, 'ns': <function make_numstr.<locals>.numstr at 0x000001BA3E221488>} # 自由变量表: # ('__strs',) # 自由变量__strs中的值: # ['one', 'two', 'three']
从示例中可以看出, 闭包函数可以延伸作用域, 可以访问函数体外的非全局作用域, 这就引出了自由变量, 自由变量不同于局部变量和全局变量, 自由变量保存在列表__closure__
中, 每个自由变量对应__closure__
中一个元素, 而自由变量值就保存在对应元素的cell_contents
接下来我们再通过示例来看看, 闭包函数什么情况下会引入自由变量,如何显示声明, 让闭包函数引入自由变量
# 示例 9 def make_numstr(): __str='' def numstr(s): __str+="," #局部变量 __str+=s print(__str) return numstr ns = make_numstr() print("局部变量表:") print(ns.__code__.co_varnames) print("自由变量表:") print(ns.__code__.co_freevars) ns("one") ns("two") ns("three") # 输出错误 # UnboundLocalError: local variable '__str' referenced before assignment # 局部变量表: # ('s', '__str') # 自由变量表: # ()
# 示例 10 def make_numstr(): __str='' def numstr(s): nonlocal __str #自由变量声明 __str+="," __str+=s print(__str) return numstr ns = make_numstr() ns("one") ns("two") ns("three") print("局部变量表:") print(ns.__code__.co_varnames) print("自由变量表:") print(ns.__code__.co_freevars) # 输出 # ,one # ,one,two # ,one,two,three # 局部变量表: # ('s',) # 自由变量表: # ('__str',)
从示例9中可以看出, __str
这种不可变类型, 需要重新赋值操作, 这样会在闭包函数中创建一个局部变量, 而不是引入闭包函数外的自由变量, 因为语句__str+=","
相当于__str=__str+","
这会产生局部变量未分配的异常。
而示例10中, 我们用nonlocal显示声明__str
是非局部变量, 闭包函数内部就不会再因为赋值操作而生成新的局部变量, 而是把值赋值给自由变量。
学习了python的变量作用域, 闭包函数, 自由变量, 我们再根据装饰器的定义来实现一个装饰器就不难了。
接下来, 我们需要了解下装饰器是何时执行, 何时生效呢, 还是通过如下示例来亲自体验下:
#示例 11 count=0 def decorator(func): global count print("执行装饰器:%s"%func) count+=1 print("已装饰函数个数:%d"%count) def inner(*args): func(*args, pre="decorate") return inner @decorator def target_a(pre=''): print(pre+"我是函数a") @decorator def target_b(pre=''): print(pre+"我是函数c") class targetC: @decorator def __call__(self, pre=''): print(pre+"我是函数c") print("main running") target_a() target_b() target_c = targetC() target_c() #输出 # 执行装饰器:<function target_a at 0x000001D5DB4A1488> # 已装饰函数个数:1 # 执行装饰器:<function target_b at 0x000001D5DB4A1598> # 已装饰函数个数:2 # 执行装饰器:<function targetC.__call__ at 0x000001D5DB4A1730> # 已装饰函数个数:3 # main running # decorate我是函数a # decorate我是函数c # decorate我是函数c
从示例11中可以看出, 装饰器是在函数定义后立刻就开始执行了, 这个动作在主程序入口之前完成。
# 示例12 def decorate1(func): print("执行装饰器1:%s"%func) def inner(*args, **kwargs): func(pre="decorate1-"+kwargs["pre"]) return inner def decorate2(func): print("执行装饰器2:%s"%func) def inner(*args, **kwargs): func(pre="decorate2-"+kwargs["pre"]) return inner @decorate1 @decorate2 def target_a(pre=''): print(pre+"我是函数a") target_a(pre='') # 输出 # 执行装饰器2:<function target_a at 0x0000016460F51510> # 执行装饰器1:<function decorate2.<locals>.inner at 0x0000016460F51598> # decorate2-decorate1-我是函数a
从示例12中可以看出, 叠放的装饰器是从内向外装饰的, 先用装饰器2装饰target_a
, 再用装饰器1装饰装饰器2返回的decorate2.inner
通过给装饰器定义参数, 来改变装饰器行为或给装饰器传递数据
我们还是通过一个例子来了解下:
# 示例13 def decorate_wrap1(pre='decorate1-'): def decorate(func): print("执行装饰器1:%s" % func) def inner(*args, **kwargs): func(pre=pre+kwargs["pre"]) return inner return decorate def decorate_wrap2(pre='decorate2-'): def decorate(func): print("执行装饰器2:%s" % func) def inner(*args, **kwargs): func(pre=pre+kwargs['pre']) return inner return decorate @decorate_wrap1(pre="args1-") @decorate_wrap2(pre="args2-") def target_a(pre=''): print(pre+"我是函数a") target_a(pre='') # 输出 # 执行装饰器2:<function target_a at 0x0000023860F11620> # 执行装饰器1:<function decorate_wrap2.<locals>.decorate.<locals>.inner at 0x0000023860F116A8> # args2-args1-我是函数a
functools.lru_cache装饰器会把耗时的函数结果缓存起来, 在下次调用的时候直接从缓存中读取, 减少函数调用产生的耗时。下面还是通过一个示例来看下具体用法:
# 示例 14 import functools import time, arrow def clock(func): def inner(*args): st = arrow.now().timestamp() res = func(*args) args_str = ",".join(args) print("[%f s] %s (%s) return : %s"%(arrow.now().timestamp()-st, func.__name__, args_str, res)) return res return inner #如果注释掉这个装饰器, 第二次耗时会增加很多, target_a函数也会被调用俩次 @functools.lru_cache() @clock def target_a(): time.sleep(1) return 10 st = arrow.now().timestamp() print(target_a()) print("第一次用时:%f s"%(arrow.now().timestamp()-st)) print(target_a()) print("第二次用时:%f s"%(arrow.now().timestamp()-st)) # 输出 # [1.014150 s] target_a () return : 10 # 10 # 第一次用时:1.014150 s # 10 # 第二次用时:1.014885 s #如果把functools.lru_cache注释掉 #输出结果如下: # [1.005498 s] target_a () return : 10 # 10 # 第一次用时:1.005498 s # [1.012670 s] target_a () return : 10 # 10 # 第二次用时:2.019177 s
从输出结果的对比中可以看出, 不是用这个装饰器, 会调用俩次target_a函数, 每次耗时1s多
而添加了这个装饰器, 只会调用一次函数, 第二次获取结果耗时几乎可以忽略不计
用好这个装饰器, 可以大大提高程序的性能。
这个装饰器可以让被装饰的函数变成一个泛函数, 实现类似java重载函数的功能。
我们还是通过一个示例来学习下:
# 示例 15 import functools @functools.singledispatch def type_print(obj): print("%s是obj类型"%repr(obj)) @type_print.register(str) def _(text): print("\"%s\"是string类型"%text) @type_print.register(int) def _(n): print("%d是int类型"%n) @type_print.register(float) def _(f): print("%f是float类型"%f) @type_print.register(list) def _(l): s=",".join(l) print("[%s]是list类型"%s) @type_print.register(tuple) def _(t): s=",".join(t) print("(%s)是tuple类型"%s) @type_print.register(dict) def _(d): s='' for (k, v) in d.items(): if s != '': s+="," s+="%s:%d"%(k, v) print("{%s}是dict类型"%s) type_print(abs) type_print(123) type_print(1.23) type_print("abc") type_print(["a", "b", "c"]) type_print(("a", "b", "c")) type_print({"a":1, "b":2, "c":3}) # 输出 # <built-in function abs>是obj类型 # 123是int类型 # 1.230000是float类型 # "abc"是string类型 # [a,b,c]是list类型 # (a,b,c)是tuple类型 # {a:1,b:2,c:3}是dict类型
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。