python 装饰器案例解析

本文介绍几个装饰器案例,来分析装饰器是如何调用的

获取函数运行时间的例子

写装饰器,不可以一步到位,要慢慢一点一点的来

先写好2个函数

import time
def test1():
    time.sleep(1)
    print('in the test1')

def test2():
    time.sleep(2)
    print('in the test2')

test1()
test2()

执行输出

in the test1

in the test2

在不修改源代码的情况下,新增一个功能呢?

这个时候,需要用到 高阶函数

import time

def deco(func):
    start_time = time.time()
    func()
    stop_time = time.time()
    print('the func run time is %s' % (stop_time - start_time))

def test1():
    time.sleep(1)
    print('in the test1')

def test2():
    time.sleep(2)
    print('in the test2')

deco(test1)
deco(test2)

执行输出

in the test1

the func run time is 1.0009195804595947

in the test2

the func run time is 2.0000431537628174

注意:

执行的时候,不能写deco(test1()),为什么呢?这样写,是把test1函数执行的结果,传给deco了。

deco函数,不需要执行结果,它需要一个函数即可。

Pycharm编辑器写代码的时候,有自动补全功能,切记这里,要把括号删掉才行。

上面实现了显示执行时间的功能,下面考虑一个问题,如何在此基础上,直接执行

test1()和test2() 就可以实现上面的功能

需要用到变量的引用

def deco(func):
    start_time = time.time()
    func()
    stop_time = time.time()
    print('the func run time is %s' % (stop_time - start_time))

def test1():
    time.sleep(1)
    print('in the test1')

def test2():
    time.sleep(2)
    print('in the test2')

test1 = deco(test1)
test2 = deco(test2)
test1()
test2()

执行报错

TypeError: 'NoneType' object is not callable

因为deco没有return,无法得到函数内存地址

再改一下

import time
def deco(func):
    start_time = time.time()
    #返回函数内存地址
    return func
    stop_time = time.time()
    print('the func run time is %s' % (stop_time - start_time))

def test1():
    time.sleep(1)
    print('in the test1')

def test2():
    time.sleep(2)
    print('in the test2')

test1 = deco(test1)
test2 = deco(test2)
test1()
test2()

执行输出

in the test1

in the test2

从结果可以看出,函数的调用方式没有变,执行结果也没有变。搞了半天,啥都没干。

因为deco中直接returun了,所以打印时间没有输出。

上面的应用都是高阶函数,还缺嵌套函数,就成了

下面写一个嵌套函数。

def timer():
    def deco():
        pass

能不能把嵌套函数的形式融入到deco函数中呢?

把deco函数的代码直接拷贝进来,最后return deco

一个函数只有一个return,把中间的return修改为func()

将func参数移动到函数最上层

def timer(func):
    def deco():
        start_time = time.time()
        func()
        stop_time = time.time()
        print('the func run time is %s' % (stop_time - start_time))
    return deco

完整代码如下:

import time

def timer(func): #time(test1) func=test1
    def deco():
        start_time = time.time()
        func() #run test1()
        stop_time = time.time()
        print('the func run time is %s' % (stop_time - start_time))
    return deco

def test1():
    time.sleep(1)
    print('in the test1')

def test2():
    time.sleep(2)
    print('in the test2')

print(timer(test1))

执行输出

function timer.

结果返回deco函数的内存地址

有了函数的内存地址,把它赋值给一个变量,执行变量就可以了

print(timer(test1))改成

test1 = timer(test1)
test1()

执行输出

in the test1

the func run time is 1.0003840923309326

这里,效果就实现了,源代码和执行方式都没有改变。

注意: 最后的test1()行函数,已经不是原来的函数了,而是被装饰过的函数。

执行函数,需要2个步骤,太麻烦了,这不是最终效果

python 提供一个语法,用来执行装饰器函数,语法

@函数名
被装饰的函数名

