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实现ipsec开权限实例
Nov 11 Python
结合Python的SimpleHTTPServer源码来解析socket通信
Jun 27 Python
Python生成密码库功能示例
May 23 Python
Python进阶之递归函数的用法及其示例
Jan 31 Python
教你使用python实现微信每天给女朋友说晚安
Mar 23 Python
使用Python和xlwt向Excel文件中写入中文的实例
Apr 21 Python
Python操作Sqlite正确实现方法解析
Feb 05 Python
浅谈Python中range与Numpy中arange的比较
Mar 11 Python
Mysql数据库反向生成Django里面的models指令方式
May 18 Python
基于python图书馆管理系统设计实例详解
Aug 05 Python
解决python和pycharm安装gmpy2 出现ERROR的问题
Aug 28 Python
pandas中关于apply+lambda的应用
Feb 28 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 基本语法格式
2009/12/15 PHP
php中文验证码实现示例分享
2014/01/12 PHP
php反射类ReflectionClass用法分析
2016/05/12 PHP
PHP基于自增数据如何生成不重复的随机数示例
2017/05/19 PHP
CI框架(CodeIgniter)实现的数据库增删改查操作总结
2018/05/23 PHP
Riot.js 快速的JavaScript单元测试框架
2009/11/09 Javascript
JavaScript中常见陷阱小结
2010/04/27 Javascript
js+css实现增加表单可用性之提示文字
2013/06/03 Javascript
js关于字符长度限制的问题示例探讨
2014/01/24 Javascript
jquery append 动态添加的元素事件on 不起作用的解决方案
2015/07/30 Javascript
如何用JavaScript实现动态修改CSS样式表
2016/05/20 Javascript
JS调用打印机功能简单示例
2016/11/28 Javascript
详解PHP中pathinfo()函数导致的安全问题
2017/01/05 Javascript
js控制按钮,防止频繁点击响应的实例
2017/02/15 Javascript
react-router中的属性详解
2017/06/01 Javascript
利用nginx + node在阿里云部署https的步骤详解
2017/12/19 Javascript
jQuery实现DIV响应鼠标滑过由下向上展开效果示例【测试可用】
2018/04/26 jQuery
jQuery+PHP实现上传裁剪图片
2020/06/29 jQuery
小程序测试后台服务的方法(ngrok)
2019/03/08 Javascript
[05:17]DOTA2誓师:今天我们在这里 明天TI4等我!
2014/03/26 DOTA
[04:22]DOTA2上海特级锦标赛主赛事第四日TOP10
2016/03/06 DOTA
Python的Flask开发框架简单上手笔记
2015/11/16 Python
详解python websocket获取实时数据的几种常见链接方式
2019/07/01 Python
Anaconda 查看、创建、管理和使用python环境的方法
2019/12/03 Python
对Tensorflow中Device实例的生成和管理详解
2020/02/04 Python
Python使用多进程运行含有任意个参数的函数
2020/05/02 Python
Python项目打包成二进制的方法
2020/12/30 Python
日本最大化妆品和美容产品的综合口碑网站:cosme shopping
2019/08/28 全球购物
业务助理岗位职责
2013/11/18 职场文书
简单而又朴实的个人求职信分享
2013/12/12 职场文书
《画杨桃》教学反思
2014/04/13 职场文书
2014小学语文教师个人工作总结
2014/12/03 职场文书
2014年个人教学工作总结
2014/12/09 职场文书
七年级作文之游记
2019/12/11 职场文书
MySQL Innodb关键特性之插入缓冲(insert buffer)
2021/04/08 MySQL
python 自动刷新网页的两种方法
2021/04/20 Python