1.什么是装饰器?

器即函数

装饰即修饰,意指为其他函数添加新功能

装饰器定义:本质就是函数,功能是为其他函数添加新功能

2.遵循的原则

装饰器必须遵循以下两个原则:

  • 不修改被装饰函数的源代码(开放封闭原则)
  • 为被装饰函数添加新功能后,不修改被修饰函数的调用方式

3.一步一步剖析Python中装饰器原理

我们知道,装饰器就是给其他函数动态增加功能的函数。其本质还是函数。下面我们就依据装饰器必须遵循的两个原则,手动实现一个装饰器,以此来剖析装饰器的内部原理。

前置知识储备:

装饰器=高阶函数+函数嵌套+闭包

3.1需求

假设我们需要给test函数增加一个计算该函数运行时间功能,即在函数运行完毕后输出该函数运行的时间,但又不希望修改原本函数的定义,此时,我们要定义一个能计算函数运行时间的装饰器timer

test函数如下:

def test():
res = 0
for i in range(100):
res += i
print(res)

OK,需求有了,我们现在试着动手实现一下。

3.2 低级版实现

import time

# 定义timer函数,用来计算test函数的运行时间
def timer(func):
start_time = time.time()
func()
end_time = time.time()
print('函数运行时间为:%s' %(start_time-end_time)) def test():
res = 0
for i in range(100):
res += i
print(res) timer(test)

以上代码中,当我们想运行test函数并计算该函数的运行时间时,我们只需调用timer(test)函数即可,虽然这样算是实现了需求,但是却违反了装饰器第二条原则:为被装饰函数添加新功能后,不修改被修饰函数的调用方式。最初调用test函数只需test()即可,而现在增加了新功能后必须timer(test)这样调用才行,显然这样是不行的,代码还需要继续改进!

3.3 进阶版实现

试想一下,如果我们像timer(test)这样调用timer()函数时,并不让timer()函数直接执行,而是让它返回一个函数的地址,然后我们在把这个地址赋值给一个变量,然后再以变量( )的方式调用。这样做的好处就是:这个变量名我们可以自定义,假如我们把这个变量名还叫test,那么后续调用test()就等于调用了上面代码中的timer(test)。这样我们就遵循了装饰器的两大原则:既不修改原函数的代码,也没有修改原函数的调用方式。

话不多说,来试试看:

import time

def timer(func):
def wrapper():
start_time = time.time()
func()
end_time = time.time()
print('函数运行时间为:%s' % (start_time - end_time)) return wrapper def test():
res = 0
for i in range(100):
res += i
print(res) # timer(test)返回函数地址wrapper,将地址赋值给变量test
test = timer(test)
# 调用test()即执行了返回的函数地址wrapper函数
test()

3.4 高阶版完美实现

通过进阶版的实现,我们想计算哪个函数的运行时间,只需在该函数调用之前加一行函数名=timer(函数名)代码即可。

但如果函数非常多的话,这样还是有些繁琐。其实这个问题Python早就替我们想到了,Python提供一个语法糖@有了这个语法糖,我们只需在被装饰函数定义前加@timer(函数名)即可,@timer(函数名)就等于函数名=timer(函数名)

import time

def timer(func):
def wrapper():
start_time = time.time()
func()
end_time = time.time()
print('函数运行时间为:%s' % (start_time - end_time)) return wrapper @timer # @timer等同于test = timer(test)
def test():
res = 0
for i in range(100):
res += i
print(res) test()

3.5 总结

通过以上三步实现,我们即可总结出装饰器原理:

@timer放到被装饰函数test的定义之前,就相当于执行了test = timer(test),由于timer()是一个高阶函数,它返回一个函数地址,所以原来的test函数仍然存在,只是现在同名的test变量指向了新的函数,于是调用test()将执行在timer()函数中返回的wrapper()函数,在wrapper()函数内,首先执行原始函数,再计算函数运行时间。

4. 装饰器执行时机

# 定义装饰器
def timer(func):
print('----正在装饰----')
def wrapper():
start_time = time.time()
func()
end_time = time.time()
print('函数运行时间为:%s' % (start_time - end_time)) return wrapper @timer # @timer等同于test = timer(test)
def test():
res = 0
for i in range(100):
res += i
print(res)

执行以上代码,我们可以发现,当没有调用test()函数时,装饰器也会执行,也就是说,只要python解释器执行到了@timer这句代码,那么就会自动进行装饰,而不是等到调用的时候才装饰,在函数调用之前就已经对函数装饰完毕了.

5. 多个装饰器执行顺序

如果一个函数被多个装饰器装饰时,那么装饰顺序是怎样的呢?看以下代码:

# 定义装饰器1:加粗
def makeBold(func):
print('-----准备装饰1-----')
def wrapper():
print('-----装饰中1-----')
return '<b>' + func() + '</b>'
return wrapper # 定义装饰器2:斜体
def makeItalic(func):
print('-----准备装饰2-----')
def wrapper():
print('-----装饰中2-----')
return '<i>' + func() + '</i>'
return wrapper @makeBold
@makeItalic
def test():
return 'Hello World' print(test())
# 输出
# -----准备装饰2-----
# -----准备装饰1-----
# -----装饰中1-----
# -----装饰中2-----
# <b><i>Hello World</i></b>

运行以上代码,从输出结果上来看,当一个函数被多个装饰器装饰时,装饰的时候是从里往外装,装饰的结果也是一层一层往外套,但是装饰器执行时却是从外往里执行。

6.装饰器基本框架

# 装饰器基本框架
def decorator(func):
def wrapper():
func()
return wrapper

7.被装饰函数有参数

# 被装饰函数有参数
def decorator(func):
def wrapper(a,b):
print('---装饰器---')
func(a,b)
return wrapper @decorator
def test(a,b):
print('----test--%s+%s' %(a,b)) test(1,2)

我们知道,当调用test(1,2)函数时,其实是调用了wrapper函数,所以要给wrapper函数传入相应的参数,而wrapper函数内部的func函数才是真正调用test函数,所以也要给func传入相应的参数。

8. 被装饰函数有return

如果被装饰的函数有返回值,那么我们需要在装饰器中将返回值接收一下。

# 被装饰函数有参数
def decorator(func):
def wrapper(a,b):
print('---装饰器---')
ret = func(a,b)
return ret
return wrapper @decorator
def test(a,b):
return a + b test(1,2)

通常情况下,为了装饰器的通用性,无论被装饰函数是否有返回值,都会在装饰器中加上return

9. 通用装饰器

无论被装饰函数是否有return,有多少个参数,均可以适用的通用装饰器。

# 通用装饰器

def decorator(func):
def wrapper(*args, **kwargs):
ret = func(*args, **kwargs)
return ret return wrapper # 被装饰函数无参数,无返回值
@decorator
def test1():
print('----test--') # 被装饰函数有参数,无返回值
@decorator
def test2(a, b):
print('----test--%s+%s' % (a, b)) # 被装饰函数无参数,有返回值
@decorator
def test3():
return 'test' # 被装饰函数有参数,有返回值
@decorator
def test4(a, b):
return a + b test1() # 输出----test--
test2(1, 2) # 输出----test--1+2
print(test3()) # 输出test
print(test4(1, 2)) # 输出3

10. 装饰器本身带有参数

如果装饰器本身需要传入参数,那就需要编写一个返回装饰器的高阶函数。

# 装饰器本身带有参数

def decorator(text):
def wrapper(func):
def inner(*args, **kwargs):
print('--%s是装饰器的参数---' %text)
ret = func(*args, **kwargs)
return ret
return inner
return wrapper @decorator('execute')
def test(a, b):
return a + b print(test(1, 2))
# 输出
# --execute是装饰器的参数---
# 3

和两层嵌套的装饰器相比,3层嵌套的效果是这样的:

@decorator放到test()函数的定义处,相当于执行了语句:

test = decorator('execute')(test)(),首先执行decorator('execute'),返回的是wrapper函数,再调用返回的函数,参数是test函数,返回值最终是inner函数。最后再执行inner函数。

11.装饰器应用

我们可以编写一个验证用户是否登录的装饰器,在用户调用函数之前,先验证用户是否登录,如果登录,则可正常访问,如果未登录,则提示用户先登录。

# 装饰器应用实例:验证是否登录
user_list = [
{'name': 'haha', 'password': '123'}
] current_user = {'name': None, 'login': False} def auth_dec(func):
def wrapper(*args, **kwargs):
if current_user['name'] and current_user['login']:
ret = func(*args, **kwargs)
return ret
else:
print('您还未登录,请您先登录,再访问页面!')
username = input('请输入用户名:')
pw = input('请输入密码:') for user_dic in user_list:
if username == user_dic['name'] and pw == user_dic['password']:
current_user['name'] = username
current_user['login'] = True
ret = func(*args, **kwargs)
return ret
break
print('用户名或密码错误') return wrapper @auth_dec
def index():
print('欢迎来到主界面') def home():
print('欢迎来到家园') index()
home()

(完)