这一句,需要加在被装饰函数的上一行

我删除了test2(),最终完整代码如下:

#!/usr/bin/env python
# coding: utf-8
__author__ = 'www.py3study.com'

import time

def timer(func): #timer(test) func=test1
    def deco():
        start_time = time.time()
        func() #run test1()
        stop_time = time.time()
        print('the func run time is %s' % (stop_time - start_time))
    return deco

@timer #test1=timer(test1)
def test1():
    time.sleep(1)
    print('in the test1')
    
test1()

执行输出

in the test1

the func run time is 1.0006184577941895

test1()函数上面的@timer

就相当于

test1 = timer(test1)

注意:上面写的装饰器,还不够完美

举个例子,再加一个函数,去装饰它

#!/usr/bin/env python
# coding: utf-8
__author__ = 'www.py3study.com'

import time

def timer(func):
    def deco():
        start_time = time.time()
        func()
        stop_time = time.time()
        print('the func run time is %s' % (stop_time - start_time))
    return deco

@timer
def test1():
    time.sleep(1)
    print('in the test1')

@timer
def test2(name):
    print("test",name)

test1()
test2("zhang")

执行报错

TypeError: test2() missing 1 required positional argument: 'name'

为什么呢?

test2上面的@timer就相当于

test2 = timer(test2)

timer(test2) -> 调用deco() -> 调用func() ->调用原test2(name)

注意,调用原test2函数的时候,需要传一个参数才行,而func()调用它的时候,没法传参数,所以程序报错。

为了解决传参的问题,需要调整一下deco函数,要求能接受参数。由于被装饰的函数,千奇百怪,有参数,没参数的都存在。使用*args,**kwargs就可以表示任意的参数。

最后终极代码如下:

#!/usr/bin/env python
# coding: utf-8
__author__ = 'www.py3study.com'

import time

def timer(func):
    def deco(*args,**kwargs):
        start_time = time.time()
        func(*args,**kwargs)
        stop_time = time.time()
        print('the func run time is %s' % (stop_time - start_time))
    return deco

@timer
def test1():
    time.sleep(1)
    print('in the test1')

@timer
def test2(name):
    time.sleep(2)
    print("test",name)

test1()
test2("zhang")

执行输出

in the test1

the func run time is 1.000878095626831

test zhang

the func run time is 2.0003161430358887

网页登陆

代码如下:

#!/usr/bin/env python
# coding: utf-8
__author__ = 'www.py3study.com'

import time

user,passwd = 'zhang','abc123'
def auth(func):
    def wrapper(*args,**kwargs):
        username = input("username:").strip()
        password = input("password:").strip()

        if user == username and passwd == password:
            print("\033[32;1mUser has passed authentication\033[0m")
            func(*args,**kwargs)
        else:
            exit("\033[31;1mInvalid username or password\033[0m")
    return  wrapper

def index():
    print("welcome to index page")

@auth
def home():
    print("welcome to home page")
@auth
def bbs():
    print("welcome to bbs page")

index()
home()
bbs()

执行输出

输出了2次用户名和密码

为什么呢?因为home和bbs页面,需要登录才能访问。

下面加一个验证方式

#!/usr/bin/env python
# coding: utf-8
__author__ = 'www.py3study.com'

import time

user,passwd = 'zhang','abc123'
def auth(auth_type):
    print("auth func:",auth_type)
    def outer_wrapper(func):
        def wrapper(*args, **kwargs):
            print("wrapper func args:",*args, **kwargs)
            username = input("username:").strip()
            password = input("password:").strip()

            if user == username and passwd == password:
                print("\033[32;1mUser has passed authentication\033[0m")
                res = func(*args, **kwargs)
                print("---after authentication")
                return res
            else:
                exit("\033[31;1mInvalid username or password\033[0m")

        return wrapper
    return outer_wrapper


def index():
    print("welcome to index page")

@auth(auth_type="local")
def home():
    print("welcome to home page")
