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多线程扫描端口示例
Jan 16 Python
Python每天必学之bytes字节
Jan 28 Python
Python cookbook(数据结构与算法)通过公共键对字典列表排序算法示例
Mar 15 Python
Python中elasticsearch插入和更新数据的实现方法
Apr 01 Python
将tensorflow的ckpt模型存储为npy的实例
Jul 09 Python
解决Django后台ManyToManyField显示成Object的问题
Aug 09 Python
Flask框架请求钩子与request请求对象用法实例分析
Nov 07 Python
python绘制规则网络图形实例
Dec 09 Python
如何使用repr调试python程序
Feb 28 Python
python 利用opencv实现图像网络传输
Nov 12 Python
使用OpenCV校准鱼眼镜头的方法
Nov 26 Python
python 如何在测试中使用 Mock
Mar 01 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
星际争霸任务指南——人族
2020/03/04 星际争霸
php小技巧之过滤ascii控制字符
2014/05/14 PHP
PHP+Mysql+Ajax+JS实现省市区三级联动
2014/05/23 PHP
ThinkPHP3.1数据CURD操作快速入门
2014/06/19 PHP
php使用mysqli向数据库添加数据的方法
2015/03/20 PHP
php函数传值的引用传递注意事项分析
2016/06/25 PHP
基于ThinkPHP5.0实现图片上传插件
2017/09/25 PHP
摘自百度的图片轮换效果代码
2007/11/19 Javascript
javascript实现的网页局布刷新效果
2008/12/01 Javascript
在javascript将NodeList作为Array数组处理的方法
2010/07/09 Javascript
jquery自动将form表单封装成json的具体实现
2014/03/17 Javascript
javascript中数组array及string的方法总结
2014/11/28 Javascript
javascript实现checkbox复选框实例代码
2016/01/10 Javascript
基于jquery fly插件实现加入购物车抛物线动画效果
2016/04/05 Javascript
JS实现页面载入时随机显示图片效果
2016/09/07 Javascript
bootstrap confirmation按钮提示组件使用详解
2017/08/22 Javascript
解决vue打包项目后刷新404的问题
2018/03/06 Javascript
详解ajax的data参数错误导致页面崩溃
2018/04/30 Javascript
node前端开发模板引擎Jade的入门
2018/05/11 Javascript
vue实现表单录入小案例
2019/09/27 Javascript
python中bisect模块用法实例
2014/09/25 Python
编写Python的web框架中的Model的教程
2015/04/29 Python
python获取各操作系统硬件信息的方法
2015/06/03 Python
Python实现Linux命令xxd -i功能
2016/03/06 Python
pow在python中的含义及用法
2019/07/11 Python
Python文件操作方法详解
2020/02/09 Python
零基础学Python之前需要学c语言吗
2020/07/21 Python
Pycharm 跳转回之前所在页面的操作
2021/02/05 Python
CSS3实现滚动条动画效果代码分享
2016/08/03 HTML / CSS
基于Modernizr 让网站进行优雅降级的分析
2013/04/21 HTML / CSS
详解如何解决canvas图片getImageData,toDataURL跨域问题
2018/09/17 HTML / CSS
Senreve官网:美国旧金山的奢侈手袋品牌
2019/03/21 全球购物
美国第二大连锁药店:Rite Aid
2019/04/03 全球购物
工商管理实习生自我鉴定范文
2013/12/18 职场文书
进口业务员岗位职责
2014/04/06 职场文书
出国留学担保书
2014/05/20 职场文书