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. 2022春每日一题:Day 38

    题目[USACO17JAN]Promotion Counting P 从根节点dfs一遍,树状数组维护进入和出去时这个节点的贡献,一减就是答案 代码: #include <cstdio> ...

  2. SpringCloud——Eureka Feign Ribbon Hystrix Zuul等关键组件的学习与记录

    SpringCloud--Eureka Feign Ribbon Hystrix Zuul等关键组件的学习与记录 前言:本篇是对近日学习狂神SpringCloud教程之后的感想和总结,鉴于对Sprin ...

  3. 8、将两个字符串s1,s2进行比较,如果s1>s2,则输出一个正数。如果s1 = s2,输出零。如果s1 < s2, 输出一个负数,不用strcmp函数,输出的正数或者负数的绝对值应该是比较两字符串相应字符的ascii码的差值。

    /* 将两个字符串s1,s2进行比较,如果s1>s2,则输出一个正数.如果s1 = s2,输出零.如果s1 < s2, 输出一个负数,不用strcmp函数,输出的正数或者负数的绝对值应该是 ...

  4. selenium被某些网页检测不允许正常访问、登录等,解决办法

    网站通过什么方式检测 function b() { return "$cdc_asdjflasutopfhvcZLmcfl_"in u || d.webdriver } 通过上方的 ...

  5. Java开发学习(四十四)----MyBatisPlus查询语句之查询条件

    1.查询条件 前面我们只使用了lt()和gt(),除了这两个方法外,MybatisPlus还封装了很多条件对应的方法. MybatisPlus的查询条件有很多: 范围匹配(> . = .betw ...

  6. 解决aspnetcore-browser-refresh.js:234 WebSocket connection to 'wss://localhost:62356/Admin/' failed问题

    前言 前段时间升级了Visual Studio到v17.1.1最新版本,然后今天来运行之前的一个.net5项目一直提示:aspnetcore-browser-refresh.js:234 WebSoc ...

  7. 使用 System.Text.Json 时,如何处理 Dictionary 中 Key 为自定义类型的问题

    在使用 System.Text.Json 进行 JSON 序列化和反序列化操作时,我们会遇到一个问题:如何处理字典中的 Key 为自定义类型的问题. 背景说明 例如,我们有如下代码:   // 定义一 ...

  8. 高可用系列文章之三 - NGINX 高可用实施方案

    前文链接 高可用系列文章之一 - 概述 - 东风微鸣技术博客 (ewhisper.cn) 高可用系列文章之二 - 传统分层架构技术方案 - 东风微鸣技术博客 (ewhisper.cn) 四 NGINX ...

  9. PowerDotNet平台化软件架构设计与实现系列(14):平台建设指南

    软件开发中常见的几种不同服务模型包括SaaS(软件即服务).LaaS(许可即服务).PaaS(平台即服务).CaaS(容器即服务).IaaS(基础设施即服务)和FaaS(功能即服务). 很多人认为Ia ...

  10. nuxt.js安装使用

    首先要学会看文档,https://www.nuxtjs.cn/guide/configuration 一.创建项目,并运行 终端运行 npx create-nuxt-app <项目名> ( ...