Python装饰器实例讲解(一)

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

小编点评

**1.多种角度讲述装饰器** * **装饰器的本质**: 装饰器是一种动态代理,它允许你对函数进行动态代理,也就是在运行时动态替换函数的实现。 * **装饰器的优点**: * 增强代码的可重用性 * 减少代码重复 * 改善代码可维护性 * **装饰器的缺点**: * 增加了代码执行的时间 * 可能影响函数的执行顺序 **2. 示例** ```python def is_prime(n): if n == 2: return True elif n == 1 or n % 2 == 0: return False for i in range(3, int(n ** 0.5) + 1, 2): if n % i == 0: return False return True def count_time(func): def wrapper(*args,**kwargs): # 记录开始时间 start_time = time() result = func(*args, **kwargs) # 记录结束时间 end_time = time() # 计算并返回执行时间 return end_time - start_time return wrapper ``` **3. 坑点** * **关键字参数**: 装饰器可以接受多个关键字参数,这些参数会添加到代理函数的形参列表中。 * **start 参数**: 这是用于控制代理函数开始执行的时间的关键字参数,默认值为 0。 * ***args 和 *kwargs**: *args 是一个元组,包含所有形参的名称,而 *kwargs 是一个字典,包含所有非形参的名称和值。 **4. 总结** 装饰器是一种强大的工具,可以帮助你编写更可维护和可重用的代码。尽管它可能增加代码执行的时间,但它可以显著提高代码的可维护性。

正文

Python装饰器实例讲解(一)

多种角度讲述这个知识,这是个系列文章

但前后未必有一定的顺承关系

部分参考网络

本文以一个小案例引出装饰器的一些特点,不涉及理论,后面再谈

案例

  • 写一个代码来求一个数是否是质数

    def is_prime(x):
        if  x == 2 :
            return True
        elif x == 1 or x % 2 == 0 :
            return False
        for i in range(3, int(x ** 0.5) + 1, 2):
            if x % i == 0:
                return False
        return True
    
  • 写个代码来计算某个数值范围内有多少个质数

    def get_prime_nums():
        return len(list(filter(is_prime,range(2,50000))))
    
  • 换一下,我们不是要学这个,我们要学装饰器

    def get_prime_nums():
        from time import time
        start_time = time()
        prime_nums = 0
        for num in range(2,50000):
            if is_prime(num):
                prime_nums = prime_nums + 1
        end_time = time()
        print(f'统计花了{end_time-start_time}时间')
        print(f'一共有{prime_nums}个质数')
    
    
    get_prime_nums()   
    # 统计花了0.025316476821899414时间
    # 一共有5133个质数
    
    
  • 你在这里会发现一个潜在的需求,可能不光是你这么一个函数有统计时间的需求,其他函数一样有,但现在这种处理方法可能要在每个目标函数上去加那段时间处理的代码,非常麻烦,那有没有好的做法呢?答案就是装饰器。

装饰器

  • 改造(对比下跟之前的区别)

  • 获取质数个数函数,不需要统计时间

    def get_prime_nums():
        prime_nums = 0
        for num in range(2,50000):
            if is_prime(num):
                prime_nums = prime_nums + 1
        print(f'一共有{prime_nums}个质数')
    
  • 写一个装饰器的函数(不用管为何这么写,以后会详细说明)

    def count_time(func):
        def wrapper():
            from time import time
            start_time = time()
            func()
            end_time = time()
            print(f'统计花了{end_time-start_time}时间')
        return wrapper
    
  • 给要加时间的函数套上这个装饰器

    @count_time
    def get_prime_nums():
        prime_nums = 0
        ... # 不重复了
    
  • 再次执行get_prime_nums()效果跟之前是一样的

  • 同样的你可以将这个装饰器运用到其他函数上去

    @count_time
    def get_odd_nums():
        odd_nums = 0
        for num in range(2,50000):
            if num % 2 == 1:
                odd_nums = odd_nums + 1
        print(f'一共有{odd_nums}个奇数')
    
    get_odd_nums()
    
  • 完了吗,没有。可能性还有很多,主要是被装饰函数的变化导致了装饰器本身要随之适应变化。

