当前位置: 首页 > news >正文

Python 装饰器decorator 圣经

文章目录

  • 普通装饰器decorator
    • 0. 万能公式,非常重要
    • 1. 便于入门的decorator原理
    • 2. 理解函数
    • 3. 装饰器的作用:
    • 4. 装饰器的语法糖
    • 5. 装饰器顺序
    • 6. 极简的装饰器
    • 7. 装饰器的参数
      • 无参 函数装饰器
      • 有参 函数装饰器
  • 类装饰器class decorator
    • 0. 万能公式,非常重要
    • 1. 可以成为装饰器的类
      • 无参 装饰器类
      • 有参 装饰器类
    • 2. 可以装饰类的装饰器
      • 无参 可以装饰类的装饰器
      • 有参 可以装饰类的装饰器
  • 如何把装饰器放到类里面
  • @warps详解
      • 装饰器中`@wraps(view_func)`的作用
      • 不加@wraps的例子
      • 加@wraps后

普通装饰器decorator

0. 万能公式,非常重要

  • 在一个函数上添加装饰器,就等价于,这个函数名=装饰器调用这个函数

1. 便于入门的decorator原理

  • decorator是一个输入和输出都是函数的函数。请暂时这样理解,以便入门。

2. 理解函数

  • 在Python中定义一个函数,其实就是新建了一个变量,这个变量里保存了函数对象

    def double(x):return x * 2
    print(double)  # <function double at 0x10688a710>
    
  • 函数对象有一个特点就是callable,在Python中callable这个东西后面是可以跟一对小括号的,从而调用它。

    print(double(2))  # 4
    
  • 实际上在Python的语法层面,任何的东西都可以调用,只不过如果这个东西不是callable的话,会在Python的runtime时会出错。

    import dis
    # 例如我们让1进行调用,那么也是可以通过编译的,只不过会给出syntax warning,但是它的字节码表示一样可一LOAD完这个1之后再尝试call它
    dis.dis("1()")
    
  • 当我们理解了函数在Python中只是一个普通的对象后,我们就可以理解函数可以被当作参数传进其他的函数里。

    def double(x):return x * 2
    def triple(x):return x * 3
    def calc_number(func, x):print(func(x))calc_number(double, 3)  # 6
    calc_number(triple, 3)  # 9
    
  • 函数不单单可以作为变量传进其他函数,函数本身也可以成为一个返回值。

    def get_multiple_func(n):def multiple(x):# multiple函数return什么值是由get_multiple_func的n决定的return n * xreturn multipledouble = get_multiple_func(2)
    triple = get_multiple_func(3)print(double(2))  # 4
    print(triple(3))  # 9
    

    此时我们就理解了“函数的返回值可以是一个函数”,那么decorator相对来说就容易理解了。

3. 装饰器的作用:

在不改变原有功能代码的基础上,添加额外的功能,如用户验证等。

4. 装饰器的语法糖

@ 符号是装饰器的语法糖。它放在一个函数开始定义的地方(头顶),和这个函数绑定在一起。

在我们调用这个函数的时候,会先将这个函数做为参数传入它头顶,即装饰器里。

5. 装饰器顺序

一个函数可以同时定义多个装饰器,比如:

@a
@b
@c
def f ():pass

它的执行顺序是从里到外,最先调用最里层的装饰器,最后调用最外层的装饰器,它等效于:

f = a(b(c(f)))

6. 极简的装饰器

  • decorator本身就是一个callable,它没有特殊的地方,那么我们可以暂时理解为decorator本身就是一个函数,@后面跟的这个名字就是函数名,我们定义了一个函数叫做dec
def dec(f):pass@dec
def double(x):return x * 2# 第4行到第6行的代码完全等价于:double = dec(double)
  • decorator是一个输入和输出都是函数的函数。当然了输入一定是函数,但是输出不一定是,下面是极端的例子,没有人会这样写代码。

    def dec(f):return 1@dec
    def double(x):return x * 2print(double)  # 1
    

    注意,我们print的是double本身,而不是对double进行调用。

    double = dec(double)
    # dec(double)返回的是1
    # 1被赋值给double变量
    # print(double)就是1
    
  • 上面的例子只是极端情况,我们还是要认为decorator就是输入和输出都是函数的函数。

7. 装饰器的参数

无参 函数装饰器

