Python装饰器实例讲解(三)

本文多参考《流畅的python》,在此基础上增加了一些实例便于理解

姊妹篇

Python装饰器实例讲解(一),让你简单的会用

Python装饰器实例讲解(二),主要讲了一个万能公式(原理)

本文其实反而是最最基础的部分,当然也回答了好几个关键的问题,也有一些是重复的地方

  • 理解装饰器必须理解函数、闭包等概念
  • 闭包后面单独讲,函数在本文是重点,从函数讲起

函数:一等对象

  • 在Python中,函数是一等对象,需要满足以下条件:

    • 在运行时创建
    • 能赋值给变量或数据结构中的元素
    • 能作为参数传给函数
    • 能作为函数的返回结果
  • 在Python中,整数、字符串和字典都是一等对象

函数名能赋值给变量

  • 示例

    def func():
    print('hello') my_func = func # 此处不要写成func()
    my_func() # hello
    func() # hello
  • 这样的使用比比皆是,比如在pytest中的一个应用

    import pytest
    
    xfail = pytest.mark.xfail  # 就是这里
    
    @xfail  # 这样看就比较简洁了
    def test_hello():
    assert 1 if __name__ == '__main__':
    pytest.main(['-sv',__file__])
  • 较为为典型的应用就是lambda,它是匿名的,但它同样可以赋值给一个变量

    my_add = lambda x,y:x+y
    result = my_add(1,2)
    print(result) # 3

函数能作为参数传给函数

  • 示例

    def double(x):
    return x*2 def triple(x):
    return x*3 def calc(funcion_name,x):
    return funcion_name(x) print(double(2)) # 4
    print(triple(2)) # 6
    print(calc(double,2)) # 4
    print(calc(triple,2)) # 6
  • 在上面的例子中你可以看到calc这个函数接收的第一个参数是函数名字

  • 调用的时候你传入的是double、triple这样的名字

  • 仔细观察代码,calc的实现其实的本意就是把第一个参数当做函数名,第二个参数是第一个参数的参数。所以本质上你可以做任何事情,只要这个函数仅接收一个参数即可

    print(calc(bin,10))  # 返回的是bin(10)的结果  0b1010
    print(calc(max,(2,5,3))) # 执行的是max((2,5,3))
  • 高阶函数如map/filter/reduce/sort等,如果你接触过,他们的参数不都是函数名吗?

  • 我也写过一篇文章,Python函数式编程之map/filter/reduce/sorted

能作为函数的返回结果

  • 示例

    def add(x,y):
    return x+y def func():
    print('calling func')
    return add print(func()(1,2)) 
    # 输出如下
    # calling func
    # 3
    # func() 就是 add , 跟你执行add(1,2)的效果是一样的
  • 你也可以这样

    new_add = func()
    print(new_add(1,2))
    # calling func
    # 3
  • 如果你看过前面的两篇文章,到这里就应该很熟悉了

可调用对象

  • 除了函数是可调用的,还有很多(其实也没多少)都是可调用对象

  • 按照流畅的python的说法,有这么多可调用对象

    可调用对象 说明
    用户定义的函数 使用 def 语句或 lambda 表达式创建
    内置函数 使用 C 语言(CPython)实现的函数,如 len 或 time.strftime
    内置方法 使用 C 语言实现的方法,如 dict.get
    方法 在类的定义体中定义的函数
    调用类时会运行类的 new 方法创建一个实例,然后运行 init 方法,初始化实 例,最后把实例返回给调用方。因为 Python 没有 new 运算符,所以调用类相当于调用函数。
    类的实例 如果类定义了 call 方法,那么它的实例可以作为函数调用。
    生成器函数 使用 yield 关键字的函数或方法。调用生成器函数返回的是生成器对象。

对普通的初学者而言其实就是函数和类,类的调用分2级,Obj()这是实例化,同时调用new和init。

new和init魔术方法,后面会单独开篇讲解,单例跟这个是息息相关的。

生成器后面也考虑单独开文章说一下。

  • 示例代码(说明new和init)

    class Person:
    def __new__(cls, *args, **kwargs):
    print('calling new')
    cls.instance = super().__new__(cls)
    return cls.instance
    def __init__(self):
    print('calling init') wuxianfeng = Person()
  • 示例输出

    calling new
    calling init
  • 但此时wuxianfeng这个Person类的实例并不是可调用的对象

  • 如果你写wuxianfeng(),会给你提示

    TypeError: 'Person' object is not callable
  • 你需要在Person类中定义一个__call__方法

    class Person:
    ...
    def __call__(self, *args, **kwargs):
    print('callable')
  • 此时再次执行wuxianfeng()就可以得到callable了

  • 当然如果你执行Person()()结果也是这样的

    calling new
    calling init
    callable

  • python提供了一个内置的callable()函数来检测对象是否可调用

    print([callable(obj) for obj in (abs, str, 13)])  # [True, True, False]