装饰器改造一

  • 如果被装饰的函数有返回值呢?

    @count_time
    def get_prime_nums():
        prime_nums = 0
        for num in range(2,50000):
            if is_prime(num):
                prime_nums = prime_nums + 1
        return prime_nums
    
  • 此时你直接调用函数,而不改造装饰器的话,是无法得到这个数量的

    get_prime_nums()  # 统计花了0.032898664474487305时间
    
    print(get_prime_nums())
    # 统计花了0.039182424545288086时间
    # None
    
  • 改造装饰器

  • 如何改造呢?你应该要去理解装饰器的运行原理(没那么复杂,但我们这个课不深入,仅作为案例给你展示)

    def count_time(func):
        def wrapper():
            from time import time
            start_time = time()
            result = func()  # 改动1: 用一个变量来接收func()的返回
            end_time = time()
            print(f'统计花了{end_time-start_time}时间')
            return result # 改动2: return出去
        return wrapper
    
  • 此时你再执行

    print(get_prime_nums())
    # 统计花了0.054421424865722656时间
    # 5133 就能看到这个返回值了
    
    
  • 完了吗?还没有,如果我们的被装饰函数有参数呢?

装饰器改造二

  • 你的被装饰函数存在参数

    @count_time
    def get_prime_nums(end):
        prime_nums = 0
        for num in range(2,end):
            if is_prime(num):
                prime_nums = prime_nums + 1
        return prime_nums
    
    
    print(get_prime_nums(50000))
    
  • 其实在IDE中get_prime_nums(50000)就会提示你意外实参

  • 执行结果

    Traceback (most recent call last):
      File "...\demo.py", line 37, in <module>
        print(get_prime_nums(50000))
    TypeError: wrapper() takes 0 positional arguments but 1 was given
    
  • 这是初学者最困惑的地方了,等我们讲了原理(或者说诀窍)你应该就非常清楚为何会这样报错了

  • 怎么修改呢?

    def count_time(func):
        def wrapper(*args): # 改动1: 增加一个不定参数
            from time import time
            start_time = time()
            result = func(*args) # func也增加
            end_time = time()
            print(f'统计花了{end_time-start_time}时间')
            return result
        return wrapper
    
  • 再次执行,就ok了

    print(get_prime_nums(50000)) 
    # 统计花了0.029825448989868164时间
    # 5133
    
  • 但是要注意,这样的话,如果你的被装饰函数是之前的没有参数的情况,是会报错的

    # 回到过去
    @count_time
    def get_prime_nums():
        prime_nums = 0
        for num in range(2,50000):
            if is_prime(num):
                prime_nums = prime_nums + 1
        return prime_nums
    print(get_prime_nums(50000))
    
  • 报错

    Traceback (most recent call last):
      File "...\demo.py", line 37, in <module>
        print(get_prime_nums(50000))
      File "...\demo.py", line 22, in wrapper
        result = func(*args)
    TypeError: get_prime_nums() takes 0 positional arguments but 1 was given
    
    进程已结束,退出代码为 1
    
    
  • 但由于是*args,你改成多个参数倒是可以的

    @count_time
    def get_prime_nums(start,end):
        prime_nums = 0
        for num in range(start,end):
            if is_prime(num):
                prime_nums = prime_nums + 1
        return prime_nums
    
    
    print(get_prime_nums(2,50000))   # 可以执行
    

  • 如果你这样调用

    print(get_prime_nums(start=2,end=50000))
    
  • 报错

    Traceback (most recent call last):
      File "...\demo.py", line 37, in <module>
        print(get_prime_nums(start=2,end=50000))
    TypeError: wrapper() got an unexpected keyword argument 'start'
    
    进程已结束,退出代码为 1
    
    
  • 可以这样修改你的装饰器

    def count_time(func):
        def wrapper(*args,**kwargs):  # 加个关键字参数
            from time import time
            start_time = time()
            result = func(*args,**kwargs) # 这样也要加
            end_time = time()
            print(f'统计花了{end_time-start_time}时间')
            return result
        return wrapper
    

说在最后

  • 这个案例是入门的,讲解了装饰器的一些简单使用
  • 但,留了一些坑,你可能未必知道为何要这么修改,装饰器是怎么调度的等等
  • 且听下回分解

与Python装饰器实例讲解(一)相似的内容:

Python装饰器实例讲解(一)

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

Python装饰器实例讲解(二)

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

Python装饰器实例讲解(三)

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

< 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函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象.它经常用于有迫切需求的场