热门标签 | 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之间进行共享的时候,需要锁来保护它被多次访问(临界区)。这个锁可以在装饰器中编写,代码如下:
这里写图片描述


推荐阅读
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板
    本文介绍了在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板的方法和步骤,包括将ResourceDictionary添加到页面中以及在ResourceDictionary中实现模板的构建。通过本文的阅读,读者可以了解到在Xamarin XAML语言中构建控件模板的具体操作步骤和语法形式。 ... [详细]
  • 全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件
    本文旨在全面介绍Windows内存管理机制及C++内存分配实例中的内存映射文件。通过对内存映射文件的使用场合和与虚拟内存的区别进行解析,帮助读者更好地理解操作系统的内存管理机制。同时,本文还提供了相关章节的链接,方便读者深入学习Windows内存管理及C++内存分配实例的其他内容。 ... [详细]
  • 本文详细介绍了SQL日志收缩的方法,包括截断日志和删除不需要的旧日志记录。通过备份日志和使用DBCC SHRINKFILE命令可以实现日志的收缩。同时,还介绍了截断日志的原理和注意事项,包括不能截断事务日志的活动部分和MinLSN的确定方法。通过本文的方法,可以有效减小逻辑日志的大小,提高数据库的性能。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • iOS超签签名服务器搭建及其优劣势
    本文介绍了搭建iOS超签签名服务器的原因和优势,包括不掉签、用户可以直接安装不需要信任、体验好等。同时也提到了超签的劣势,即一个证书只能安装100个,成本较高。文章还详细介绍了超签的实现原理,包括用户请求服务器安装mobileconfig文件、服务器调用苹果接口添加udid等步骤。最后,还提到了生成mobileconfig文件和导出AppleWorldwideDeveloperRelationsCertificationAuthority证书的方法。 ... [详细]
  • 本文介绍了一种轻巧方便的工具——集算器,通过使用集算器可以将文本日志变成结构化数据,然后可以使用SQL式查询。集算器利用集算语言的优点,将日志内容结构化为数据表结构,SPL支持直接对结构化的文件进行SQL查询,不再需要安装配置第三方数据库软件。本文还详细介绍了具体的实施过程。 ... [详细]
  • 使用eclipse创建一个Java项目的步骤
    本文介绍了使用eclipse创建一个Java项目的步骤,包括启动eclipse、选择New Project命令、在对话框中输入项目名称等。同时还介绍了Java Settings对话框中的一些选项,以及如何修改Java程序的输出目录。 ... [详细]
  • 背景应用安全领域,各类攻击长久以来都危害着互联网上的应用,在web应用安全风险中,各类注入、跨站等攻击仍然占据着较前的位置。WAF(Web应用防火墙)正是为防御和阻断这类攻击而存在 ... [详细]
  • 超级简单加解密工具的方案和功能
    本文介绍了一个超级简单的加解密工具的方案和功能。该工具可以读取文件头,并根据特定长度进行加密,加密后将加密部分写入源文件。同时,该工具也支持解密操作。加密和解密过程是可逆的。本文还提到了一些相关的功能和使用方法,并给出了Python代码示例。 ... [详细]
  • python3 logging
    python3logginghttps:docs.python.org3.5librarylogging.html,先3.5是因为我当前的python版本是3.5之所 ... [详细]
  • 精讲代理设计模式
    代理设计模式为其他对象提供一种代理以控制对这个对象的访问。代理模式实现原理代理模式主要包含三个角色,即抽象主题角色(Subject)、委托类角色(被代理角色ÿ ... [详细]
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社区 版权所有