Python 装饰器以及装饰器为什么要双层嵌套和能用在什么业务场景

def lazy_print(*args):
     def pr():
         print(args)
     return pr

当我们调用lazy_print()时,返回的并不是求和结果,而是求和函数:

>>> p = lazy_print(1,2,3,4,5)
>>> p
<function lazy_print.<locals>.pr at 0x000000000364ED90>

调用函数p时,才真正计算求和的结果:

>>> p()
(1, 2, 3, 4, 5)

.....

一个函数可以返回一个计算结果,也可以返回一个函数。

返回一个函数时,牢记该函数并未执行,返回函数中不要引用任何可能会变化的变量。

首先定义一个装饰器:

def log(fun):    
    def wrapper():
        print('This is wrapper!')
        return fun()
    return wrapper

观察上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处:

@log
def now():
    print('This is now!')

调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志:

>>> now()
This is wrapper!
This is now!

@log放到now()函数的定义处,相当于执行了语句:

now = log(now)

由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。

因为我们讲了函数也是对象,它有__name__等属性,但你去看经过decorator装饰之后的函数,它们的__name__已经从原来的'now'变成了'wrapper'

>>> now.__name__
'wrapper'

因为返回的那个wrapper()函数名字就是'wrapper',所以,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。

不需要编写wrapper.__name__ = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下:

 
import functools

def log(fun):
    @functools.wraps(fun)
    def wrapper():
        print('This is wrapper!')
        return fun()
    return wrapper
 

import functools是导入functools模块。模块的概念稍候讲解。现在,只需记住在定义wrapper()的前面加上@functools.wraps(func)即可。

>>> now.__name__
'now'

一.装饰器

1.函数装饰圈的定义

函数装饰器:一种装饰函数的函数

2.个人理解两层函数装饰器

两层函数装饰器个人觉得他其实就是把需要装饰的函数名丢入形参,然后用一个嵌套的函数对其头尾进行添加程序,但是不能减少他的程序内容,他的原来程序不变只能增不能减少,然后返回装饰好的子函数,再全局定义一个变量名与要装饰的函数名相同名字,并且将装饰后的函数调用赋予改变量.

1.简单的例子(无参函数)

#有个函数f1
def f1():
    print('nick machachong')
#我们要对齐装饰使其输出打印内容上下都加了'-'线
def f1_deco(f1):                         #第一步吧变量丢进去
    def wrapper():
        #print(50*'-')                #我们加的内容
        #print('nick machachong')   #这个等同于f1()
        #print(50*'-')                #我们要加的内容
        #这是我们需要的打印效果
        
        pritn(50*'-')
        f1()
        print(50*'-')
    return wrapper  

f1 = f1_deco(f1)             #前面的f1是变量f1,函数本是f1并未发生变化只是一个变量名接受了f1
f1()

​```
--------------------------------------------------
nick machachong
--------------------------------------------------
​```
#另外种写法
@f1_deco
def f1():
    print('nick machachong')
f1()
​```
--------------------------------------------------
nick machachong
--------------------------------------------------
​```

2.关于有参函数

#如函数
def sum(x,y):
    print(x+y)
#我们要对齐装饰使其输出打印内容上下都加了'-'线
def sum_deco(sum):
    def wrapper(x,y):
        print("-")
        sum(x,y)
     return sum
sum() = sum_deco(sum)
sum(x,y)

#多个值
def sb(x,y,z=2,b=1):
    print(x,y,z,b)
#把他进行封装
def sb_deco(sb):
    def wrapper(*args,**kwargs):
        print('nick machachong')
        sb(*args,**kwargs)
    return wrapper
sb = sb_deco(sb)
sb(1,23,2,3)

3.对于有返回值的函数式

#有返回值
def sb(x,y,z=2,b=1):
    return x,y,z,b
#对于返回值乘以3
def sb_deco(sb):
    def wrapper(*args,**kwargs):
        res = sb(*args,**kwargs)
        res = list(res)
        for a in range(len(res)):
            res[a] = 3*res[a]
        res = tuple(res)
        return res
    return wrapper
sb = sb_deco(sb)
print(sb(2,3,4,5))

4.装饰圈模板

def sb(*args,**kwargs):
    pass
#装饰器模板
def sb_deco(sb):
    def wrapper(*args,**kwargs):
        #sb(*args,**kwargs)   
        res = sb(*args,**kwargs)  #赋值的时候已经开始调用了所有没必要在写一步调用
        return res
    return wrapper
sb = sb_deco(sb)
sb(1,23,2,3,12,312,312,3,123)

三.对于三层装饰器理解

#比如说一个函数为,我们对齐装饰,打印内容前后上下加'-'
def sb():
    print('i am sb')
    
#装饰
def sb_deco(sb):
    def wrapper():
        print('-'*5)
        sb()
        print('-'*5)
    return wrapper
#只时候我们加条件要区分是你输入还是我输入的

# 只时候我们加条件要区分是你输入还是我输入的, 我那边聪明肯定不是SB
def sb_deco(sb):
    def wrapper():
        if user == 'you':
            print('-' * 5)
            sb()
            print('-' * 5)
        elif user == 'i':
            print('-' * 5)
            sb()
            print('这是不可能的')
            print('-' * 5)
    return wrapper
#这时候我们导入的参数多了个user
def user(user):
    def sb_deco(sb):
        def wrapper():
            if user == 'you':
                print('-' * 5)
                sb()
                print('-' * 5)
            elif user == 'i':
                print('-' * 5)
                sb()
                print('这是不可能的')
                print('-' * 5)
        return wrapper
    return sb_deco