import timedef timeit(f):def wrapper(x):start = time.time()ret = f(x)print(time.time() - start)return retreturn wrapper# 等价于:my_func = timeit(my_func)
@timeit
def my_func(x):time.sleep(x)my_func(1) # 等价于:timeit(my_func)(1)
# 这是通用写法,用可变参数来适配各种被包装的函数
import timedef timer(func):def wrapper(*args, **kwargs):start_time = time.time()func(*args, **kwargs)  # 这是函数真正执行的地方stop_time = time.time()cost_time = stop_time - start_timeprint("花费时间:{}秒".format(cost_time))return wrapper@timer
def want_sleep(sleep_time):time.sleep(sleep_time)want_sleep(3)

有参 函数装饰器

import timedef timeit(iteration):  # iteration是运行多少次。返回的是inner函数,相当于是之前没有参数的decoratordef inner(f):def wrapper(*args, **kwargs): # 里面运行了iteration次的函数fstart = time.time()for _ in range(iteration):ret = f(*args, **kwargs)print(time.time() - start)return retreturn wrapperreturn inner# 等价于:double = timeit(10)(double)
# 即:
# inner = timeit(10)
# double = inner(double)
@timeit(10)
def double(x):return x * 2double(1) # 等价于:timeit(10)(double)(1)
import timedef timer(sleep_time):def outter(func):def wrapper(*args, **kwargs):start_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())print("start_time:" + str(start_time))result = func(*args, **kwargs)  # 这是函数真正执行的地方time.sleep(sleep_time)stop_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())print("stop_time:" + str(stop_time))return resultreturn wrapperreturn outter@timer(sleep_time=3)
def sleep_status(name):print(name + " is sleeping...")return name + " is wake..."r = sleep_status("zhangsan")
print(r)# 输出
# start_time:2022-09-23 23:53:16
# zhangsan is sleeping...
# stop_time:2022-09-23 23:53:19
# zhangsan is wake...

类装饰器class decorator

  • 类装饰器class decorator这个名称实际上是有一定的歧义的。

    • 在有些地方是指:“可以成为装饰器的类”。这说的是装饰器本身。用中文的语境来描述,应该是“装饰器类”,而不是“类装饰器”。
    • 在有些地方是指:“可以装饰类的装饰器”。这说的是装饰器要装饰的对象。用中文的语境来描述,应该是“类 的 装饰器”。
  • 装饰器本身既可以是函数,还可以是类。而装饰的对象同样既可以是函数也可以是类。

0. 万能公式,非常重要

  • 万能公式非常重要,只要你把它转换成等价形式,decorator就比较好读,而转换成等价形式之后,我们就知道代码如何写,它的输入应该是什么,它的输出应该是什么。

1. 可以成为装饰器的类

装饰器类 的实现,必须实现__call____init__两个内置函数。

当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。

无参 装饰器类

__init__:接收被装饰函数
__call__:实现装饰逻辑。

import timeclass Timer:def __init__(self, func):# 函数add被传入__init__方法里,所以add被作为参数保存在了self.func里self.func = func# __call__魔术方法的意思,让所有这个类的实例都变成一个callable# 简单理解就是这个类的实例,都可以当作函数用,你都可以调用它 即在后面用小括号def __call__(self, *args, **kwargs):start = time.time()ret = self.func(*args, **kwargs)  # self.func就是原来的add函数print(f"Time: {time.time() - start}")return ret# 把Timer这个类本身当作一个装饰器,装饰在了add函数上
# 等价于:add = Timer(add)
# @Timer装饰器相当于把名为add的变量,其值从一个函数变成了一个Timer类的实例
@Timer
def add(a, b):return a + bprint(type(add))  # <class '__main__.Timer'>
print(add(2, 3))  # 实际上是调用了Timer类的实例add的__call__方法,此时2和3参数就被传到__call__的参数里。
"""
Time: 5.7220458984375e-06
5
"""
  • 通过上面的例子,我们可以看到和前面讲的函数decorator其实没有很大的区别。原来的函数装饰器是“函数调用一个函数返回一个函数”,更确切的说是“函数调用一个函数返回一个callable”。
  • 现在是类调用一个函数返回一个实例,这个实例依然是callable
  • 所以我们只需要把decorator在脑海中进行等价转换就非常容易理解了。
class Logger(object):def __init__(self, func):self.func = funcdef __call__(self, *args, **kwargs):print("[INFO]: the function {func}() is running..." \.format(func=self.func.__name__))return self.func(*args, **kwargs)@Logger
def say(something):print("say {}!".format(something))say("hello")# 输出
# [INFO]: the function say() is running...
# say hello!

有参 装饰器类

带参数和不带参数的类装饰器有很大的不同。

__init__ :不再接收被装饰函数,而是接收传入参数。
__call__:接收被装饰函数,实现装饰逻辑。

