python中的装饰器该如何使用


Posted in Python onJune 18, 2021
目录
  • 1. 需求是怎么来的
  • 2. 以不变应万变,是变也
  • 3. 最大限度地少改动
  • 4.对带参数的函数使用装饰器
  • 5. 给装饰器参数
  • 6.带类参数的装饰器
  • 7. 对一个函数应用多个装饰器
  • 8. 作为一个类

 

1. 需求是怎么来的

装饰器的定义很是抽象,我们来看一个小例子。

def foo():
    print('in foo()')
foo()

这是一个很无聊的函数没错。但是突然有一个更无聊的人,我们称呼他为B君,说我想看看执行这个函数用了多长时间,好吧,那么我们可以这样做:

import time

def foo():
    start = time.time()
    print('in foo()')
    time.sleep(2)
    end = time.time()
    print(f'used:{end - start}')

foo()

很好,功能看起来无懈可击。可是蛋疼的B君此刻突然不想看这个函数了,他对另一个叫foo2的函数产生了更浓厚的兴趣。

怎么办呢?如果把以上新增加的代码复制到foo2里,这就犯了大忌了~复制什么的难道不是最讨厌了么!而且,如果B君继续看了其他的函数呢?

 

2. 以不变应万变,是变也

还记得吗,函数在Python中是一等公民,那么我们可以考虑重新定义一个函数timeit,将foo的引用传递给他,然后在timeit中调用foo并进行计时,这样,我们就达到了不改动foo定义的目的,而且,不论B君看了多少个函数,我们都不用去修改函数定义了!

import time

def foo():
    print('in foo()')

def timeit(func):
    start = time.time()
    func()
    time.sleep(2)
    end = time.time()
    print('used:', end - start)

timeit(foo)

看起来逻辑上并没有问题,一切都很美好并且运作正常!……等等,我们似乎修改了调用部分的代码。原本我们是这样调用的:foo(),修改以后变成了:timeit(foo)。这样的话,如果foo在N处都被调用了,你就不得不去修改这N处的代码。或者更极端的,考虑其中某处调用的代码无法修改这个情况,比如:这个函数是你交给别人使用的。

 

3. 最大限度地少改动

既然如此,我们就来想想办法不修改调用的代码;如果不修改调用代码,也就意味着调用foo()需要产生调用timeit(foo)的效果。我们可以想到将timeit赋值给foo,但是timeit似乎带有一个参数……想办法把参数统一吧!如果timeit(foo)不是直接产生调用效果,而是返回一个与foo参数列表一致的函数的话……就很好办了,将timeit(foo)的返回值赋值给foo,然后,调用foo()的代码完全不用修改!

# -*- coding: UTF-8 -*-
import time

def foo():
    print('in foo()')

# 定义一个计时器,传入一个,并返回另一个附加了计时功能的方法
def timeit(func):
    # 定义一个内嵌的包装函数,给传入的函数加上计时功能的包装
    def wrapper():
        start = time.time()
        func()
        time.sleep(2)
        end = time.time()
        print('used:', end - start)

    # 将包装后的函数返回
    return wrapper

foo = timeit(foo)
foo()

这样,一个简易的计时器就做好了!我们只需要在定义foo以后调用foo之前,加上foo = timeit(foo),就可以达到计时的目的,这也就是装饰器的概念,看起来像是foo被timeit装饰了。在在这个例子中,函数进入和退出时需要计时,这被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。与传统编程习惯的从上往下执行方式相比较而言,像是在函数执行的流程中横向地插入了一段逻辑。在特定的业务领域里,能减少大量重复代码。面向切面编程还有相当多的术语,这里就不多做介绍,感兴趣的话可以去找找相关的资料。

这个例子仅用于演示,并没有考虑foo带有参数和有返回值的情况,完善它的重任就交给你了 :)

上面这段代码看起来似乎已经不能再精简了,Python于是提供了一个语法糖来降低字符输入量。

import time


