热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

AdvancedPython(1)——装饰器(Decorator)

装饰器,作为Python中的一个非常重要的功能,在web系统,日志打印等领域中有着很广泛的应用,比如Flask和Django

装饰器,作为Python中的一个非常重要的功能,在web系统,日志打印等领域中有着很广泛的应用,比如Flask和Django框架的代理机制就是使用了装饰器。这里,我将总结《Expert Python Programming》第2版的装饰器部分我认为重点的内容,并配合例子进行说明。


1、Python的装饰器(Decorator)是什么?

Python装饰器,简单来讲,就是使函数包装类的方法包装(一个函数,接受函数并返回其增强函数 (对原函数增加一些日志信息或更高级的处理的方式))变得容易阅读和理解。其最初的使用场景是在方法定义的开头,将一个类的方法定义为静态方法/类方法——假设代码最后的staticmethodclassmethod是预先定义好的:

class WithOutDecorators:def static_method_A():print("This is a static method A")def class_method_B():print("This is a class method B")# 对此静态方法进行封装 static_method_A = staticmethod(static_method_A)# 对类方法进行封装class_method_B = classmethod(class_method_B)

如果用装饰器(decorator)写的话,可以想到,对于有很多静态方法和类方法的类来讲,采取decorator机制会使得代码可读性更强、容易理解的优点。

class WithDecorators:@staticmethoddef static_method_A():print("This is a static method A")@classmethoddef class_method_B():print("This is a class method B")

2、装饰器(Decorator)一般用法和可能实现

装饰器(Decorator)通常是一个命名的对象——(注意,不允许使用lambda表达式!),在被装饰函数调用时,接受单一参数,并返回另一个可调用的对象(callable object)。所以,任何可调用对象(类内部实现了__call__方法的对象)都可以用作装饰器。他们返回的对象也不是简单的函数,也可能是实现了自己的__call__方法的复杂类的实例(instance)。

事实上,任何函数都可以用作decorator,因为python没有规定装饰器的返回类型。因此可以基于此做一些有趣的实验——比如将str()作为装饰器,虽然会报错,但是也是蛮有意思。


  • 作为函数
    最简单也是最常用的用法,就是编写一个函数作为Decorator,通用模式为:


    # 1 装饰器函数 common useddef mydecorator(func):def wrapped(*args, **kwargs):print("调用函数之前处理...")# 在函数调用之前做一些处理result = func(*args, **kwargs)print('函数调用完成之后处理...')# 函数调用完成之后,做点什么处理return resultreturn wrapped@mydecorator
    def calc(n):assert type(n) == int, "输入务必是integer"number = n*2print(number)return numbercalc(10)# 调用函数之前处理...# 20# 函数调用完成之后处理...

  • 作为类
    虽然decorator总是几乎可以用函数实现,但是某些情况下,使用用户self-define的类可能会更好————比如需要复杂参数化或者依赖于特定状态的decorator,那么这种说法往往是对的。
    通用模式如下:

    # 2 装饰器作为类, 当decorator需要复杂的参数化或依赖于特定状态的时候,需要这么做class DecoratorAsClass(object):def __init__(self, func):self.func = funcdef __call__(self, *args, **kwargs):# 调用原始函数之前做些什么print("类装饰器:调用函数之前处理...")result = self.func(*args, **kwargs)# 调用原始函数之后做些什么print("类装饰器:调用函数之后处理...")return result@DecoratorAsClass
    def calc2(n):assert type(n) == int, "输入务必是integer"number = n*10print(number)return numbercalc2(50)# 类装饰器:调用函数之前处理...# 500# 类装饰器:调用函数之后处理...

  • 参数化装饰器
    实际代码中,通常需要使用参数化的装饰器。如果用函数作为装饰器的话,那么解决方式很简单————需要在wrap一层(也就是从之前2层变成了3层):


    # 3 参数化装饰器————我的理解就是在函数式装饰器基础上,再加一层# 以一个重复打印的装饰器为例进行说明def repeat(number):"""多次重复执行装饰函数重复次数为number的数目。"""def actual_decorator(func):def wrapped(*args, **kwargs):result = Noneprint("参数化装饰器:调用函数之前处理...")# 在函数调用之前做一些处理for i in range(number):result = func(*args, **kwargs)print('参数化装饰器:函数调用完成之后处理...')# 函数调用完成之后,做点什么处理return resultreturn wrappedreturn actual_decorator# 务必注意,及时参数化decorator有默认值,也必须加括号——————@repeat(), 而不可以使用@repeat这种形式@repeat(3)
    def calc3(n):assert type(n) == int, "输入务必是integer"print(n)return ncalc3(100)#参数化装饰器:调用函数之前处理...#100#100#100#参数化装饰器:函数调用完成之后处理...

  • 保存内省的装饰器(保留被封装的原函数名称和文档)
    这块没细看,有兴趣的同学可以看书学习。

3、装饰器(Decorator)使用和有用例子

由于装饰器在module被首次读取的时候由解释器(intepreter)来加载,所以其使用受限于通用的包装器(wrapper),如果装饰器与方法的类或所增强的函数签名绑定,应该将其重构为常规的可调用对象,以避免复杂性。

常规的装饰器模式如下:

  • 参数检查
  • 缓存
  • 代理
  • 上下文提供

