python函数式编程之装饰器(一)

简介: 1.开放封闭原则简单来说,就是对扩展开放,对修改封闭在面向对象的编程方式中,经常会定义各种函数。一个函数的使用分为定义阶段和使用阶段,一个函数定义完成以后,可能会在很多位置被调用这意味着如果函数的定义阶段代码被修改,受到影响的地方就会有很多,此时很容易因为一个小地方的修改而影响整套系统的崩溃,所以对于现代程序开发行业来说,一套系统一旦上线,系统的源代码就一定不能够再改动了。

1.开放封闭原则

简单来说,就是对扩展开放,对修改封闭

在面向对象的编程方式中,经常会定义各种函数。

一个函数的使用分为定义阶段和使用阶段,一个函数定义完成以后,可能会在很多位置被调用

这意味着如果函数的定义阶段代码被修改,受到影响的地方就会有很多,此时很容易因为一个小地方的修改而影响整套系统的崩溃,

所以对于现代程序开发行业来说,一套系统一旦上线,系统的源代码就一定不能够再改动了。

然而一套系统上线以后,随着用户数量的不断增加,一定会为一套系统扩展添加新的功能。

此时,又不能修改原有系统的源代码,又要为原有系统开发增加新功能,这就是程序开发行业的开放封闭原则,这时就要用到装饰器了。

2.什么是装饰器??

装饰器,顾名思义,就是装饰,修饰别的对象的一种工具。

所以装饰器可以是任意可调用的对象,被装饰的对象也可以是任意可调用对象

3.装饰器的作用

在不修改被装饰对象的源代码以及调用方式的前提下为被装饰对象添加新功能

原则:

1.不修改被装饰对象的源代码
2.不修改被装饰对象的调用方式

目标:

为被装饰对象添加新功能

4.装饰器的定义和使用

来看下面的代码:

    import time
    import random
    
    def index():
        time.sleep(random.randrange(1,5))
        print("welcome to index page")
    
    index()

index函数的作用是程序在随机睡眠1到5秒之后,打印一句话

现在想为index函数添加一个新功能:统计index函数的运行时间,该怎么做呢??

修改index函数如下:

    import time
    import random
    
    def index():
        start_time=time.time()
        time.sleep(random.randrange(1,5))
        print("welcome to index page")
        end_time=time.time()
        print("cost time: %s" %(end_time - start_time))
    
    index()

运行程序,执行结果如下:

welcome to index page
cost time: 2.000999927520752    

可以看到,为index函数添加新功能确实实现了,但是却违反了开放封闭原则。

在符合开放封闭原则的前提下,如果想为index函数添加新功能,此时就要使用装饰器了

修改代码

    import time
    import random
    
    def index():
        time.sleep(random.randrange(1,5))
        print("welcome to index page")
    
    def timmer():
        def inner():
            start_time=time.time()
            index()
            end_time=time.time()
            print("run time: %s " %(end_time-start_time))
        return inner
    
    f=timmer()
    f()

运行程序,查看执行结果

welcome to index page
run time: 1.0 

从程序执行结果可以看出,index函数的运行时间已经被统计出来了

但是查看源码可以知道,index函数的源码确实没有被修改,但是index的调用方式被修改了

而且还有一个问题就是,timmer这个装饰器只能被用来装饰index这个函数,如果以后想统计别的函数的运行时间,又要重新定义别的装饰器,这样也太不灵活了。

修改上面的代码

    import time
    import random
    
    def timmer(func):
        def inner():
            start_time=time.time()
            func()
            end_time=time.time()
            print("run time: %s " %(end_time-start_time))
        return inner
        
    def index():
        time.sleep(random.randrange(1,5))
        print("welcome to index page")
    
    index=timmer(index)
    index()

运行程序,查看程序执行结果

welcome to index page
run time: 4.0 

可以看到,index函数的源代码没有被修改,index函数的调用方式也没有改变,但是依然为index函数添加了统计时间的功能,这里使用的就是装饰器了。