回到装饰器

  • 虽然你可能已经学到装饰器三了,但请你清空下你了解的装饰器,倒也不是从0开始,带点复习

  • 示例代码

    def decorate(function_name):
    def inner():
    print('calling inner')
    function_name()
    return inner
    @decorate
    def target():
    print('calling target') target()
  • 输出结果

    calling inner
    calling target

  • 根据万能公式,分析下执行过程

    • 当你在执行target()的时候,由于target上有个装饰器,实际上发生的事情是target = decorate(target)

    • 前面的target 是新的(一个变量),后面的decorate(target)中的target是你之前定义的函数

    • decorate(target)就会去调用decorate函数传入target参数,返回inner

    • 卡....返回了inner,是你加了装饰器的效果,至此都没有执行函数

    • 正是由于最终的target(),就是去调用了inner(),对应的语句是

      print('calling inner')
      function_name() # 你传入的是target就是此处的function_name

  • 说一些理论

    • 装饰器只是语法糖
    • 装饰器可以像常规的可调用对象那样调用,其参数是另一个函数(被装饰的函数)。
    • 装饰器可能会处理被装 饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象。
    • 装饰器的一大特性是,能把被装饰的函数替换成其他函数
    • 第二个特性是,装饰器在加载模块时立即执行

  • 关于被替换

    def decorate(function_name):
    def inner():
    print('calling inner')
    function_name()
    print('这是inner的id:',id(inner))
    return inner
    @decorate
    def target():
    print('calling target') print('这是target的id:',id(target))
  • 示例输出(你输出的id跟我肯定不一样,但2者应该是一致的,从这个角度也能看出来你执行的target不再是原来的target了)

    这是inner的id: 1804087435904
    这是target的id: 1804087435904

叠放装饰器

  • 日常代码中还是有一些场景能看到一个函数被多个装饰器装饰的情况,比如pytest的allure

  • 这个执行顺序就是如你所想的那般,先装饰的先执行

  • 示例代码

    def decorate1(function_name):
    def inner1():
    print('calling inner1')
    function_name()
    return inner1 def decorate2(function_name):
    def inner2():
    print('calling inner2')
    function_name()
    return inner2 @decorate1
    @decorate2
    def target():
    print('calling target') target()
    # 输出
    # calling inner1
    # calling inner2
    # calling target
  • 但这种情况下的万能公式是怎样的呢???你知道不~

  • 万能公式1

    @decorate1
    def target():
    print('calling target') # 等价于做了一件事
    target = decorate1(target)
  • 万能公式2

    @decorate1
    @decorate2
    def target():
    print('calling target') # 等价于做了2件事
    # 第一件事,注意,就近原则
    target = decorate2(target) # 前面的target是新的变量,后面的target是def的最初的、原始的函数
    # 第二件事
    target = decorate1(target) # 前面的target又是一个新的变量,后面的target是line8的前面的target # 你也可以理解为做了一件事(合并上面2行)
    target = decorate1(decorate2(target) ) # 最近的@的先调用
  • 不信请看

    def decorate1(function_name):
    def inner1():
    print('calling inner1')
    function_name()
    return inner1 def decorate2(function_name):
    def inner2():
    print('calling inner2')
    function_name()
    return inner2 def target():
    print('calling target') target = decorate2(decorate1(target) )
    target()

  • 装饰器就讲到这里了
  • 会用是第一步,理解简单的过程是第二步,会写一个装饰器才算是基本懂了

