- 装饰器是什么
- 变量作用域
- 闭包和nonlocal
- 装饰器何时执行
- 叠放装饰器
- 参数化装饰器
- 标准库中的装饰器
- functools.lru_cache
- functools.singledispatch
装饰器是什么参考《流畅的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个示例可以看出:
- 示例5中, 局部变量a会遮盖全局变量a, test函数中没有定义局部变量b, 使用全局变量b
- 示例6中, 给b赋值, 会创建一个局部变量b, 而不是使用全局变量b
- 示例7中, 使用global声明全局变量b, test函数中给b赋值不会创建局部变量b, 会改变全局变量b的值
从这三个示例中, 可以对变量作用域有个基本认识了, 接下来基于此看下闭包是什么, 以及怎么用
闭包和nonlocal通过下面三个示例, 来了解下闭包是什么, 自由变量, 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': , 'ns': .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()
#输出
# 执行装饰器:
# 已装饰函数个数:1
# 执行装饰器:
# 已装饰函数个数:2
# 执行装饰器:
# 已装饰函数个数: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:
# 执行装饰器1:.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:
# 执行装饰器1:.decorate..inner at 0x0000023860F116A8>
# args2-args1-我是函数a
标准库中的装饰器
functools.lru_cache
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多
而添加了这个装饰器, 只会调用一次函数, 第二次获取结果耗时几乎可以忽略不计
用好这个装饰器, 可以大大提高程序的性能。
functools.singledispatch这个装饰器可以让被装饰的函数变成一个泛函数, 实现类似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})
# 输出
# 是obj类型
# 123是int类型
# 1.230000是float类型
# "abc"是string类型
# [a,b,c]是list类型
# (a,b,c)是tuple类型
# {a:1,b:2,c:3}是dict类型
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)