@user('you')                  #其中@user('you') 相当于a =user('you') sb = a(sb)  
def sb():                                                                           
    print('i am sb')
sb()
#他是对于输入的值的一层装饰,判断他是拿来的

Python 装饰器为什么要双层嵌套

如果没有嵌套,实际上装饰器返回的要么是原函数的定义,要么根本不是函数,也就是说函数根本没有被装饰。

即使碰巧得到了想要的结果也是装饰器在定义的阶段便运行的,这其实是不应该发生的,因为这意味着不调用函数,照样会有输出,而且效果不能在函数被调用时复现。

代码说明如下

如果直接一个单层的装饰器
def log(func):                                         #1
    print('call %s():' % func.__name__)                #2
    return func()                                      #3 
@log                                                   #4
def now():                                             #5
    print('2015-3-25')  

now()

此时#4运行时就会运行#2的print,并且装饰器要求#3  return一个函数,而不是函数的值(正确写法是returnfunc)

因此#3的语法错误,会报错'NoneType' object is not callable

3.如果改正为不要括号的写法

def log(func):                                         #1
    print('call %s():' % func.__name__)                #2
    return func                                        #3 

,则不会报错,但是会在@log时直接执行装饰器的内容(规则1),即执行print,此时装饰器返回的还是原函数a(规则3),所以之后调用函数a就无法触发装饰器

3,python的设计者为什么这么设计?

  为什么不把单层的装饰器的执行顺序等价为现在的双层装饰器的执行顺序

  一是两层结构可以传递被装饰的函数a的参数

 二是不改变函数a和函数log原来的结构,因为log本身就是一个函数,他与其他函数有相同的执行顺序

@log

def now():

    pass

完全等价于now=log(now)

解释:

装饰器等于修改了原函数定义,返回的仍然是函数定义!而不是单纯的返回的函数运行结果。

此时再调用被装饰函数实际执行的是已经被装饰过的函数(即可以理解为调用具有装饰器的函数,该函数的实际定义已经不是你看到的定义,而是被装饰器修改后的)

一次定义后即可重复使用,就像普通定义函数一般。

个人以为如果用装饰器,又不嵌套的写法是完全有悖装饰器初衷的,这时大可不必用装饰器,还不如直接函数调用来的实在。

装饰器是什么,有什么功能,能用在什么业务场景?

概念:

1.装饰器的实现是由闭包支撑的;

2.装饰器本质上是⼀个python函数,它可以在让其他函数在不需要做任何代码的变动的前提下增加额外的功能;

3.装饰器的返回值也是⼀个函数的对象,它经常用于有切面需求的场景,实现路由传参,flask的路由传参依赖于装饰器,浏览器通过url访问到装饰器的路由,从而访问视图函数获得返回的HTML页面;

应用场景:

1.可以在外层函数加上时间计算函数,计算函数运行时间;

2.计算函数运行次数;

3.可以用在框架的路由传参上;

4.插入日志,作为函数的运行日志;

5.事务处理,可以让函数实现事务的一致性,让函数要么一起运行成功,要么一起运行失败;

6.缓存,实现缓存处理;

7.权限的校验,在函数外层套上权限校验的代码,实现权限校验;

写出一个单例的装饰器(使一个本来不是单例类的类变成单例类))


def set_func(func):  
    __singleton = None  
  
    def call_func(*args, **kwargs):  
        nonlocal __singleton  
        if not __singleton:  
            __singleton = func(*args, **kwargs)  
            return __singleton  
        else:  
            return __singleton  
    return call_func  
 
 
@set_func  
class Std(object):  
  
    def __init__(self, name, age):  
        self.name = name  
        self.age = age  
  
  
s2 = Std('jack',18)  
print(id(s2),s2.name,s2.age)  
  
s1 = Std('leo',23)  
print(id(s1),s1.name,s1.age)  
运行结果:

139727292442832 jack 18  
139727292442832 jack 18  
登录判断装饰器:

    之前做过的一个用flask框架实现的移动app项目,里面大量用到是否已经登录的判断,如果这个业务逻辑大量重复地写在视图函数,代码的复用性很差,因此我将登录判断封装成装饰器,然后用这个装饰器装饰每一个需要验证是否登录的视图函数,代码如下:


def login_required(view_func):  
    """自定义装饰器判断用户是否登录"""  
 
    @wraps(view_func)  
    def wrapper(*args, **kwargs):  
        """具体实现判断用户是否登录的逻辑"""  
        user_id = session.get('user_id')  
        if not user_id:  
            return jsonify(errno=RET.SESSIONERR, errmsg='用户未登录')  
        else:  
            g.user_id = user_id  
            return view_func(*args, **kwargs)  
  
    return wrapper  
    总结:装饰器是python三大神器(迭代器,生成器,装饰器)中比较难理解的,但是它的本质实际上就是闭包,我们在闭包函数或者类外面封装了一套逻辑,因此可以增强函数的功能,增加权限校验,事务一致性,缓存等功能,这就是装饰器,使漂亮的姑娘(函数)变得更加漂亮。

参考自:

https://www.cnblogs.com/dylan-wu/p/6705149.html?utm_source=itdadao&utm_medium=referral

https://cloud.tencent.com/developer/article/1471212

https://blog.csdn.net/zhengyajun_email/article/details/86715467

https://blog.csdn.net/max_weil/article/details/80063551

原文地址:https://www.cnblogs.com/leijiangtao/p/4163830.html