Python装饰器实例讲解(三)的更多相关文章

  1. Python装饰器实例讲解(二)

    Python装饰器实例讲解(二) Python装饰器实例讲解(一) 你最好去看下第一篇,虽然也不是紧密的链接在一起 参考B站码农高天的视频,大家喜欢看视频可以跳转忽略本文:https://www.bi ...

  2. Python装饰器实例讲解(一)

    Python装饰器实例讲解(一) 多种角度讲述这个知识,这是个系列文章 但前后未必有一定的顺承关系 部分参考网络 本文以一个小案例引出装饰器的一些特点,不涉及理论,后面再谈 案例 写一个代码来求一个数 ...

  3. python --装饰器内容讲解

    python装饰器就是用于拓展原来函数功能的一种函数,这个函数的特殊之处在于它的返回值也是一个函数,使用python装饰器的好处就是在不用更改原函数的代码前提下给函数增加新的功能. 3.1 定义装饰器 ...

  4. python 装饰器(六):装饰器实例(三)内置装饰器

    内置的装饰器和普通的装饰器原理是一样的,只不过返回的不是函数,而是类对象,所以更难理解一些. @property 在了解这个装饰器前,你需要知道在不使用装饰器怎么写一个属性. def getx(sel ...

  5. python --装饰器通俗讲解

    装饰器 什么是装饰器?:在不修改源代码和调用方式的基础上给其增加新的功能,多个装饰器可以装饰在同一个函数上 Python中的装饰器是你进入Python大门的一道坎; 装饰器特点: 不改变原函数原代码: ...

  6. Python 装饰器实例

    retry 偶然看到一篇文章,想到了前几天的一个需求,git pull性能不稳,需要加入重试机制,正好这个装饰器的实例符合这样的场景. # coding:utf-8 import time impor ...

  7. Python装饰器与面向切面编程

    今天来讨论一下装饰器.装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志.性能测试.事务处理等.装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数 ...

  8. python设计模式之装饰器详解(三)

    python的装饰器使用是python语言一个非常重要的部分,装饰器是程序设计模式中装饰模式的具体化,python提供了特殊的语法糖可以非常方便的实现装饰模式. 系列文章 python设计模式之单例模 ...

  9. Python的装饰器实例用法小结

    这篇文章主要介绍了Python装饰器用法,结合实例形式总结分析了Python常用装饰器的概念.功能.使用方法及相关注意事项 一.装饰器是什么 python的装饰器本质上是一个Python函数,它可以让 ...

  10. Python装饰器讲解

    Python装饰器讲解 定义:本质是函数,就是为其他函数添加附加功能.原则:1.不能修改被装饰的函数的源代码 2.不能修改被装饰的函数的调用方式 import time def timmer(func ...

随机推荐

  1. Dockerfile 使用 SSH docker build

    如果在书写 Dockerfile 时,有些命令需要使用到 SSH 连接,比如从私有仓库下载文件等,那么我们应该怎么做呢? Dockerfile 文件配置 为了使得 Dockerfile 文件中的命令可 ...

  2. java基础篇—基础语法

    一.关键字和保留字    1.什么是关键字? 通俗来说就是带有特殊含义的字符,有自己专门用途的单词 2.特点? 关键字全部由小写构成,以下是java官方列举出的关键字 注意: 保留关键字:指的是现有版 ...

  3. 谈软件-专家谈C/C++重构的操作与思路

    1.Refactoring: 对软件内部结构的一种调整,目的是不该被软件的可观察行为的前提上,提高其可理解性,降低其修改成本. 2.代码坏味道 2.1.不易复用 2.2.不易理解 2.3.存在冗余 3 ...

  4. 单节点部署 gpmall 商城

    个人名片: 对人间的热爱与歌颂,可抵岁月冗长 Github‍:念舒_C.ying CSDN主页️:念舒_C.ying 个人博客 :念舒_C.ying 1 修改主机名: [root@localhost ...

  5. 使用Supervisor监控mysql

    Supervisor安装教程参考:https://www.cnblogs.com/brad93/p/16639953.html mysql安装教程参考:https://www.cnblogs.com/ ...

  6. 线上服务异常的定位、处理与优化的探索 - 第三章 Java虚拟机

    Java虚拟机   之所以引入关于JVM的篇章,是发现多数项目发生的线上问题很大的几率源自JVM调优配置不当引起.对JVM的内存模型.GC垃圾回收机制.调优方式有一个系统化的了解后,可以快速处理或避免 ...

  7. [百度营]AI studio用法提醒(自用)

    持久化安装 需要设置持久化路径: !mkdir /home/aistudio/external-libraries !pip install beautifulsoup4 -t /home/aistu ...

  8. VRRP原理和实战

    一.VRRP基本概述 ·VRRP能够在不改变组网的情况中,将多台路由器虚拟成一个虚拟路由器,通过配置虚拟路由器的IP地址为默认网关,实现网关的备份. ·协议版本:VRRPv2(常用)和VRRPv3 · ...

  9. @ApiImplicitParams注解的详细使用

    一.@ApiImplicitParams注解的详细使用 业务需求: 1.根据服务员类别id(单个id)+服务员星级id(id的list)查询对应的服务员列表 1.controller代码: 点击查看代 ...

  10. Netty-架构设计及入门程序-3

    一.原生 NIO 存在的问题 1.NIO 的类库和 API 繁杂,使用麻烦:需要熟练掌握 Selector.ServerSocketChannel.SocketChannel.ByteBuffer等. ...