来分析下上面代码的执行流程:

    1.导入time和random模块,定义index函数和timmer函数
    2.把原始的index函数的内存地址作为参数传给timmer函数。
    3.timmer函数内部嵌套定义一个函数inner,然后返回inner函数的内存地址
    4.timmer函数执行完成,返回timmer函数的内部函数inner的内存地址,然后把inner的内存地址赋值给index变量
    5.index是inner函数的内存地址,index变量加括号运行,实际上就是在运行inner函数
    6.运行inner函数,定义程序开始时间。
    7.执行timmer函数的变量func,在第2步知道,func这个变量就是index的内存地址,所以这里实际上是执行被装饰过后的index函数
    8.index函数执行完成,定义程序的终止时间
    9.统计并打印整个程序的执行过程中所花费的时间

这就是装饰器装饰index函数的执行流程

5.装饰器的简化使用

现在我又有另外一个函数home,现在我也想统计home函数的运行时间,可以把代码修改如下

    import time
    import random
    
    def timmer(func):
        def inner():
            start_time=time.time()
            func()
            end_time=time.time()
            print("run time: %s " %(end_time-start_time))
        return inner
    
    def index():
        time.sleep(random.randrange(1,5))
        print("welcome to index page")
    
    def home():
        time.sleep(random.randrange(1,5))
        print("welcome to home page")
        
    index=timmer(index)
    index()
    
    home=timmer(home)
    home()

运行程序,执行结果如下

welcome to index page
run time: 3.0 
welcome to home page
run time: 4.0 

可以看到,每次调用统计程序运行时间的装饰器timmer,都要先把被调用的函数的函数名作为参数传给timmer装饰器

然后再把timmer装饰器的执行结果赋值给被调用的函数名本身,最后才能调用被装饰的函数,太麻烦了有没有??

其实python中的装饰器可以简化成下面的格式

    import time
    import random
    
    def timmer(func):
        def inner():
            start_time=time.time()
            func()
            end_time=time.time()
            print("run time: %s " %(end_time-start_time))
        return inner
    
    @timmer
    def index():
        time.sleep(random.randrange(1,5))
        print("welcome to index page")
    
    @timmer
    def home():
        time.sleep(random.randrange(1,5))
        print("welcome to home page")
    
    index()
    home()

程序执行结果

welcome to index page
run time: 2.0 
welcome to home page
run time: 4.0 

可以看出,使用@加装饰器名添加到被装饰对象的上方的方式也可以为一个函数添加装饰器中定义的功能

6.多个装饰器的定义与调用

在上面的例子里,定义并调用了一个统计程序运行时间的装饰器timmer,

如果现在想为index函数添加一个用户认证的功能,可以定义一个名为auth的装饰器

    import time
    import random
    
    def auth(func):
        def wrapper():
            while True:
                user=input("Input your username>>>:").strip()
                pwd=input("Input your password>>>:").strip()
                if user== "abcd" and pwd == "abcd1234":
                    print("login successful")
                    func()
                    break
                else:
                    print("login error")
        return wrapper
    
    @auth
    def index():
        time.sleep(random.randrange(1,5))
        print("welcome to index page")
    
    index()

运行程序

    Input your username>>>:abcd             # 先输入错误的用户名和密码
    Input your password>>>:1234
    login error                             # 提示用户输入错误,登录失败
    Input your username>>>:abcd             # 让用户再次输入用户名和密码
    Input your password>>>:abcd1234
    login successful                        # 登录成功
    welcome to index page                   # 执行index函数

从程序执行结果可以看出,用户登录密码验证的装饰器auth已经定义并被成功调用了