python学习之【第十四篇】:Python中的装饰器的更多相关文章

  1. Python学习日记(二十六) 封装和几个装饰器函数

    封装 广义上的封装,它其实是一种面向对象的思想,它能够保护代码;狭义上的封装是面向对象三大特性之一,能把属性和方法都藏起来不让人看见 私有属性 私有属性表示方式即在一个属性名前加上两个双下划线 cla ...

  2. Python 学习笔记(十四)Python类(三)

    完善类的内容 示例: #! /usr/bin/env python # coding =utf-8 #通常类名首字母大写 class Person(object): """ ...

  3. Python 学习笔记(十四)Python类(二)

    创建简单的类 新式类和经典类(旧式类) Python 2.x中默认都是经典类,只有显式继承了object才是新式类 Python 3.x中默认都是新式类,经典类被移除,不必显式的继承object 新式 ...

  4. Python 学习笔记(十四)Python类(一)

    基本概念 问题空间:问题空间是问题解决者对一个问题所达到的全部认识状态,它是由问题解决者利用问题所包含的信息和已贮存的信息主动的地构成的. 初始状态:一开始时的不完全的信息或令人不满意的状况: 目标状 ...

  5. Python学习笔记(十四)

    Python学习笔记(十四): Json and Pickle模块 shelve模块 1. Json and Pickle模块 之前我们学习过用eval内置方法可以将一个字符串转成python对象,不 ...

  6. Egret入门学习日记 --- 第十四篇(书中 5.4~5.6节 内容)

    第十四篇(书中 5.4~5.6节 内容) 书中内容: 总结 5.4节 内容重点: 1.如何编写自定义组件? 跟着做: 重点1:如何编写自定义组件? 文中提到了重要的两点. 好,我们来试试看. 第一步, ...

  7. Python学习【第十二篇】模块(2)

    序列化 1.什么是python序列化? 把变量从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling 序列化就是将python的数据类型转换成字符串 反序列化就是将字符串转换成 ...

  8. Python学习笔记(十四):模块高级

    以Mark Lutz著的<Python学习手册>为教程,每天花1个小时左右时间学习,争取两周完成. --- 写在前面的话 2013-7-23 21:30 学习笔记 1,包导入是把计算机上的 ...

  9. Python之路(第二十四篇) 面向对象初级:多态、封装

    一.多态 多态 多态:一类事物有多种形态,同一种事物的多种形态,动物分为鸡类,猪类.狗类 例子 import abc class H2o(metaclass=abc.ABCMeta): ​ def _ ...

  10. Python基础笔记系列十四:python无缝调用c程序

    本系列教程供个人学习笔记使用,如果您要浏览可能需要其它编程语言基础(如C语言),why?因为我写得烂啊,只有我自己看得懂!! python语言可以对c程序代码进行调用,以弥补python语言低性能的缺 ...

随机推荐

  1. Vue入门教程 第一篇 (概念及初始化)

    注:为了本教程的准确性,部分描述引用了官网及网络内容. 安装Vue 1.使用npm安装vue: npm install vue 2.下载使用js文件: https://vuejs.org/js/vue ...

  2. 常见的javascript跨站

    第一类: <img src=javascript:alert() /> <iframe src=javascript:alert()></iframe> <s ...

  3. java IO、NIO、AIO详解

    概述 在我们学习Java的IO流之前,我们都要了解几个关键词 同步与异步(synchronous/asynchronous):同步是一种可靠的有序运行机制,当我们进行同步操作时,后续的任务是等待当前调 ...

  4. COGS 2095. 不平凡的引线

    2095. 不平凡的引线 ★☆   输入文件:firelead.in   输出文件:firelead.out   简单对比 时间限制:1 s   内存限制:256 MB [题目描述] 这里说的引线是炮 ...

  5. OFD电子文档阅读器功能说明(采用WPF开发,永久免费)

    特别说明 ofd阅读器开发语言为c#,具有完全自主产权,没有使用第三方ofd开发包.可以根据你的需求快速定制开发.本阅读器还在开发完善阶段,如有任何问题,可以联系我QQ:13712486.博客:htt ...

  6. 用go语言爬取珍爱网 | 第二回

    昨天我们一起爬取珍爱网首页,拿到了城市列表页面,接下来在返回体城市列表中提取城市和url,即下图中的a标签里的href的值和innerText值. 提取a标签,可以通过CSS选择器来选择,如下: $( ...

  7. JVM垃圾回收(下)

    接着上一篇,介绍完了 JVM 中识别需要回收的垃圾对象之后,这一篇我们来说说 JVM 是如何进行垃圾回收. 首先要在这里介绍一下80/20 法则: 约仅有20%的变因操纵着80%的局面.也就是说:所有 ...

  8. from..import 语句

    from..import 语句 如果你希望直接将 argv 变量导入你的程序(为了避免每次都要输入 sys.),那么你可以通过使用 from sys import argv 语句来实现这一点. 警告: ...

  9. 第六版PMBOK中工具与技术的介绍:数据收集数据分析数据表现

    数据收集技术: 1.头脑风暴:收集关于项目方法的创意和解决方案.2.焦点小组:召集预定的相关方和主题专家,了解他们对所讨论的产品服务或成果的期望和态度.主持人引导大家互动式讨论.3.访谈:通过与相关方 ...

  10. vue+element 实现商品sku效果

    在网上搜索了很久,没有发现合适sku编辑的文章,只能自己写一个vue+element 的sku编辑功能.实现的效果如下图 除成本.售价.库存.货号这几个写死的属性外,可自行添加/删除商品属性,自行添加 ...