当前位置:   article > 正文

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

py 装饰器简单代码

参考《流畅的python》第七章

装饰器是什么

先看下面四个示例, 并自己运行调试下, 再通过这四个示例理解下装饰器是什么

#示例1
#定义一个装饰器函数
def decorate(func):
    def inner():
        print("inner func")
    return inner
#装饰target函数
@decorate
def target():
    print("target func")

target()
# 输出
# inner func
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
#示例2
def decorate(func):
    def inner():
        print("inner func")
    return inner


def target():
    print("target func")

#装饰target函数
target = decorate(target)

target()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
#示例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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
#示例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
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

示例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的值
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
#示例6
a=3
b=3
def test(a):
    print(a)
    print(b)
    b=1 #给b赋值

test(1)
# 输出报错
# UnboundLocalError: local variable 'b' referenced before assignment
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
#示例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函数中改变
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

从上面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': <function make_numstr at 0x000001BA3DB42E18>, 'ns': <function make_numstr.<locals>.numstr at 0x000001BA3E221488>}
# 自由变量表:
# ('__strs',)
# 自由变量__strs中的值:
# ['one', 'two', 'three']
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

从示例中可以看出, 闭包函数可以延伸作用域, 可以访问函数体外的非全局作用域, 这就引出了自由变量, 自由变量不同于局部变量和全局变量, 自由变量保存在列表__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')
# 自由变量表:
# ()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
# 示例 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',)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

从示例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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

从示例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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

从示例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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

标准库中的装饰器

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

从输出结果的对比中可以看出, 不是用这个装饰器, 会调用俩次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})

# 输出
# <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类型
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/213260
推荐阅读
相关标签
  

闽ICP备14008679号