今天我们就围绕一个来展开,那就是:装饰器

一.装饰器

  在说装饰器之前,我们先说一个软件设计的原则:开闭原则,又被称为开放封闭原则,你的代码对功能的扩展是开放的,你的程序对修改源代码是封闭的,这样的软件设计思路可以更好的维护和开发.

  开放:对功能扩展开放

  封闭:对修改代码封闭

  接下来我们来看装饰器,首先我们先模拟女娲造人

def create_people():
print('女娲很厉害,捏个泥人吹口气就成了人')
create_people()

  OK,很简单,但是问题来了,上古时期啊,天气很不稳定,这时候呢三年大旱,女娲再去造人就很困难,因为啥?没水啊,也就是说,女娲想造人必须得先和泥,浇点水才能造人

def create_people():
print('浇水') #添加了个浇水功能
print('女娲很厉害,捏个泥人吹口气就成人了')
create_people()

  搞定,但是我们来想想,是不是违背了我们最开始的那个约定(开闭原则),我们是添加了新的功能,对添加功能开放,但是修改了源代码啊,这个就不好了,因为开闭原则对修改是封闭的,那怎么办,我们可以这样:

def create_people():
# print("浇水") # 添加了个浇水功能, 不符合开闭原则了
print("女娲很厉害. 捏个泥人吹口气就成了人了")
def warter():
print("先浇水")
create_people() # 造人
# create_people() # 这个就不行了.
warter() # 访问浇水就好

  现在问题又来了,你这个函数写好了,但是由于你添加了功能,重新创建了个函数,在这之前访问过这个函数的人就必须要,修改代码来访问新的函数water() 这也要修改代码,这个也不好,依然违背开闭原则,而且如果你这个函数被大量的人访问过,你让他们所有人都去改,那你就要倒霉了,不干死你就见鬼了.

  那怎么办才能既不修改源代码,又能添加新功能呢?这个时候我们就需要一个装饰器了,装饰器的作用就是在不修改原有代码的基础上给函数扩展功能.

def create_people():
# print("浇水") # 添加了个浇水功能, 不符合开闭原则了
print("女娲很厉害. 捏个泥人吹口气就成了人了") def warter(fn):
def inner():
print("浇浇水")
fn()
print("施肥")
return inner
# # create_people() # 这个就不行了.
# warter() # 访问浇水就好了
func = warter(create_people)
func() # 有人问了. 下游访问的不依然是func么, 不还是要改么?
create_people = warter(create_people)
create_people() # 这回就好了吧

  说一下执行流程

    1.首先访问water(create_people)

    2.把你的目标函数传递给water的形参fn,那么后面如果执行了fn意味着执行了你的目标函数create_people

    3.water()执行就一句话,返回inner函数,这个时候程序认为water()函数执行完,那么前面的create_people函数名被重新覆盖成inner函数

    4.执行create_people函数实际上执行的是inner函数,而inner中访问的恰恰是我们最开始传递进去的原始的create_people函数

  结论:我们使用water函数把create_people给包装了一下,在不修改create_people的前提下完成了对create_people函数的功能添加

  这是一个装饰器的雏形,接下来我们观察一下代码,很不好理解,所以呢我们可以使用语法糖来简化我们的代码

def warter(fn):
def inner():
print("浇浇水")
fn()
print("施肥")
return inner @warter # 相当于 create_people = warter(create_people)
def create_people():
print("女娲很厉害. 捏个泥人吹口气就成了人了")
create_people() 结果:
浇浇水
女娲很厉害. 捏个泥人吹口气就成了人了
施肥

  我们发现,代码运行的结果是一样的,所谓的语法糖语法:@装饰器.类似的操作在我们生活中还有很多,比方说:约一约

# 2--> 现在啊, 这个行情比较不好, 什么牛鬼蛇神都出来了.
# 那怎么办呢? 问问金老板. 金老板在前前给大家带路
# 这时候我们就需要在约之前啊. 先问问金老板了. 所以也要给函数添加一个功能, 这里依然可以使用装饰器
def wen_jin(fn):
def inner():
print("问问金老板, 行情怎么样, 质量好不好")
fn()
print("⺾, 金老板骗我")
return inner
@wen_jin
def yue(): # 1--> 约一约函数
print("约一约")
yue()

  ok, 接下来. 我们来看一下, 我约的话, 我想约个人. 比如约wusir, 这时我们要给函数添加一个参数