import timeclass Timer:def __init__(self, prefix):self.prefix = prefixdef __call__(self, func):def wrapper(*args, **kwargs):start = time.time()ret = func(*args, **kwargs)print(f"{self.prefix} {time.time() - start}")return retreturn wrapper# 等价于:add = Timer(prefix="curr_time: ")(add)
# 那么此时add就是wrapper了
@Timer(prefix="curr_time: ")
def add(a, b):return a + bprint(add(2, 3))
"""
curr_time:  1.9073486328125e-06
5
"""
class Logger(object):def __init__(self, level='INFO'):self.level = leveldef __call__(self, func):  # 接受函数def wrapper(*args, **kwargs):print("[{level}]: the function {func}() is running..." \.format(level=self.level, func=func.__name__))func(*args, **kwargs)return wrapper  # 返回函数@Logger(level='WARNING')
def say(something):print("say {}!".format(something))say("hello")# 输出
# [WARNING]: the function say() is running...
# say hello!

2. 可以装饰类的装饰器

无参 可以装饰类的装饰器

  • 在Python里,自定义的class类的实例,如果我们直接打印实例,是不会打印出任何有价值的信息的,只会打印出这个实例对象是一个什么类。

    class MyObject:def __init__(self, a, b):self.a = aself.b = bo = MyObject(1, 2)
    print(o)  # <__main__.MyObject object at 0x109447fd0>
    
  • 那么我们可以通过重载其__str__方法,来改变print出来的结果

    class MyObject:def __init__(self, a, b):self.a = aself.b = bdef __str__(self):return str(self.__dict__)o = MyObject(1, 2)
    print(o)  # {'a': 1, 'b': 2}
    
  • 当然,如果每一个对象都改的话也挺麻烦的。此时“装饰类的装饰器”就派上了用场。

# add_str函数是一个参数是class,返回值也是class 的函数
def add_str(cls):"""这个decorator本质就是重载了一下这个class的__str__方法"""def __str__(self):  # 自定义了一个名为__str__的函数,当然这个函数也可以叫任意名,但是为了规范我们不要随意起名return str(self.__dict__)# 将接收的class对象的__str__方法给替代成了add_str自己定义的__str__函数cls.__str__ = __str__return cls# 等价于:MyObject = add_str(MyObject)
@add_str
class MyObject:def __init__(self, a, b):self.a = aself.b = bo = MyObject(1, 2)
print(o)  # {'a': 1, 'b': 2}
  • 所以,给类写装饰器也没有什么难的,就是输入一个类返回一个类,然后在这个装饰器里面对传入的类动动手脚,完成我们需要的事情。

有参 可以装饰类的装饰器

# add_str函数是一个参数为greet的函数,返回一个参数为cls的inner函数
def add_str(greet):def inner(cls):print(greet)def say(self):return str(self.__dict__)# 将inner接收的class对象的__str__方法给替代成了inner自己定义的say函数cls.__str__ = sayreturn clsreturn inner# 等价于:MyObject = add_str("hello")(MyObject)
# 也就是:
# inner = add_str("hello")
# MyObject = inner(MyObject)
@add_str("hello")
class MyObject:def __init__(self, a, b):self.a = aself.b = b"""
什么都不写,也会打印出hello,因为inner(MyObject)的时候,会执行inner函数,打印出hello
"""
o = MyObject(1, 2)
print(o)  # {'a': 1, 'b': 2}

如何把装饰器放到类里面

def log_function(func):def wrapper(*args, **kwargs):print("function start!")print(f"args: {args}")ret = func(*args, **kwargs)print(f"function end!")return retreturn wrapper@log_function
def fib(n):if n <= 1:return 0return fib(n - 1) + fib(n - 2)fib(3)
"""
function start!
args: (3,)
function start!
args: (2,)
function start!
args: (1,)
function end!
function start!
args: (0,)
function end!
function end!
function start!
args: (1,)
function end!
function end!
"""

@warps详解

__name__用来显示函数的名称,__doc__用来显示文档字符串也就是(“”“文档字符串”“”)这里面的内容。

文档字符串用于解释文档程序,帮助程序文档更加简单易懂。

可以在函数体的第一行使用一对三个单引号 ‘’’ 或者一对三个双引号 “”" 来定义文档字符串。

使用__doc__(注意双下划线)调用函数中的文档字符串属性。

装饰器中@wraps(view_func)的作用

不改变使用装饰器原有函数的结构(如name, doc)。

装饰器(decorator)在实现的时候,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变)。

为了不影响,Python的functools包中提供了一个叫**@wraps**的装饰器来消除这样的副作用。