@auth(auth_type="ldap")
def bbs():
    print("welcome to bbs page")

index()
home()
bbs()

执行输出

现在还没有判断是哪种方式

完整代码如下:

#!/usr/bin/env python
# coding: utf-8
__author__ = 'www.py3study.com'

import time

user,passwd = 'zhang','abc123'
def auth(auth_type):
    #print("auth func:",auth_type)
    def outer_wrapper(func):
        def wrapper(*args, **kwargs):
            #print("wrapper func args:",*args, **kwargs)
            if auth_type == "local":

                username = input("username:").strip()
                password = input("password:").strip()

                if user == username and passwd == password:
                    print("\033[32;1mUser has passed authentication\033[0m")
                    res = func(*args, **kwargs)
                    print("---after authentication")
                    return res
                else:
                    exit("\033[31;1mInvalid username or password\033[0m")
            elif auth_type == "ldap":
                print("This is LDAP,I can't do it")
                pass

        return wrapper
    return outer_wrapper


def index():
    print("welcome to index page")

@auth(auth_type="local")
def home():
    print("from home page")
@auth(auth_type="ldap")
def bbs():
    print("from bbs page")

index()
home()
bbs()

执行输出

执行bbs,采用的是ldap方式,我就直接输出了一段话,判断逻辑不会写啊!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏机器学习入门

LWC 61:739. Daily Temperatures

LWC 61:739. Daily Temperatures 传送门:739. Daily Temperatures Problem: Given a lis...

18480
来自专栏IT技术精选文摘

跟着实例学习ZooKeeper的用法: 队列

使用Curator也可以简化Ephemeral Node (临时节点)的操作。Curator也提供ZK Recipe的分布式队列实现。 利用ZK的 PERSIS...

27570
来自专栏全华班

java学习手册-JAVA程序员笔试题(一)

JAVA程序员笔试题(一) 一、选择题: 1、类的成员变量要求仅仅能够被同一package下的类访问,应该使用哪个修辞词 A. Protected、B. Pub...

43250
来自专栏熊二哥

快速入门系列--CLR--03泛型集合

.NET中的泛型集合 在这里主要介绍常见的泛型集合,很多时候其并发时的线程安全性常常令我们担忧。因而简述下.NET并发时线程安全特性,其详情请见MSDN。 ...

18670
来自专栏Kirito的技术分享

警惕不规范的变量命名

就在最近,项目组开始强调开发规范了,今天分享一个变量名命名不规范的小案例,强调一下规范的重要性。 Boolean变量名命名规范 16年底,阿里公开了《Java...

35290
来自专栏大闲人柴毛毛

Java异常体系中的秘密

相信大家每天都在使用Java异常机制,也相信大家对try-catch-finally执行流程烂熟于胸。本文将介绍Java异常机制的一些细节问题,这些问题虽然很...

382100
来自专栏互联网杂技

前端异步代码解决方案实践(二)

早前有针对 Promise 的语法写过博文,不过仅限入门级别,浅尝辄止食而无味。后面一直想写 Promise 实现,碍于理解程度有限,多次下笔未能满意。一拖再拖...

24360
来自专栏java达人

jsp中的JSTL与EL表达式用法及区别(一)

对于JSTL和EL之间的关系,这个问题对于初学JSP的朋友来说,估计是个问题,下面来详细介绍一下JSTL和EL表达式他们之间的关系,以及JSTL和EL一些相关概...

24850
来自专栏程序员互动联盟

【编程基础】C语言常见宏定义

我们在使用C语言编写程序的时候,常常会使用到宏定义以及宏编译指令,有的可能比较常用,有的可能并不是很常用,是不是所有的C语言宏定义以及宏指令你都清楚呢? 指令 ...

39480
来自专栏爱撒谎的男孩

Spring表达式和自动装配

43880

扫码关注云+社区

领取腾讯云代金券