15个示例代码带你学会python装饰器

15个示例代码带你学会python装饰器,第1张

文章目录
  • 装饰器是什么
  • 变量作用域
  • 闭包和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类型

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/916972.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-16
下一篇 2022-05-16

发表评论

登录后才能评论

评论列表(0条)

保存