如果想为index函数添加用户认证的功能,又想统计index函数执行时间的功能,在使用装饰器的情况下该怎么调用呢

    import time
    import random
    
    def timmer(func):
        def inner():
            start_time=time.time()
            func()
            end_time=time.time()
            print("run time: %s " %(end_time-start_time))
        return inner
    
    def auth(func):
        def wrapper():
            while True:
                user=input("Input your username>>>:").strip()
                pwd=input("Input your password>>>:").strip()
                if user== "abcd" and pwd == "abcd1234":
                    print("login successful")
                    func()
                    break
                else:
                    print("login error")
        return wrapper
    
    @timmer
    @auth
    def index():
        time.sleep(2)
        print("welcome to index page")
    
    index()

在上面的代码里,为index函数添加了两个装饰器,现在有一个问题,就是这两个装饰器究竟哪个先被调用,哪个后被调用呢??

来分析一下,

如果timmer装饰器先被调用,那么程序就会先执行timmer装饰器,然后再执行auth装饰器,提示输入用户名和密码,
    这样一来timmer装饰器统计的时间就会包括输入用户名和密码的时间,这个时间会远远大于index函数睡眠的2秒种;
如果auth装饰器先被调用,timmer装饰器后被调用,那么timmer装饰器统计的运行时间就应该只包括index函数的执行时间值应该在2秒多一点点的时间范围内

运行程序,先输入错误的用户名和密码以使用程序的执行时间加长

Input your username>>>:abcd
Input your password>>>:abcd
login error
Input your username>>>:abcd
Input your password>>>:abcd1234
login successful
welcome to index page
run time: 12.759000062942505 

从程序的执行结果可以知道,程序是先运行timmer装饰器,然后才运行auth装饰器,所以timmer统计的时间就包括了用户认证的时间,所以timmer统计到的程序运行时间远远大于index睡眠的2秒钟

所以这里得出一个结论:

当一个函数同时被两个装饰器装饰时,加上函数最上面的装饰器先执行,加在下面的装饰器先装饰

把上面例子里的timmer装饰器和auth装饰器位置互换一下

    import time
    import random
    
    def timmer(func):
        def inner():
            start_time=time.time()
            func()
            end_time=time.time()
            print("run time: %s " %(end_time-start_time))
        return inner
    
    def auth(func):
        def wrapper():
            while True:
                user=input("Input your username>>>:").strip()
                pwd=input("Input your password>>>:").strip()
                if user== "abcd" and pwd == "abcd1234":
                    print("login successful")
                    func()
                    break
                else:
                    print("login error")
        return wrapper
    
    @auth
    @timmer
    def index():
        time.sleep(2)
        print("welcome to index page")
    
    index()

运行index函数,依然先输入错误的用户名和密码,增加用户认证的时间

Input your username>>>:abcd
Input your password>>>:abcd
login error
Input your username>>>:abcd
Input your password>>>:abcd1234
login successful
welcome to index page
run time: 2.0 

可以看到,这次timmer统计到的时间只包含index函数的运行时间,不包含用户进行认证的时间

来分析一下上面例子中,index函数被timmer装饰器和auth装饰器装饰的代码装饰流程

    @auth           # index=auth(timmer(index))
    @timmer         # index=timmer(index)
    def index():
        time.sleep(2)
        print("welcome to index page")

在上面得出结论,一个函数同时被两个装饰器时,加在下面的装饰器先装饰

1.timmer装饰器装饰原始的index,可以写成:index=timmer(index)
2.在timmer装饰器中,timmer装饰器实际上是返回inner的内存地址,所以在这里,index=inner
3.timmer装饰器装饰完成后,由auth装饰器来装饰,此时可以写成index=auth(index),
4.这里auth括号里的index已经不再是原始index函数,而是已经被timmer装饰过后的index了,所以index=auth(timmer(index))
5.又因为timmer装饰的结果等于inner函数的内存地址,所以:index=auth(inner)

至此,两个装饰器的装饰过程已经知道了,来看程序的执行过程

6.程序先执行auth装饰器,进入用户认证,请用户输入用户名和密码
7.用户输入正确的用户名和密码后,开始执行func函数,也已经上面分析的inner函数
8.timmer装饰器先定义程序的开始运行时间,然后运行func函数,也就是原生的index函数
9.index函数先睡眠2秒,然后执行print语句,再定义程序的结束时间
10.最后统计并打印程序的运行时间,至此程序运行完毕。

