Python装饰器

文章目录

  • Python装饰器
    • 基本概念
    • 从零开始的逐步分析
    • 修饰后的问题
    • 向被包装后的函数传递参数
    • 使用场景:stdout日志
    • 接受参数的装饰器
    • 作为一个类的装饰器
    • 总结

学习资料:

  • Python 函数装饰器
  • 装饰器类
  • Python __call__()方法(详解版)

基本概念

先建立一个观点:
函数本质上是一个对象,可以赋值给其他变量
加上()后就变成执行函数本身,并获得其返回值

修改其他函数功能的函数,即是装饰器

因为它要修改其他函数,所以装饰器本身应该返回一个函数
从而将这个修改后的函数赋值给原函数,以达到修改原函数功能的目的

从零开始的逐步分析

首先是一个原型理解
主要理解函数本身是一个对象
可以赋值给其他变量,或是本身作为参数

def deco_func(func):print("doing", func.__name__)func()def hi():print("hi")deco_func(hi)

上述代码的函数hi不能接受参数
即如果变成这样子,则会报错:

def deco_func(func):print("doing", func.__name__)func()def hi(name):print("hi", name)deco_func(hi("Archeri"))

输出:

hi Archeri
Traceback (most recent call last):File "D:\python_work\guide\deco.py", line 8, in <module>deco_func(hi("Archeri"))File "D:\python_work\guide\deco.py", line 2, in deco_funcprint("doing", func.__name__)
AttributeError: 'NoneType' object has no attribute '__name__'. Did you mean: '__ne__'?

上面的代码,会先执行hi("Archeri"),然后执行deco_func函数
因为hi函数加上了()因此变成了执行函数本身,并取得其返回值
由于hi函数没有任何返回值,或者说返回NoneType
因此报错信息最后面提醒说
'NoneType' object has no attribute '__name__'.
合情合理

为了可以给内函数传递参数,则可能需要在外函数里定义一个新的函数去返回

简单的剖析代码:

# 装饰器,就是一个函数,接收要修饰的原函数
# 这个deco_func整体就是一个装饰器
def deco_func(func):# 因为要返回一个函数,所以在函数里面定义一个新的函数来返回# 这个函数用于在执行原函数前后,执行一些操作def wrap_func():print("doing", func.__name__)func()# 返回“修改”了原函数功能的,内部新定义的函数return wrap_func# 原函数
def hi():print("hi")# 执行原函数,看看原功能
hi()
#outputs: hi# ↓↓↓装饰器起作用的原理就在这里↓↓↓
# 装饰好原函数之后,将修改后的函数返回给原函数
hi = deco_func(hi)
#now hi is wrapped by wrap_func()# 检验装饰结果
hi()
#outputs:doing hi
#        hi

使用“@”的等效装饰效果:

# 装饰器,就是一个函数,接收要修饰的原函数
# 这个deco_func整体就是一个装饰器
def deco_func(func):# 因为要返回一个函数,所以在函数里面定义一个新的函数来返回# 这个函数用于在执行原函数前后,执行一些操作def wrap_func():print("doing", func.__name__)func()# 返回“修改”了原函数功能的,内部新定义的函数return wrap_func# 原函数,使用“@”装饰
@deco_func
def hi():print("hi")# 检验装饰结果
hi()
#outputs:doing hi
#        hi# 函数定义时使用“@”装饰
# 实际上就是下面这个展示装饰原理的语句:
# hi = deco_func(hi)

以上就是装饰器最基本的理解


修饰后的问题

但是上面使用“@”修饰函数之后
print(hi.__name__)会输出wrap_func
hi函数的一些属性变了
是因为此时hi函数相当于承接了deco_func的返回值,相当于是wrap_func函数
因此hi此时更像是一个“变量”而不是“函数”
hi.__name__输出的指代返回的wrap_func

Python为上面的问题提供了一个原生又简单的解决方法:

from functools import wrapsdef deco_func(func):@wraps(func)def wrap_func():print("doing", func.__name__)func()return wrap_func

即导入wraps,然后在装饰器返回的函数前面用它来装饰即可
@wraps(func)

@wrap接受一个函数来进行装饰,并加入了复制函数名称、注释文档、参数列表等等的功能。
这可以让我们在装饰器里面访问在装饰之前的函数的属性。


向被包装后的函数传递参数

下面为了自己方便理解,作以下定义:
deco_func函数:装饰器
wrap_func函数:装饰器的返回结果,称为包装函数

若想给被装饰函数传入参数
则在包装函数原型中添加所有可变参数,并且在传给内部调用的被包装函数