写一个装饰器的时候,最好在实现之前加上functools中的wraps,它能保留原有函数的名称和文档字符串(DocStrings)。

不加@wraps的例子

def my_decorator(func):def wrapper(*args, **kwargs):"""decorator"""print("Decorated function...")return func(*args, **kwargs)return wrapper@my_decorator
def t():"""TestWord"""print("Test function")t()
# 输出:
# Decorated function...
# Test functionprint(t.__name__)  # wrapper
print(t.__doc__)  # decorator

执行的整个过程:在调用t()函数时,首先会调用装饰器(将t作为参数传入到装饰器中),执wrapper函数,再执行t函数。

但我们可以看到t函数的名字:__name__为wrapper,__doc__为decorator,已经不是原来的test函数了。

加@wraps后

from functools import wrapsdef my_decorator(func):@wraps(func)def wrapper(*args, **kwargs):"""decorator"""print("Decorated function...")return func(*args, **kwargs)return wrapper@my_decorator
def t():"""TestWord"""print("Test function")t()
# 输出:
# Decorated function...
# Test functionprint(t.__name__)  # t
print(t.__doc__)  # TestWord

会发现,test函数的__name____doc__还是原来的,即函数名称和属性没有变换。

相关文章:

  • html css 导航栏 2
  • 如何基于 esp-at 固件测试 TCP (UART 转 WiFi 透传)吞吐?
  • C语言 —— 图形打印
  • Centos8 使用编译安装nginx
  • 内网渗透-跨域环境渗透-1
  • GPT实战系列-构建多参数的自定义LangChain工具
  • vue3-admin后台管理系统: 使用Vue3+Vue-Router4 + Element-Plus打造高效后台管理系统
  • Java学习笔记15——类型转换(基本数据类型)
  • 手把手教您如何在国内免费使用GPT4接口的Bing,亲测有效!
  • 收下这份实操案例,还怕不会用Jmeter接口测试工具
  • 深入理解位运算符及其在JavaScript中的应用
  • html5cssjs代码 004 2035年倒计时
  • 揭秘2024美团春招:最全MySQL面试题大全,必看必收藏!
  • web中实现一个账号同一时间只能由一个人使用
  • 【MySQL篇】 MySQL基础学习
  • 分享的文章《人生如棋》
  • [译]前端离线指南(上)
  • 【跃迁之路】【477天】刻意练习系列236(2018.05.28)
  • 10个最佳ES6特性 ES7与ES8的特性
  • android百种动画侧滑库、步骤视图、TextView效果、社交、搜房、K线图等源码
  • Druid 在有赞的实践
  • ES6简单总结(搭配简单的讲解和小案例)
  • hadoop入门学习教程--DKHadoop完整安装步骤
  • iOS | NSProxy
  • JavaScript学习总结——原型
  • JS变量作用域
  • Linux各目录及每个目录的详细介绍
  • Netty+SpringBoot+FastDFS+Html5实现聊天App(六)
  • React-redux的原理以及使用
  • Redis 懒删除(lazy free)简史
  • SpiderData 2019年2月25日 DApp数据排行榜
  • 代理模式
  • 函数式编程与面向对象编程[4]:Scala的类型关联Type Alias
  • ------- 计算机网络基础
  • 开源SQL-on-Hadoop系统一览
  • 融云开发漫谈:你是否了解Go语言并发编程的第一要义?
  • 如何打造100亿SDK累计覆盖量的大数据系统
  • 如何学习JavaEE,项目又该如何做?
  • 使用权重正则化较少模型过拟合
  • 首页查询功能的一次实现过程
  • 思否第一天
  • 我与Jetbrains的这些年
  • 移动端 h5开发相关内容总结(三)
  • 关于Kubernetes Dashboard漏洞CVE-2018-18264的修复公告
  • # 达梦数据库知识点
  • ### Error querying database. Cause: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException
  • #AngularJS#$sce.trustAsResourceUrl
  • $.type 怎么精确判断对象类型的 --(源码学习2)
  • (学习日记)2024.04.10:UCOSIII第三十八节:事件实验
  • (转)德国人的记事本
  • .bat批处理(三):变量声明、设置、拼接、截取
  • .Mobi域名介绍
  • .NET Framework 和 .NET Core 在默认情况下垃圾回收(GC)机制的不同(局部变量部分)
  • .NET版Word处理控件Aspose.words功能演示:在ASP.NET MVC中创建MS Word编辑器
  • .vollhavhelp-V-XXXXXXXX勒索病毒的最新威胁:如何恢复您的数据?