所以这里用户输入用户名和密码的时间不会被timmer装饰器统计在内

7.被装饰函数参数的设置与定义

先来看一段代码

    import time
    
    def timmer(func):
        def inner():
            start_time=time.time()
            func()
            end_time=time.time()
            print("run time: %s " %(end_time-start_time))
        return inner
    
    @timmer
    def index():
        time.sleep(2)
        print("welcome to index page")
    
    @timmer
    def home(name):
        time.sleep(3)
        print("welcome to %s home page" % name)

如上所示,home函数添加了一个参数,而index函数并没有参数

按照正常的函数的定义与调用方式,调用index函数和home函数的方式应该是下面这种形式

index()
home("python")

然后我们运行程序就会发现,程序抛出了异常

  File "E:\python_learn\py_code\test.py", line 28, in <module>
    home("python")
TypeError: inner() takes 0 positional arguments but 1 was given

说个异常说明inner函数不需要位置参数,但是我们给了一个位置参数

回到timmer装饰器定义的部分,可以看到,timmer装饰器的内部函数确实没有定义参数

这样一来,timmer装饰器只能用于装饰没有参数的函数了,

我们可以在timmer装饰器定义的时候为inner函数添加一个参数

    import time
    
    def timmer(func):
        def inner(name):
            start_time=time.time()
            func(name)
            end_time=time.time()
            print("run time: %s " %(end_time-start_time))
        return inner
    
    @timmer
    def index():
        time.sleep(2)
        print("welcome to index page")
    
    @timmer
    def home(name):
        time.sleep(3)
        print("welcome to %s home page" % name)
    
    index()
    home("python")

但是这样一来,timmer装饰器装饰index函数的时候又会抛出异常,因为index函数没有参数

File "E:\python_learn\py_code\test.py", line 27, in <module>
index()
TypeError: inner() missing 1 required positional argument: 'name'       

在不知道被装饰函数的参数个数的情况下,即被装饰函数的参数可变长,且形式不固定的时候,

可以使用*args**kwargs,把上面的代码修改

    import time
    
    def timmer(func):
        def inner(*args,**kwargs):
            start_time=time.time()
            func(*args,**kwargs)
            end_time=time.time()
            print("run time: %s " %(end_time-start_time))
        return inner
    
    @timmer
    def index():
        time.sleep(2)
        print("welcome to index page")
    
    @timmer
    def home(name):
        time.sleep(3)
        print("welcome to %s home page" % name)
    
    index()
    home("python")

再次运行程序,查看运行结果

welcome to index page
run time: 2.0 
welcome to python home page
run time: 3.0 

由上可知,在不知道被装饰函数的参数个数时,可以使用*args**kwargs来表示任意长度任意形式的参数

8.被装饰函数的返回值

修改上面的代码,为home函数定义一个返回值,分别打印index函数和home函数的返回值

    import time
    
    def timmer(func):
        def inner(*args,**kwargs):
            start_time=time.time()
            func(*args,**kwargs)
            end_time=time.time()
            print("run time: %s " %(end_time-start_time))
        return inner
    
    @timmer
    def index():
        time.sleep(2)
        print("welcome to index page")
    
    @timmer
    def home(name):
        time.sleep(3)
        print("welcome to %s home page" % name)
        return("home func")
    
    index_res=index()
    print(index_res)
    
    home_res=home("python")
    print(home_res)

运行程序,可以看到

welcome to index page
run time: 2.0 
None
welcome to python home page
run time: 3.0 
None

可以看到,home函数中定义的返回值并没有被打印出来,显示的值为None

因为这里执行的home函数不是原始定义的home函数,而是wrapper函数的执行结果

因为wrapper函数并没有定义返回值,所以执行被装饰后的home函数并没有打印出返回值