from functools import wrapsdef deco_func(func):# 下面只是在定义函数,不执行其中的过程@wraps(func)def wrap_func(*args, **kw):print("doing", func.__name__)func(*args, **kw)# 最后返回定义好的函数,没有执行它return wrap_func@deco_func
def hi(name):print("hi", name)hi("Archeri")
#output: doing hi
#        hi Archeri

使用场景:stdout日志

from functools import wrapsdef logit(func):@wraps(func)def wrap_func(*args, **kw):print("doing", func.__name__)return func(*args, **kw)return wrap_func@logit
def my_add(x, y):return x + y@logit
def my_dec(x, y):return x - yprint(my_add(1, 1))
print(my_dec(1, 1))

output:

doing my_add
2
doing my_dec
0

接受参数的装饰器

上述装饰器虽然能装饰不同的函数
但是“装饰”这一行为本身是一样的
就好像一种啤酒,能装在任何杯子里
但装的量都是一样的
不能根据具体杯子的大小去变化装入的多少

因此可以给装饰器加上除被装饰函数之外的参数
来使装饰器的装饰行为变得可变、灵活

像是上面的日志场景,只能规定日志输出在stdout
下面就给装饰器添加参数,能够选择输出位置

一开始我是这样写装饰器的

def logit(func, logfile):@wraps(func)def wrap_func(*args, **kw):with open(logfile, 'a') as log_obj:log_obj.write("doing " + func.__name__)return func(*args, **kw)return wrap_func

想着直接在装饰器原型里加上除被装饰函数外的另一个参数
但是当使用@去装饰my_add函数时,传参遇到了问题

一开始@logit("addlog"),但是提示logit() missing 1 required positional argument: 'logfile',说明这个日志文件参数给到了func

然后试试@logit(logfile="addlog"),但是又提示缺失参数func

好嘛,@logit(func=my_add, logfile="addlog"),就说我my_add未定义了

说明上面那个带参数的装饰器的写法是有问题的


之后看了看正确的写法
下面就是我的分析和猜测

上面的装饰器,用@装饰时使用了()
是不是意味着实际的装饰器变成了logit函数执行后的返回结果
即是wrap_func
也就是说@logit()=@wrap_func

看了看正确写法,为此又加多了一层函数去封装?

from functools import wraps# 最外层接受装饰器的参数
def logit(logfile):# 因为使用“@”装饰时要加圆括号传参,所以最外层应该返回真正的装饰器def log_deco(func):# 真正的装饰器,返回装饰后的原函数@wraps(func)def wrap_func(*args, **kw):with open(logfile, 'a') as log_obj:log_obj.write("doing "+func.__name__+'\n')return func(*args, **kw)return wrap_funcreturn log_deco

测试:

@logit("addlog")
def my_add(x, y):return x + y@logit("declog")
def my_dec(x, y):return x - yprint(my_add(1, 1))
print(my_dec(1, 1))

结果输出“2”、“0”
然后也各自生成了正确的日志文件,写入了正确的内容

作为一个类的装饰器

上面涉及到的装饰器,本质上都是一个函数(普通装饰器)
而实际上装饰器本身也可以是一个类(装饰器类)

例如上面的不带参数的装饰器,可以这样把它写成一个类:

class logit:# 初始化工作def __init__(self, func):self.func = func# __call__方法使得类可像函数那样去加上()调用# 类似于之前的warp函数def __call__(self, *args, **kw):print("doing", self.func.__name__)return self.func(*args, **kw)

普通的装饰方法:

my_add = logit(my_add)
# 之后的my_add变成了一个logit类的实例
print(my_add(1,1))
#output: doing my_add
#        2

上面等价于print(logit(my_add)(1, 1))
输出也是一样的

使用@的装饰方法:

@logit
def my_add(x, y):return x + yprint(my_add(1,1))
# 输出同上面一致
# 装饰后的my_add也变成了logit类的一个实例

下面是接受参数的动态装饰器类的写法:

from functools import wrapsclass logit:# 因为是在这个类初始化时传入的参数# 所以应该在__init__方法里准备好装饰器要用的参数def __init__(self, logfile):self.logfile = logfile# 因为初始化时使用()去传入装饰器参数# 后面需要再调用一次才能执行这个__call__# 因此在这里使用被装饰的函数def __call__(self, func):# 返回被装饰后的原函数# 只定义,不执行@wraps(func)def wrap_func(*args, **kw):with open(self.logfile, "a") as log_obj:log_obj.write("doing "+func.__name__+'\n')return func(*args, **kw)return wrap_func

普通的装饰方法:

def my_add(x, y):return x + y
my_add = logit("addlog")(my_add)
print(my_add(1, 1))
# 输出2,生成日志文件

使用@的装饰方法:

@logit("addlog")
def my_add(x, y):return x + y
print(my_add(1, 1))
# 输出2,生成日志文件