(1)参数检查

其目的是:检查函数接受或者返回的参数,在特定的上下文中执行有用。书中的例子是:“当一个函数通过XML-RPC来调用,那么Python无法像静态语言C/C++一样,直接提供其完整的签名。当XML-RPC客户端请求函数签名时,就需要用这功能来提供内省能力。”

也就是说,在一些web应用中,采用Decorator机制的参数检查会发挥作用,这块我暂时没看。

(2)缓存

缓存和参数检查十分相似,不过,它重点关注的是不受状态影响的函数,即唯一确定的参数一定会产生唯一确定的结果,这就是函数式编程(functional programming)的风格。

因此,缓存decorator可以将输出与计算其所需要的参数放在一起,并在后续的调用中直接返回它。这种行为被称为memoizing
下面就是一个装饰器的实现,其中
is_obsolete函数是判断某组(函数,参数)的计算结果是否超过cache的固定时间,如果超过就返回yes。
compute_key的作用是计算某组(函数,参数)的签名。
这里写图片描述

这里写图片描述

一个明显的问题就是,cache到时了,字典中对应的结果没被擦除,这块可以根据自己的需要个性定制。有想更深入学习缓存装饰器的,建议看一下functools里面的lru_cache,它也是以装饰器的形式使用。网上也有比较详实的说明介绍。


(3)代理(Web)

代理装饰器使用全局机制来标记和注册函数。举个例子,一个根据当前用户来保护代码访问的安全层可以使用集中式检查器和相关的可调用对象要求的权限来实现。

这里写图片描述

这种模型通常用于Python的Web框架中,用于定义可发布类的安全性。例如,Django提供decorator来保护函数访问的安全

下面的简单例子是,当前用户被保存在全局变量(用到了python的全局机制,globals())。在方法被访问的时候decorator会检查其角色,看是否符合。
这里写图片描述
对复杂情况,可以对每个角色的分工设置权限,比如某个用户只能看到网页A,B,而root用户能看到网页A,B,C,D这种设置。

(4) 上下文提供者(通常被上下文管理器(with …)所替代)

上下文装饰器确保函数可以运行在正确的上下文中,或者在函数前后运行一些代码。换句话说,它设定并复位一个特定的执行环境。

举个例子,当一个数据需要在多个thread之间进行共享的时候,需要锁来保护它被多次访问(临界区)。这个锁可以在装饰器中编写,代码如下:
这里写图片描述


推荐阅读
  • 提升Python编程效率的十点建议
    本文介绍了提升Python编程效率的十点建议,包括不使用分号、选择合适的代码编辑器、遵循Python代码规范等。这些建议可以帮助开发者节省时间,提高编程效率。同时,还提供了相关参考链接供读者深入学习。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文介绍了使用Python根据字典中的值进行排序的方法,并给出了实验结果。通过将字典转化为记录项,可以按照字典中的值进行排序操作。实验结果显示,按照值进行排序后的记录项为[('b', 2), ('a', 3)]。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • 闭包一直是Java社区中争论不断的话题,很多语言都支持闭包这个语言特性,闭包定义了一个依赖于外部环境的自由变量的函数,这个函数能够访问外部环境的变量。本文以JavaScript的一个闭包为例,介绍了闭包的定义和特性。 ... [详细]
  • 怎么在PHP项目中实现一个HTTP断点续传功能发布时间:2021-01-1916:26:06来源:亿速云阅读:96作者:Le ... [详细]
  • 31.项目部署
    目录1一些概念1.1项目部署1.2WSGI1.3uWSGI1.4Nginx2安装环境与迁移项目2.1项目内容2.2项目配置2.2.1DEBUG2.2.2STAT ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • 本文介绍了在CentOS 6.4系统中更新源地址的方法,包括备份现有源文件、下载163源、修改文件名、更新列表和系统,并提供了相应的命令。 ... [详细]
  • 本文介绍了一种轻巧方便的工具——集算器,通过使用集算器可以将文本日志变成结构化数据,然后可以使用SQL式查询。集算器利用集算语言的优点,将日志内容结构化为数据表结构,SPL支持直接对结构化的文件进行SQL查询,不再需要安装配置第三方数据库软件。本文还详细介绍了具体的实施过程。 ... [详细]
  • 本文介绍了使用Spark实现低配版高斯朴素贝叶斯模型的原因和原理。随着数据量的增大,单机上运行高斯朴素贝叶斯模型会变得很慢,因此考虑使用Spark来加速运行。然而,Spark的MLlib并没有实现高斯朴素贝叶斯模型,因此需要自己动手实现。文章还介绍了朴素贝叶斯的原理和公式,并对具有多个特征和类别的模型进行了讨论。最后,作者总结了实现低配版高斯朴素贝叶斯模型的步骤。 ... [详细]
  • 全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件
    本文旨在全面介绍Windows内存管理机制及C++内存分配实例中的内存映射文件。通过对内存映射文件的使用场合和与虚拟内存的区别进行解析,帮助读者更好地理解操作系统的内存管理机制。同时,本文还提供了相关章节的链接,方便读者深入学习Windows内存管理及C++内存分配实例的其他内容。 ... [详细]
author-avatar
兰雪儿MM_840
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有