修改代码,在timmer装饰器中定义并返回被装饰函数执行的返回值

    import time
    
    def timmer(func):
        def inner(*args,**kwargs):
            start_time=time.time()
            res=func(*args,**kwargs)
            end_time=time.time()
            print("run time: %s " %(end_time-start_time))
            return res
        return inner
    
    @timmer
    def index():
        time.sleep(2)
        print("welcome to index page")
    
    @timmer
    def home(name):
        time.sleep(3)
        print("welcome to %s home page" % name)
        return("home func")
    
    index_res=index()
    print(index_res)
    
    home_res=home("python")
    print(home_res)

再次执行函数,查看执行结果

    welcome to index page
    run time: 2.0 
    None
    
    welcome to python home page
    run time: 3.0 
    home func
    

可以看来,原始home函数中定义的返回值被打印出来了

结论:

如果被装饰函数没有定义返回值,timmer装饰器装饰后的返回值为None
而如果被装饰函数定义了返回值,则timmer装饰器装饰后则返回被装饰函数的返回值

9.wraps内置方法的作用

查看一个函数的帮助文档有两种方法

func_name.__doc__
help(func_name)

先来看一个例子,定义timmer装饰器和index函数,并且都添加了帮助文档

import time

def timmer(func):
    def inner(*args,**kwargs):
        'wrapper inner function'
        start_time=time.time()
        res=func(*args,**kwargs)
        end_time=time.time()
        print("run time: %s " %(end_time-start_time))
        return res
    return inner

def index():
    'index function'
    time.sleep(2)
    print("welcome to index page")

在index没有被timmer装饰前,来查看index的帮助文档

print(index.__doc__)

程序运行结果

index function

然后为index添加timmer装饰器,再次查看index函数的帮助文档

import time

def timmer(func):
    def inner(*args,**kwargs):
        'wrapper inner function'
        start_time=time.time()
        res=func(*args,**kwargs)
        end_time=time.time()
        print("run time: %s " %(end_time-start_time))
        return res
    return inner

@timmer
def index():
    'index function'
    time.sleep(2)
    print("welcome to index page")

print(index.__doc__)

程序运行结果

wrapper inner function

可以看到,在为index函数添加装饰器后,index函数的帮助文档变成装饰器timmer内部函数的帮助文档了

换句话说,就是原始index函数内部的数据被装饰器timmer修改了

怎么样才能在保留原始被装饰函数的数据的前提下,为函数添加新功能呢??就是python内置的wraps装饰器

导入wraps装饰器,修改上面的代码,为timmer的内部函数添加wraps装饰器,然后再次查看被装饰函数的帮助文档

import time
from functools import wraps

def timmer(func):
    @wraps(func)
    def inner(*args,**kwargs):
        'wrapper inner function'
        start_time=time.time()
        res=func(*args,**kwargs)
        end_time=time.time()
        print("run time: %s " %(end_time-start_time))
        return res
    return inner

@timmer
def index():
    'index function'
    time.sleep(2)
    print("welcome to index page")

print(index.__doc__)

运行程序,执行结果如下

index function

可以看到,index函数即使添加了装饰器,其内部的原始数据仍然没有被装饰器修改

从上面的示例可以看出,wraps装饰器的作用就是保留被装饰对象的原始数据信息

