码迷,mamicode.com
首页 > 编程语言 > 详细

Python学习—装饰器

时间:2018-08-20 22:48:42      阅读:158      评论:0      收藏:0      [点我收藏+]

标签:修改   函数对象   模仿   parameter   写在前面   新闻   可变   tar   原函数   

装饰器

装饰器实际上就是为了给某程序增添功能,但该程序已经上线或已经被使用,那么就不能大批量的修改源代码,这样是不科学的也是不现实的,因为就产生了装饰器,使得其满足:
(1).不能修改被装饰的函数的源代码
(2).不能修改被装饰的函数的调用方式
(3).满足(1)、(2)的情况下给程序增添功能
实现:
我们写一个嵌套函数,在内部函数中添加新功能新内容,然后调用原函数,再在外部函数return这个内部函数。由于函数也是一个对象,而且函数对象可以被赋值给变量,所以我们调用这个嵌套函数,把返回值(返回的是一个函数)赋给一个和原函数同名的变量,通过变量也能调用该函数。这样就实现了不改变原函数代码增强其功能。

举个例子:
已经有一个函数login()实现的登陆功能,我想给它添加一些新内容:

def desc(fun):
    def add_info():
        print(‘写在前面.......‘)
        fun()
        print(‘写在后面........‘)
    return add_info

def login():
    print(‘login............‘)

login = desc(login)
login()

运行:
技术分享图片

其实这就是一个装饰器的雏形了。但是在使用这个装饰器装饰的时候,需要在每个函数前面加上这样一句代码:

    function = desc(function)

显然有些麻烦,Python提供了一种语法糖来代替它:

@装饰器名    #注意要把这句代码放到原函数前

代码修改如下,会时同样的效果且更加简洁:

def desc(fun):
    def add_info():
        print(‘写在前面.......‘)
        fun()
        print(‘写在后面........‘)
    return add_info

@desc
def login():
    print(‘login............‘)

login()

技术分享图片

用装饰器来实现计时功能:
计算在字符串连接时,+和join那个效率更高。

import random,string,time
li = [random.choice(string.ascii_letters) for i in range(1000)]    #这里用了1000个字符来比较

def desc(fun):
    def wrapper():
        start_time = time.time()
        fun()
        end_time = time.time()
        print(‘%.8f‘ %(end_time-start_time))
    return wrapper

@desc
def a_add():
    st = ‘‘
    for i in li:
        st += (i+‘,‘)
    return st
@desc
def b_add():
    return ‘,‘.join(li)

a_add()
b_add()

运行:
技术分享图片

显然,此时join方式效率要高于连接符+
可以看到以上内容中函数没有参数,即是无参函数装饰器。

装饰有参函数

用装饰器来实现判断变量类型:
对函数的传入参数进行检查,参数符合要求(整数)正常调用函数,不符合要求则报错。

import functools   #导入functools包

def required_int(fun):
    @functools.wraps(fun)   #functools包中的wraps函数保证原函数的信息不因装饰器而改变。
    def wrapper(*args):    #可变参数
        for i in args:
            if not str(i).isdigit():    #或者用isinstance(i,int)更加简洁
                print(‘参数必须是整数...‘)
                return None
                break
        else:
            res=fun(*args)
            return res
    return wrapper

@required_int
def add_num(*args):   #参数为可变参数
    sum = 0
    for i in args:
        sum += i
    return sum

print(‘参数合法输出结果:‘)
print(add_num(2,3,45,6))
print(‘参数不合法输出结果:‘)
print(add_num(3,4,5,‘e‘))

运行:
技术分享图片

模仿博客逻辑功能:
登陆后才能写博客,浏览博客不需要登陆。

import functools

login_users = [‘admin‘,‘root‘]     #已经登陆的用户
def is_login(fun):
    @functools.wraps(fun)
    def wrapper(*args,**kwargs):    #可变参数加关键字参数
        if kwargs.get(‘name‘) in login_users:
            res = fun(*args,**kwargs)
        else:
            res = login()
        return res
    return wrapper

def login():
    return ‘请先登陆.......‘

@is_login
def writtelog(name):
    return ‘写博客日之.....‘

def viewnews():
     return ‘看文章新闻.....‘

print(writtelog(name=‘root‘))
print(writtelog(name=‘root11‘))
print(viewnews())

运行:
技术分享图片

多个装饰器的使用

模仿只有admin用户在登陆后才能进入后台:

import functools

login_users = [‘admin‘,‘root‘]     #已经登陆的用户

def is_admin(fun):
    @functools.wraps(fun)
    def wrapper(*args,**kwargs):
        if kwargs.get(‘name‘)==‘admin‘:
            res = fun(*args,**kwargs)
        else:
            res = ‘没有权限....‘
        return res
    return wrapper

def is_login(fun):
    @functools.wraps(fun)
    def wrapper(*args,**kwargs):
        if kwargs.get(‘name‘) in login_users:
            res = fun(*args,**kwargs)
        else:
            res = login()
        return res
    return wrapper

def login():
    return ‘请先登陆.......‘

@is_login
@is_admin
def houtai(name):
    return ‘进入后台.....‘

print(houtai(name=‘admin‘))
print(houtai(name=‘root‘))
print(houtai(name=‘uuuu‘))

运行:
技术分享图片

这里需要注意的是,能用一个装饰器尽量不要使用多个装饰器。
当有多个装饰器时,从上往下依次调用装饰器,真实的wrapper内容时从上到下执行的。

带参数的装饰器

一个装饰器,对不同的函数有不同的装饰。那么就需要知道对哪个函数采取哪种装饰。因此,就需要装饰器带一个参数来标记一下。
将参数传入,我们需要再加一层函数嵌套,来传递装饰器的参数。
举栗子:
对两个不同的函数计时:

import time
def timer(parameter):    #参数parameter传入调用的函数的名字
    def outer_wrapper(func):
        def wrapper(*args, **kwargs):
            if parameter == ‘task1‘:
                start_time = time.time()
                func(*args, **kwargs)
                stop_time = time.time()
                print("the task1 run time is :", stop_time - start_time)
            elif parameter == ‘task2‘:
                start_time = time.time()
                func(*args, **kwargs)
                stop_time = time.time()
                print("the task2 run time is :", stop_time - start_time)
        return wrapper
    return outer_wrapper

@timer(parameter=‘task1‘)
def task1():
    time.sleep(2)
    print("in the task1")

@timer(parameter=‘task2‘)
def task2():
    time.sleep(2)
    print("in the task2")

task1()
task2()

运行:
技术分享图片

判断函数传入参数升级版本:

import functools,math

def required(*argss):
    def required_01(fun):
        @functools.wraps(fun)
        def wrapper(*args):
            for i in args:
                if not isinstance(i,argss):    #或者用isinstance(i,int)
                    print(‘参数必须是:‘,*argss)
                    return None
                    break
            else:
                res=fun(*args)
                return res
        return wrapper
    return required_01

@required(str,int)  #这里可以看到要求传入参数时str或者int
def add_num(*args):
    return args

print(add_num(‘ssss‘,789))    #符合要求
print(add_num(‘aaaa‘,693,3.14))     #有浮点型,不符合要求

运行:
技术分享图片

Python学习—装饰器

标签:修改   函数对象   模仿   parameter   写在前面   新闻   可变   tar   原函数   

原文地址:http://blog.51cto.com/13885935/2162154

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!