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 04 Python
python批量实现Word文件转换为PDF文件
Mar 15 Python
Django开发中的日志输出的方法
Jul 02 Python
详解Python中的分组函数groupby和itertools)
Jul 11 Python
python实现银联支付和支付宝支付接入
May 07 Python
利用pyuic5将ui文件转换为py文件的方法
Jun 19 Python
Python实现报警信息实时发送至邮箱功能(实例代码)
Nov 11 Python
python自动化实现登录获取图片验证码功能
Nov 20 Python
python GUI库图形界面开发之PyQt5状态栏控件QStatusBar详细使用方法实例
Feb 28 Python
Python基于百度AI实现OCR文字识别
Apr 02 Python
使用Keras 实现查看model weights .h5 文件的内容
Jun 09 Python
Python的Tqdm模块实现进度条配置
Feb 24 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
PHP的FTP学习(三)
2006/10/09 PHP
分享一段php获取linux服务器状态的代码
2014/05/27 PHP
PHP对象的浅复制与深复制的实例详解
2017/10/26 PHP
Add a Table to a Word Document
2007/06/15 Javascript
基于jQuery实现的百度导航li拖放排列效果,即时更新数据库
2012/07/31 Javascript
基于jQuery实现在线选座之高铁版
2015/08/24 Javascript
jQuery实现div横向拖拽排序的简单实例
2016/07/13 Javascript
Vuejs第十三篇之组件——杂项
2016/09/09 Javascript
jQuery中fadein与fadeout方法用法示例
2016/09/16 Javascript
Bootstrap CSS布局之代码
2016/12/17 Javascript
jQuery实现导航栏头部菜单项点击后变换颜色的方法
2017/07/19 jQuery
聊聊Vue.js的template编译的问题
2017/10/09 Javascript
基于vue实现可搜索下拉框定制组件
2020/03/26 Javascript
浅谈Angularjs中不同类型的双向数据绑定
2018/07/16 Javascript
解决在vue项目中webpack打包后字体不生效的问题
2018/09/01 Javascript
jQuery实现form表单基于ajax无刷新提交方法实例代码
2019/11/04 jQuery
vue视频播放暂停代码
2019/11/08 Javascript
typescript编写微信小程序创建项目的方法
2021/01/29 Javascript
[59:59]EG vs IG 2018国际邀请赛小组赛BO2 第二场 8.16
2018/08/17 DOTA
Python的Flask框架中使用Flask-Migrate扩展迁移数据库的教程
2016/06/14 Python
DJANGO-URL反向解析REVERSE实例讲解
2019/10/25 Python
Python实现变声器功能(萝莉音御姐音)
2019/12/05 Python
python正则过滤字母、中文、数字及特殊字符方法详解
2020/02/11 Python
python通过对字典的排序,对json字段进行排序的实例
2020/02/27 Python
Python API len函数操作过程解析
2020/03/05 Python
Python matplotlib实时画图案例
2020/04/23 Python
jupyter notebook运行命令显示[*](解决办法)
2020/05/18 Python
如何用python写个模板引擎
2021/01/14 Python
详解CSS3中@media的实际使用
2015/08/04 HTML / CSS
西尔斯百货官网:Sears
2016/09/06 全球购物
高中军训感言800字
2014/03/05 职场文书
开业庆典主持词
2014/03/21 职场文书
房屋维修申请报告
2015/05/18 职场文书
解决Go gorm踩过的坑
2021/04/30 Golang
vue的项目如何打包上线
2022/04/13 Vue.js
华为HarmonyOS3.0强在哪? 看看鸿蒙3.0这7个小功能
2023/01/09 数码科技