Python装饰器实例讲解(三)

python,装饰,实例,讲解 · 浏览次数 : 398

小编点评

**万能公式** **函数** 函数是一等对象,在运行时创建能赋值给变量或数据结构中的元素能作为参数传给函数能作为函数的返回结果。函数在Python中,函数是一等对象,需要满足以下条件: * 在运行时创建能赋值给变量或数据结构中的元素能作为参数传给函数能作为函数的返回结果。 * 函数必须返回一个可以被执行的函数。 **装饰器** 装饰器是一种技术,它可以把一个函数包装成一个新的函数。新函数被称为 **装饰函数**。 **万能公式** 万能公式是一种技术,它可以把一个函数包装成一个新函数,同时保留了原始函数的某些特性。例如,我们可以使用万能公式将一个函数包装成一个可以执行该函数的函数,但我们可以保留该函数的返回值类型、参数类型和函数名等信息。 **示例** ```python # 定义一个装饰器 def decorate(func): def wrapper(*args, **kwargs): print('calling inner') result = func(*args, **kwargs) print('calling target') return result return wrapper # 使用装饰器包装一个函数 @decorate def my_function(x, y): return x + y # 调用装饰后的函数 result = my_function(1, 2) print(result) ``` **结果** 这会输出以下结果: ``` calling inner calling target 5 ``` **解释** 1. 我们首先定义了一个装饰器函数 `decorate`,它接受一个函数作为参数。 2. 当我们使用 `@decorate`装饰一个函数时,它会创建一个新的函数作为返回值。 3. 新的函数会把原始函数的代码复制到其内部。 4. 新的函数返回原始函数的返回值。 **注意** * 万能公式的实现依赖于 Python 的字节码执行机制。 * 万能公式可以用于包装任何类型的函数,包括可调用对象、函数和类。

正文

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装饰器实例讲解(三)相似的内容:

Python装饰器实例讲解(三)

Python装饰器实例讲解(三) 本文多参考《流畅的python》,在此基础上增加了一些实例便于理解 姊妹篇 Python装饰器实例讲解(一),让你简单的会用 Python装饰器实例讲解(二),主要讲了一个万能公式(原理) 本文其实反而是最最基础的部分,当然也回答了好几个关键的问题,也有一些是重复的

Python装饰器实例讲解(二)

Python装饰器实例讲解(二) Python装饰器实例讲解(一) 你最好去看下第一篇,虽然也不是紧密的链接在一起 参考B站码农高天的视频,大家喜欢看视频可以跳转忽略本文:https://www.bilibili.com/video/BV19U4y1d79C 一键三连哦 本文的知识点主要是 ​ 类装

Python装饰器实例讲解(一)

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

< Python全景系列-9 > Python 装饰器:优雅地增强你的函数和类

装饰器在 Python 中扮演了重要的角色,这是一种精巧的语言特性,让我们能够修改或增强函数和类的行为,无需修改它们的源代码。这篇文章将深入探讨装饰器的所有相关主题,包括装饰器的基础知识、实现与使用、工作原理,以及通过实际例子学习装饰器的独特用法。

单例模式

python实现单例模式 在Python中实现单例模式可以通过装饰器、元类或者直接在类中实现。以下是一个使用装饰器实现的单例模式示例: def singleton(cls): instances = {} def get_instance(*args, **kwargs): if cls not i

Python模块 adorner 的使用示例

模块介绍 adorner 是一个现代轻量级的 Python 装饰器辅助模块。 目前该模块仅实现了 4 个类,对应着 4 个功能:制造装饰器、执行计时、函数缓存、捕获重试。 仓库地址:https://github.com/gupingan/adorner 安装 该模块可在上方仓库中的 Releases

测试type和isinstance两个函数,那个速度更加的快

一、解决方案 通过装饰器实现二、相关知识点 isinstance()函数 1. isinstance()函数是python中的一个内置函数,作用:判断一个函数是否是一个已知类型,类似type()。 2. 语法:isinstance ( object , classinfo ) 参数: object:

Python常见面试题016. 请实现如下功能|谈谈你对闭包的理解

016. 请实现如下功能|谈谈你对闭包的理解 摘自<流畅的python> 第七章 函数装饰器和闭包 实现一个函数(可以不是函数)avg,计算不断增加的系列值的平均值,效果如下 def avg(...): pass avg(10) =>返回10 avg(20) =>返回10+20的平均值15 avg(

(转载)PYTHON修饰器的函数式编程--纪念下陈皓老师

# PYTHON修饰器的函数式编程 > 2014年的时候陈老师就写了这个python装饰器的文章,比现在的很多的文章都好,虽然他是python 2 写的,但核心思想是没变的。 > 原文:https://coolshell.cn/articles/11265.html > 谨以此文 纪念下陈皓老师(1

6.0 Python 使用函数装饰器

装饰器可以使函数执行前和执行后分别执行其他的附加功能,这种在代码运行期间动态增加功能的方式,称之为`"装饰器"(Decorator)`,装饰器的功能非常强大,装饰器一般接受一个函数对象作为参数,以对其进行增强,相当于C++中的构造函数,与析构函数。装饰器本质上是一个python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象.它经常用于有迫切需求的场