def timeit(func):
    def wrapper():
        start = time.time()
        func()
        time.sleep(2)
        end = time.time()
        print('used:', end - start)

    return wrapper


@timeit
def foo():
    print('in foo()')


foo()

重点关注第11行的@timeit,在定义上加上这一行与另外写foo = timeit(foo)完全等价,千万不要以为@有另外的魔力。除了字符输入少了一些,还有一个额外的好处:这样看上去更有装饰器的感觉。

看到这里其实你也明白了,python 中的装饰器本质上就是一个函数,这个函数接收其他的函数作为参数,并将其以一个全新的修改后的函数替换它。

 

4.对带参数的函数使用装饰器

如果要包装的函数有参数,也不麻烦,只要内嵌包装函数的形参和返回值与原函数相同,装饰函数返回内嵌包装函数对象就可以啦

import datetime,time

def out(func):
    def inner(*args):
        start = datetime.datetime.now()
        func(*args)
        end = datetime.datetime.now()
        print(end-start)
        print("out and inner")
    return inner

@out
def myfunc(*args):
    time.sleep(1)
    print("args is{}".format(args))

myfunc("lalalal")

 

5. 给装饰器参数

给装饰器传参也不难,和上一示例相比在外层多了一层包装而已

#coding:utf-8
def outermost(*args):
	def out(func):
		print ("装饰器参数{}".format(args))
		def inner(*args):
			print("innet start")
			func(*args)
			print ("inner end")
		return inner
	return out

@outermost(666)
def myfun(*args):
	print ("试试装饰器和函数都带参数的情况,被装饰的函数参数{}".format(args))

myfun("zhangkun")

 

6.带类参数的装饰器

参数是什么类型其实都不影响的,你看,参数是个类也一样的

class locker:
    def __init__(self):
        print("locker.__init__() should be not called")

    @staticmethod
    def acquire():
        print("locker.acquire() static method be called")

    @staticmethod
    def release():
        print("locker.release() static method be called")

def outermost(cls):
    def out(func):
        def inner():
            cls.acquire()
            func()
            cls.release()
        return inner
    return out

@outermost(locker)
def myfunc():
    print("myfunc called")

myfunc()

 

7. 对一个函数应用多个装饰器

一个函数可以拥有多个装饰器,但是要注意顺序

class mylocker:
    def __init__(self):
        print("mylocker.__init__() called.")

    @staticmethod
    def acquire():
        print("mylocker.acquire() called.")

    @staticmethod
    def unlock():
        print("  mylocker.unlock() called.")

class lockerex(mylocker):
    @staticmethod
    def acquire():
        print("lockerex.acquire() called.")

    @staticmethod
    def unlock():
        print("  lockerex.unlock() called.")

def lockhelper(cls):
    def _deco(func):
        def __deco2(*args, **kwargs):
            print("before %s called." % func.__name__)
            cls.acquire()
            try:
                return func(*args, **kwargs)
            finally:
                cls.unlock()
        return __deco2
    return _deco

class example:
    @lockhelper(mylocker)
    @lockhelper(lockerex)
    def myfunc2(self, a, b):
        print(" myfunc2() called.")
        print(a+b)

a = example()
a.myfunc2(1,2)

 

8. 作为一个类

虽然装饰器几乎总是可以用函数实现,但是在某些情况下,使用用户自定义的类可能会更好

import time


class DerocatorAsClass:
    def __init__(self,funcation):
        self.funcation = funcation

    def __call__(self, *args, **kwargs):
        # 调用函数之前,做点什么
        result = self.funcation(*args,**kwargs)
        print('3333333333')
        # 在调用之后做点什么并且返回结果
        return result

@DerocatorAsClass
def foo():
    print('in foo()')


foo()

如上例,用类作为装饰器也是很方便的