总结

装饰器类比普通装饰器具有更好的可读性

听说还要知道什么闭包的概念
会更好理解一点
先放着,会用就行

Python装饰器学习笔记相关推荐

  1. Python装饰器学习笔记 1

    前言 最近跟着<流畅的Python>和<Python Cookbook>学习,看到装饰器部分,有些头大 倒不是因为概念难以理解,而是书和网上文章中有些地方有些矛盾之处 在简单学 ...

  2. Python装饰器学习(九步入门)

    这是在Python学习小组上介绍的内容,现学现卖.多练习是好的学习方式. 第一步:最简单的函数,准备附加额外功能 1 2 3 4 5 6 7 8 # -*- coding:gbk -*- '''示例1 ...

  3. python 装饰器简单笔记(附 *args **kw)

    1. 装饰器 由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数. 现在,假设我们要增强函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改函数的定义,这种在代码 ...

  4. Python 装饰器学习以及实际使用场景实践

    前言 前几天在看Flask框架,对于非常神奇的@语法,不是非常的理解,回来补装饰器的功课.阅读很多的关于装饰器的文章,自己整理一下,适合自己的思路的方法和例子,与大家分享. app = Flask(_ ...

  5. Python装饰器学习记录.

    装饰器 Python函数基础 python中万物皆对象,看一个实例: ​这里需要注意,单纯地函数名和函数名加括号是两种概念,前者表示该函数定义本身,而后者表示函数执行,实际上是函数的执行结果. 函数中 ...

  6. python装饰器学习

    装饰器 要学会装饰器,必须首先知道什么是闭包. 闭包: 在函数中提出的概念 就是内层函数对外层函数(非全局变量的运用)并且返回值是内部函数的引用. 格式: - def 外部函数:def内部函数:ret ...

  7. python 函数装饰器学习

    如果看<Python 核心编程>上的讲解还是不太清楚,我建议看这个链接: Python装饰器学习 看完之后,这里有一些总结: 其实总体说起来,装饰器其实也就是一个**函数**,一个用来** ...

  8. Python 装饰器 函数

    Python装饰器学习(九步入门):http://www.cnblogs.com/rhcad/archive/2011/12/21/2295507.html 浅谈Python装饰器:https://b ...

  9. python装饰器教学_Python装饰器学习(九步入门)

    这是在Python学习小组上介绍的内容,现学现卖.多练习是好的学习方式. 第一步:最简单的函数,准备附加额外功能 # -*- coding:gbk -*- '''示例1: 最简单的函数,表示调用了两次 ...

最新文章

  1. Nginx proxy_cache 使用示例
  2. HEVC/H265 namespace 介绍
  3. Atom飞行手册翻译: 2.13 基本的自定义
  4. 使用DAO模式实现电子宠物数据更新
  5. 服务器入站规则 共享文件,How to :发布内部网络中的文件共享服务
  6. (转)什么是DevOps?阿里专家为你来解读
  7. imp崩溃怎么办_IMP注入时游戏闪退崩溃 - Powered by GTA666 - Powered by GTA666 - Powered by GTA666...
  8. FCM模糊聚类算法python实现
  9. 网络邻居上的计算机没权限,xp系统打开网上邻居提示“您可能没有权限使用网络资源”怎么办...
  10. 2017 技术大检阅
  11. 美军与敏捷领导力—八个改变工作方式世界的老兵
  12. 博通网卡管理软件Linux,博通网卡管理软件
  13. 计数排序CountingSort
  14. L1, L2以及smooth L1 loss
  15. Step1我学习ros2的一些经历(从ubuntu安装到ros2中的位姿转换)
  16. [4G5G专题-79]:流程 - 4G LTE 寻呼流程Paging
  17. 【逻辑思维训练 二】系统思维训练
  18. 从面试官的角度聊聊培训班对程序员的帮助,同时给培训班出身的程序员一些建议...
  19. TensorFlow Lite,ML Kit 和 Flutter 移动深度学习:1~5
  20. Clipboard.js 实现点击复制

热门文章

  1. 【SIP协议】学习初学笔记
  2. C# 委托(Delegate) 事件(Event)应用详解
  3. 网络字节序与主机字节序的转换[转]
  4. linux vim五则运算编程,第3章--vi-vim编辑器的使用.ppt
  5. 【数据结构与算法】之深入解析“安装栅栏”的求解思路与算法示例
  6. iOS之常用的正则表达式
  7. 2019年第十届蓝桥杯 - 省赛 - C/C++大学C组 - B. 矩形切割
  8. TypeError: ‘BasePermissionMetaclass‘ object is not iterable
  9. 40. Combination Sum II 组合总和 II
  10. 【MFC】工具栏按钮单选效果