# 2--> 现在啊, 这个行情比较不好, 什么牛鬼蛇神都出来了.
# 那怎么办呢? 问问金老板. 金老板在前面给大家带路
# 这时候我们就需要在约之前啊. 先问问金老板了. 所以也要给函数添加一个功能, 这里依然可以使用装饰器
def wen_jin(fn):
def inner():
print("问问金老板, 行情怎么样, 质量好不好")
fn()
print("⺾, 金老板骗我")
return inner
@wen_jin
def yue(name): # 1--> 约一约函数
print("约一约", name)
yue("wusir")
结果:
Traceback (most recent call last):
File "/Users/sylar/PycharmProjects/oldboy/fun_2.py", line 131, in
<module>
yue("wusir")
TypeError: inner() takes 0 positional arguments but 1 was given

  程序报错. 分析原因: 我们在外面访问yue()的时候实际上访问的是inner函数,而inner函数没有参数,我们给了参数,这肯定要报错的,那么该怎么改呢?给inner加上参数就好了

def wen_jin(fn):
def inner(name):
print("问问金老板, 行情怎么样, 质量好不好")
fn(name)
print("⺾, 金老板骗我")
return inner @wen_jin
def yue(name):
print("约一约", name) yue("wusir")

  这样就够了么? 如果我的yue()改成两个参数呢? 你是不是还要改inner. 对了用*args和**kwargs

def wen_jin(fn):
def inner(*args, **kwargs): # 接收任意参数
print("问问金老板, 行情怎么样, 质量好不好")
fn(*args, **kwargs) # 把接收到的内容打散再传递给目标函数
print("⺾, 金老板骗我")
return inner @wen_jin
def yue(name):
print("约一约", name) yue("wusir")

  搞定. 这时 wen_jin()函数就是一个可以处理带参数的函数的装饰器,光有参数还不够. 返回值呢?

def wen_jin(fn):
def inner(*args, **kwargs):
print("问问金老板, 行情怎么样, 质量好不好")
ret = fn(*args, **kwargs) # 执行目标函数. 获取目标函数的返回值
print("⺾, 金老板骗我")
return ret # 把返回值返回给调用者
return inner @wen_jin
def yue(name):
print("约一约", name)
return "小萝莉" # 函数正常返回 r = yue("wusir") # 这里接收到的返回值是inner返回的. inner的返回值是目标函数的返回值
print(r)

  返回值和参数我们都搞定了. 接下来给出装饰器的完整模型代码(必须记住)

# 装饰器: 对传递进来的函数进来包装. 可以在目标函数之前和之后添加任意的功能.
def wrapper(func):
def inner(*args, **kwargs):
'''在执行目标函数之前要执行的内容'''
ret = func(*args, **kwargs)
'''在执行目标函数之后要执行的内容'''
return ret
return inner # @wrapper 相当于 target_func = wrapper(target_func) 语法糖
@wrapper
def target_func():
print("我是目标函数")
# 调用目标函数
target_func()

  请把上面的代码写10遍, 并理解. 分析每一步的作用.

  接下来我们来看一看被装饰器装饰之后的函数名:

# 装饰器: 对传递进来的函数进行包装. 可以在目标函数之前和之后添加任意的功能.
def wrapper(func):
def inner(*args, **kwargs):
'''在执行目标函数之前要执行的内容'''
ret = func(*args, **kwargs)
'''在执行目标函数之后要执行的内容'''
return ret
return inner # @wrapper 相当于 target_func = wrapper(target_func) 语法糖
@wrapper
def target_func():
print("我是目标函数")
# 调用目标函数
target_func()
print(target_func.__name__) # inner
结果:
inner

  我们虽然访问的是target_func函数. 但是实际上执行的是inner函数. 这样就会给下游的程序员带来困惑. 之前不是一直执行的是target_func么. 为什么突然换成了inner. inner是个什么鬼?? 为了不让下游程序员有这样的困惑. 我们需要把函数名修改一下. 具体修改方案:

from functools import wraps # 引入函数模块
# 装饰器: 对传递进来的函数进行包装. 可以在目标函数之前和之后添加任意的功能.
def wrapper(func):
@wraps(func) # 使用函数原来的名字
def inner(*args, **kwargs):
'''在执行目标函数之前要执行的内容'''
ret = func(*args, **kwargs)
'''在执行目标函数之后要执行的内容'''
return ret
return inner # @wrapper 相当于 target_func = wrapper(target_func) 语法糖
@wrapper
def target_func()
print("我是目标函数") # 调用目标函数
target_func()
print(target_func.__name__) # 不再是inner. 而是target_func了 @wrapper
def new_target_func():
print("我是另一个目标函数")
new_target_func()
print(new_target_func.__name__)

二.装饰器传参

  现在来这样一个场景. 还是约.

from functools import wraps

def wrapper(fn):
@wraps(fn)
def inner(*args, **kwargs):
print("问问金老板啊, 行情怎么样.")
ret = fn(*args, **kwargs)
print("⺾, 金老板骗我")
return ret
return inner @wrapper
def yue():
  print("约一次又不会死")
yue()

  那么现在如果查的很严,怎么办呢? 打电话问金老板严不严.那如果整体体声都不是那么紧呢,是不是就不需要问金老板了, 所以我们需要一个开关来控制是否要询问金老板,这时我们就需要给装饰器传递一个参数来通知装饰器要用怎么样的方式来装饰你的目标函数

from functools import wraps

def wrapper_out(flag):
def wrapper(fn):
@wraps(fn)
def inner(*args, **kwargs):
if flag == True: # 查的严啊. 先问问吧
print("问问金老板啊, 行情怎么样.")
ret = fn(*args, **kwargs)
print("⺾, ⾦⽼板骗我")
return ret
else: # 查的不严. 你慌什么
ret = fn(*args, **kwargs)
return ret
return inner
return wrapper
@wrapper_out(False) # 传递True和False来控制装饰器内部的运行效果
def yue():
print("约一次又不会死") yue()

  注意: 咱们之前的写法是@wrapper 其中wrapper是一个函数,那么也就是说如果我能让wrapper这里换成个函数就行了. wrapper(True)返回的结果是wrapper也是一个函数啊. 刚刚好和前面的@组合成一个@wrapper. 依然还是原来那个装饰器. 只不过这里套了3层. 但你要能看懂. 其实还是原来那个装饰器@wrapper

  执行步骤: 先执行wrapper(True) 然后再@返回值. 返回值恰好是wrapper. 结果就是@wrapper
三.多个装饰器装饰同一个函数

  先读一下这样一个代码:

def wrapper1(fn):
def inner(*args, **kwargs):
print("111")
ret = fn(*args, **kwargs)
print("222")
return ret
return inner def wrapper2(fn):
def inner(*args, **kwargs):
print("333")
ret = fn(*args, **kwargs)
print("444")
return ret
return inner @wrapper2
@wrapper1
def eat():
print("我想吃水果")
eat()
结果:
333
111
我想吃水果
222
444

  执行顺序: 首先@wrapper1装饰起来, 然后获取到一个新函数是wrapper1中的inner, 然后执行@wrapper2,这个时候wrapper2装饰的就是wrapper1中的inner了. 所以执行顺序就像:第二层装饰器前(第一层装饰器前(目标)第一层装饰器后)第一层装饰器后. 程序从左到右执行起来. 就是我们看到的结果