目录
相关文章
|
14天前
|
Python
深入理解Python装饰器:从入门到实践####
本文旨在通过简明扼要的方式,为读者揭开Python装饰器的神秘面纱,从基本概念、工作原理到实际应用场景进行全面解析。不同于常规的摘要仅概述内容概要,本文将直接以一段精炼代码示例开篇,展示装饰器如何优雅地增强函数功能,激发读者探索兴趣,随后深入探讨其背后的机制与高级用法。 ####
46 11
|
11天前
|
设计模式 缓存 开发者
深入浅出Python装饰器
【10月更文挑战第39天】本文将通过浅显易懂的语言和生动的比喻,带你探索Python中一个神奇而又强大的特性——装饰器。我们将一起揭开装饰器的神秘面纱,了解它的工作原理,并通过实际代码示例学习如何应用它来美化我们的代码。无论你是编程新手还是有经验的开发者,这篇文章都将为你打开一扇新的大门,让你的代码更加优雅和高效。
|
11天前
|
缓存 测试技术 数据库
深入理解Python中的装饰器
在本文中,我们将探讨Python语言中一个强大而灵活的特性——装饰器。装饰器允许开发者在不修改原有函数或方法代码的情况下增加额外的功能,这大大提高了代码的复用性和可读性。通过具体示例和应用场景的讲解,本篇文章旨在为读者提供一个关于如何使用装饰器的全面指南,包括装饰器的定义、使用场景、以及如何自定义装饰器等内容。
|
15天前
|
设计模式 Python
掌握Python中的装饰器
【10月更文挑战第34天】装饰器是Python中一种强大的工具,它允许我们在不修改原函数代码的情况下增加其功能。本文通过简单易懂的语言和实例,引导你理解装饰器的概念、种类及其应用,帮助你在编程实践中灵活使用这一高级特性。
|
6天前
|
开发框架 缓存 测试技术
Python中的装饰器:魔法般的功能增强
在Python编程中,装饰器是一种强大而灵活的工具,它允许开发者修改或扩展函数和类的行为。本文将深入探讨Python装饰器的工作原理,并通过实例演示如何创建和使用自定义装饰器来增强代码的功能性和可读性。我们将从基础概念讲起,逐步深入到高级应用,揭示装饰器背后的“魔法”,并展示它们在实际开发中的多种用途。
|
11天前
|
缓存 监控 测试技术
Python中的装饰器:功能扩展与代码复用的利器###
本文深入探讨了Python中装饰器的概念、实现机制及其在实际开发中的应用价值。通过生动的实例和详尽的解释,文章展示了装饰器如何增强函数功能、提升代码可读性和维护性,并鼓励读者在项目中灵活运用这一强大的语言特性。 ###
|
14天前
|
缓存 开发者 Python
探索Python中的装饰器:简化代码,增强功能
【10月更文挑战第35天】装饰器在Python中是一种强大的工具,它允许开发者在不修改原有函数代码的情况下增加额外的功能。本文旨在通过简明的语言和实际的编码示例,带领读者理解装饰器的概念、用法及其在实际编程场景中的应用,从而提升代码的可读性和复用性。
|
10天前
|
设计模式 缓存 开发框架
Python中的装饰器:从入门到实践####
本文深入探讨了Python中装饰器的工作原理与应用,通过具体案例展示了如何利用装饰器增强函数功能、提高代码复用性和可读性。读者将学习到装饰器的基本概念、实现方法及其在实际项目开发中的实用技巧。 ####
21 3
|
11天前
|
Python
探索Python中的装饰器:简化代码,提升效率
【10月更文挑战第39天】在编程的世界中,我们总是在寻找使代码更简洁、更高效的方法。Python的装饰器提供了一种强大的工具,能够让我们做到这一点。本文将深入探讨装饰器的基本概念,展示如何通过它们来增强函数的功能,同时保持代码的整洁性。我们将从基础开始,逐步深入到装饰器的高级用法,让你了解如何利用这一特性来优化你的Python代码。准备好让你的代码变得更加优雅和强大了吗?让我们开始吧!
18 1
|
16天前
|
设计模式 缓存 监控
Python中的装饰器:代码的魔法增强剂
在Python编程中,装饰器是一种强大而灵活的工具,它允许程序员在不修改函数或方法源代码的情况下增加额外的功能。本文将探讨装饰器的定义、工作原理以及如何通过自定义和标准库中的装饰器来优化代码结构和提高开发效率。通过实例演示,我们将深入了解装饰器的应用,包括日志记录、性能测量、事务处理等常见场景。此外,我们还将讨论装饰器的高级用法,如带参数的装饰器和类装饰器,为读者提供全面的装饰器使用指南。
下一篇
无影云桌面