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. PHP实现CURL发送请求

    public function curl($url, $params = false, $ispost = 0) { $httpInfo = array(); //初始化 $ch = curl_ini ...

  2. c++小练习——黑白棋

    没什么好发的,发给黑白棋水一水,如果有人能发现问题就更好了. /* Othello.cpp 黑白棋,实现随时结束并判断胜负的功能 成功运行于Visual Studio 2013 */ #include ...

  3. ArcObjects SDK开发 007 自定义App-Command-Tool框架

    1.为什么再设计一套App-Command-Tool框架 为什么我们要自己再设计一套App-Command框架,而不直接使用AO API中的AxControl-ICommand这套已经非常好的框架呢? ...

  4. JavaScript入门②-函数(1)基础{浅出}

    01.JS函数基础 1.1.函数定义 函数(方法)就是一段定义好的逻辑代码,函数本身也是一个object引用对象.三种函数构造方式: ① 函数申明:function 函数名(参数){代码},申明函数有 ...

  5. log4j漏洞原理

    一.前置知识 1.JNDI接口 JNDI即Java Naming and Directory Interface(JAVA命名和目录接口),它提供一个目录系统,并将服务名称与对象关联起来,从而使得开发 ...

  6. 4.10:Spark之wordcount

    〇.概述 1.拓扑结构 2.目标 使用spark完成计数实验 一.启动环境 二.新建数据文件 三.查看文件内容 四.启动spark服务 五.编写代码 复制以下代码到shell中(复制后在终端右键-&g ...

  7. MyBatis02:流程分析、注解、代理dao实现CRUD、参数深入、传统DAO、配置

    今日内容 回顾 mybatis的自定义.分析和环境搭建 完善基于注解的mybatis mybatis的curd(基于代理dao的方式)※ mybatis的参数深入及结果集的深入 mybatis中基于传 ...

  8. CountDownLatch闭锁源码解析(基于jdk11)

    目录 CountDownLatch闭锁源码解析(基于jdk11) 1.1 CountDownLatch概述 1.2 CountDownLatch原理 1.2.1 基本结构(jdk11) 1.2.2 a ...

  9. 关于盒子动态高度与transition的问题

    今天遇到个小问题 大概要实现类似手风琴的效果 本来设计是定死的高度,直接 height:0; - > height:xxxpx;但之后要改成动态变化的高度,手风琴展开后是个列表,并且列表每行高度 ...

  10. 【Vue】启动vue项目报错: errno: -4058, code: ‘ENOENT‘, syscall: ‘spawn cmd‘

    运行vue项目(npm run dev)报错 报错如下 问题原因 缺少cmd运行程序的环境变量 解决方法在环境变量Path中加上C:\windows\system32