Python入门-装饰器初始的更多相关文章

  1. Python的装饰器实例用法小结

    这篇文章主要介绍了Python装饰器用法,结合实例形式总结分析了Python常用装饰器的概念.功能.使用方法及相关注意事项 一.装饰器是什么 python的装饰器本质上是一个Python函数,它可以让 ...

  2. Python各式装饰器

    Python装饰器,分两部分,一是装饰器本身的定义,一是被装饰器对象的定义. 一.函数式装饰器:装饰器本身是一个函数. 1.装饰函数:被装饰对象是一个函数 [1]装饰器无参数: a.被装饰对象无参数: ...

  3. Python札记 -- 装饰器补充

    本随笔是对Python札记 -- 装饰器的一些补充. 使用装饰器的时候,被装饰函数的一些属性会丢失,比如如下代码: #!/usr/bin/env python def deco(func): def ...

  4. python基础——装饰器

    python基础——装饰器 由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数. >>> def now(): ... print('2015-3-25 ...

  5. 【转】详解Python的装饰器

    原文链接:http://python.jobbole.com/86717/ Python中的装饰器是你进入Python大门的一道坎,不管你跨不跨过去它都在那里. 为什么需要装饰器 我们假设你的程序实现 ...

  6. 两个实用的Python的装饰器

    两个实用的Python的装饰器 超时函数 这个函数的作用在于可以给任意可能会hang住的函数添加超时功能,这个功能在编写外部API调用 .网络爬虫.数据库查询的时候特别有用 timeout装饰器的代码 ...

  7. python 基础——装饰器

    python 的装饰器,其实用到了以下几个语言特点: 1. 一切皆对象 2. 函数可以嵌套定义 3. 闭包,可以延长变量作用域 4. *args 和 **kwargs 可变参数 第1点,一切皆对象,包 ...

  8. 理解Python中的装饰器//这篇文章将python的装饰器来龙去脉说的很清楚,故转过来存档

    转自:http://www.cnblogs.com/rollenholt/archive/2012/05/02/2479833.html 这篇文章将python的装饰器来龙去脉说的很清楚,故转过来存档 ...

  9. python基础—装饰器

    python基础-装饰器 定义:一个函数,可以接受一个函数作为参数,对该函数进行一些包装,不改变函数的本身. def foo(): return 123 a=foo(); b=foo; print(a ...

随机推荐

  1. leetcode-119-Pascal's Triangle II(生成某一行的帕斯卡三角形)

    题目描述:   Given a non-negative index k where k ≤ 33, return the kth index row of the Pascal's triangle ...

  2. 游戏1:HTML5制作网页游戏围住神经猫--createjs

    游戏简介:点击小圆圈,是蓝色的小圆圈不跑出圆圈外,跑出则结束游戏 准备工作: 下载easejs  :下载地址:http://www.createjs.cc/easeljs    中文网站 效果: in ...

  3. PHP用curl远程下载图片

    function http_get_data($url){ $ch = curl_init (); curl_setopt ( $ch, CURLOPT_CUSTOMREQUEST, 'GET' ); ...

  4. MySQL:管理MySQL、事务(三)

    干货: 命令行程序mysql实际上是MySQL客户端,真正的MySQL服务器程序是mysqld,在后台运行. 数据库事务具有ACID特性,用来保证多条SQL的全部执行. 五.MySQL 通过mysql ...

  5. JAVA泛型方法与类型限定

     泛型方法可以定义在普通类中,也可以定义在泛型类中 class ArrayAlg{ public static <T> T getMiddle(T...a){ return a[a.len ...

  6. 高阶篇:4.2.4)DFMEA严重度S(+分类e)、频度O、探测度D、风险优先系数RPN

    本章目的:填写严重度S(+分类).频度O.探测度D,判定风险优先系数RPN. 1.前言 实施阶段中, 要求.潜在失效模式.潜在失效后果.潜在失效原因和现有设计控制措施等 5 个为基础项, 它们的分析是 ...

  7. bzoj2938 poi病毒 AC自动机

    题目传送门 思路: 要求构建一个字符串,使得这个字符串不包含给出的任意一个单词. 如果我们已经构建出了一个安全代码,放在ac自动机上跑,那么我们必定不能得到任何一个字符串,此时我们得到的fail指针必 ...

  8. sql count中加条件

    一般的,我们会在where, 或者 having中加条件,count中只是某个字段 今天看到另外一种写法,不知道性能怎么样 select count( case when xxx>10 and ...

  9. docker 容器 centos + tomcat + jdk

    环境: 阿里云ecs服务器 步骤: 1.安装docker 2.获取centos镜像 3.下载tomcat 和 jdk 安装包 4.配置 1.安装docker https://www.cnblogs.c ...

  10. DES加密之强制更新下载分离器

    数据加密算法(Data Encryption Algorithm,DEA)是一种对称加密算法,很可能是使用最广泛的密钥系统,特别是在保护金融数据的安全中,最初开发的DEA是嵌入硬件中的.通常,自动取款 ...