以上就是python中的装饰器该如何使用的详细内容,更多关于python 装饰器的使用的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
python使用urlparse分析网址中域名的方法
Apr 15 Python
浅谈Python数据类型判断及列表脚本操作
Nov 04 Python
Python类的动态修改的实例方法
Mar 24 Python
python如何使用正则表达式的前向、后向搜索及前向搜索否定模式详解
Nov 08 Python
python实现决策树
Dec 21 Python
Python程序员面试题 你必须提前准备!(答案及解析)
Jan 23 Python
Python中%是什么意思?python中百分号如何使用?
Mar 20 Python
python 日期操作类代码
May 05 Python
python实现简单日期工具类
Apr 24 Python
Python获取二维数组的行列数的2种方法
Feb 11 Python
scrapy爬虫:scrapy.FormRequest中formdata参数详解
Apr 30 Python
django template实现定义临时变量,自定义赋值、自增实例
Jul 12 Python
Python预测分词的实现
学会Python数据可视化必须尝试这7个库
python tqdm用法及实例详解
Jun 16 #Python
python使用pymysql模块操作MySQL
分析Python感知线程状态的解决方案之Event与信号量
Jun 16 #Python
Python中else的三种使用场景
Jun 16 #Python
Python基础之条件语句详解
You might like
ie与session丢失(新窗口cookie丢失)实测及解决方案
2013/07/15 PHP
7个鲜为人知却非常实用的PHP函数
2015/07/01 PHP
自己开发Dojo的建议框架
2008/09/24 Javascript
元素未显示设置width/height时IE中使用currentStyle获取为auto
2014/05/04 Javascript
基于JQuery制作可编辑的表格特效
2014/12/23 Javascript
jQuery中clone()方法用法实例
2015/01/16 Javascript
JavaScript实现Java中StringBuffer的方法
2015/02/09 Javascript
JavaScript通过字符串调用函数的实现方法
2015/03/18 Javascript
javascript实现点击按钮弹出一个可关闭层窗口同时网页背景变灰的方法
2015/05/13 Javascript
jquery+css3实现会动的小圆圈效果
2016/01/27 Javascript
基于HTML5上使用iScroll实现下拉刷新,上拉加载更多
2016/05/21 Javascript
Angular2 (RC4) 路由与导航详解
2016/09/21 Javascript
JQuery学习总结【二】
2016/12/01 Javascript
canvas快速绘制圆形、三角形、矩形、多边形方法介绍
2016/12/29 Javascript
原生JS实现隐藏显示图片 JS实现点击切换图片效果
2021/01/27 Javascript
JQuery 获取Dom元素的实例讲解
2017/07/08 jQuery
详解bootstrap用dropdown-menu实现上下文菜单
2017/09/22 Javascript
js 获取json数组里面数组的长度实例
2017/10/31 Javascript
微信小程序之事件交互操作实例分析
2018/12/03 Javascript
js实现轮播图效果 z-index实现轮播图
2020/01/17 Javascript
vue实现全屏滚动效果(非fullpage.js)
2020/03/07 Javascript
Element Breadcrumb 面包屑的使用方法
2020/07/26 Javascript
[41:20]2014 DOTA2华西杯精英邀请赛 5 24 NewBee VS DK
2014/05/26 DOTA
Python使用ntplib库同步校准当地时间的方法
2016/07/02 Python
对python 数据处理中的LabelEncoder 和 OneHotEncoder详解
2018/07/11 Python
pandas 快速处理 date_time 日期格式方法
2018/11/12 Python
python 消除 futureWarning问题的解决
2019/12/25 Python
Python内建序列通用操作6种实现方法
2020/03/26 Python
tensorflow实现残差网络方式(mnist数据集)
2020/05/26 Python
python 引用传递和值传递详解(实参,形参)
2020/06/05 Python
python 代码运行时间获取方式详解
2020/09/18 Python
25个CSS3动画按钮和菜单教程分享
2012/10/03 HTML / CSS
音乐教师个人工作总结
2015/02/06 职场文书
支教个人总结
2015/03/04 职场文书
2015年财务科工作总结范文
2015/05/13 职场文书
销售口号霸气押韵
2015/12/24 职场文书