Python-装饰器

一、闭包函数

闭包函数=函数嵌套定义+函数对象+名称空间与作用域

闭包函数:在函数中(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。

1、闭:指的是该函数是定义在一个函数内部的函数
2、包:值得是该函数访问了一个来自于外层函数的变量

为函数体传参的方法:

'''方案一:直接使用参数的形式传递'''
def wrapper(x):
    print(x)

wrapper(111)
wrapper(222)
'''方案二:把函数体想要的参数包给它(即使用闭包的概念)'''
def outter(x):
    def wrapper():  # wrapper = 闭包函数的内存地址
        print(x)

    return wrapper  # 一定不要加括号

f1 = outter(111)  # f = 闭包函数的内存地址
f2 = outter(222)  # f = 闭包函数的内存地址
f1()
f2()
-----------
111
222

乍一看会感觉使用闭包来传参数非常的麻烦,我们之前使用函数,需要参数都是直接传给他,方便也快捷,但是在某些场景下我们定死了某个函数无法直接传参数,那就必须通过其他方式传参数,即闭包的方式,下面介绍的装饰器就是闭包的使用。

二、@符号的使用

@符号作用: 
(1) 可以自动把@符号下面的函数当成参数传递给装饰器
(2) 把新函数进行返回,让新函数去替换旧函数,以实现功能的扩展.
如:
@timer  # 等于func1 = timer(func1),其目的是为了让使用者不改变调用方式,它依然在调用func1,但是实际已经被我们换成了timer
def func1():
  print(1)

三、叠加多个装饰器的运行步骤

结论:(记住结论既可)

加载顺序:自下而上
执行顺序:自上而下运行内层的wrapper函数

验证过程:

# 叠加多个装饰器
def deco1(func1):  # func1 = wrapper2的内存地址
    def wrapper1(*args,**kwargs):
        print('wrapper1====>')
        res1=func1(*args,**kwargs)
        return res1
    return wrapper1

def deco2(func2):  # func2 = wrapper3的内存地址
    def wrapper2(*args,**kwargs):
        print('wrapper2====>')
        res2=func2(*args,**kwargs)
        return res2
    return wrapper2

def deco3(func3):  # func3 = 最原始的那个被装饰函数的内存地址
    def wrapper3(*args,**kwargs):
        print('wrapper3====>')
        res3=func3(*args,**kwargs)
        return res3
    return wrapper3

        # index=wrapper1的内存地址
@deco1  # deco1(wrapper2的内存地址)=>wrapper1的内存地址
@deco2  # deco2(wrapper3的内存地址)=>wrapper2的内存地址
@deco3  # deco3(最原始的那个被装饰函数的内存地址)=>wrapper3的内存地址  
def index(x,y):
    print('index=>',x,y)

index(1,2)
-----------------------
wrapper1====>'
wrapper2====>
wrapper3====>
index=>1,2

四、无参装饰器

什么是装饰器:

器:工具
装饰:为被装饰者添加额外的功能

为何要有装饰器:

软件一旦上线运行之后,就应该遵循开放封闭原则:
1、开放指的是对拓展新功能开放
2、封闭指的是对修改源代码封闭
这种情况下,我们在写新功能时,若需要用到新的参数,就无法直接通过原函数来传了,必须通过其他方式来传参,目前我们想到的方法,是两种传参的另一种的方式,闭包函数

定义装饰器的目的:
定义装饰器就是为了在遵循1和2的前提下来为其他函数添加新功能的

ps:
不修改被装饰对象指的是定义与调用都不能修改
所以下述行为都违反了开放封闭原则:
①、修改被装饰对象定义时的源代码
②、修改被装饰对象的调用方式

如何用装饰器:

'''无参装饰器基本格式'''
def wrapper(func):
    def inner(*args,**kwargs):
        res = func(*args,**kwargs)
        return func
    return inner 
  '''
  切记不能在此处加括号,加括号就代表运行,相当于先把inner运行一遍,然后拿到返回值return出去.
  return 出去的值是无法加括号在运行的。
  '''
# 例子
def wrapper(func):
    def inner(*args,**kwargs):
        res = func(*args,**kwargs)
        return res
    return inner()  # 切记不能在此处加括号,加括号就代表运行

@wrapper
def func1():
    print(11)

func1()
-----------------
11
TypeError: 'NoneType' object is not callable

'''
前面打印出来的11其实是运行wrapper里的inner产生的,所以以上程序的运行流程是
1.先把func1传给wrapper,然后运行了wrapper中的代码,返回 inner()
2.inner() 就开始执行inner函数,即运行了一遍func1,所以打印了11
3.此时的func1 = inner()  而inner()又等于func1(),func1没有返回值,默认返回None
4.变成了None(),报错
'''

无参装饰器的应用场景以及构建装饰器步骤:

需求:有一个index函数,此时我们要在index函数基础上添加一个计算运算时间的功能,因为是公司项目,我们不能修改原函数的调用方式,也不能修改源代码。

# 一、被装饰器对象index如下
import time

def index(x,y):
    time.sleep(1)
    print("index---->",x,y)

index(1,2)

# 二、为index添加计算运算时间与登录功能
import time
from functools import wraps

def timmer(func):
    def wrapper(*args,**kwargs):
        start = time.time()
        res = func(*args,**kwargs)
        end = time.time()
        print(end-start)
        return res
    return wrapper

def login(func):
    def wrapper(*args,**kwargs):
        name = 'yang'
        pwd = '123'
        inp_name = input("请输入您的用户名:").strip()
        inp_pwd = input("请输入您的密码").strip()
        if inp_name == name and inp_pwd == pwd:
            print("登录成功")
            res = func(*args,**kwargs)
            return res
    return wrapper

@timmer
@login
def index(x,y):
    time.sleep(1)
    print("index---->",x,y)

index(1,2)

五、有参装饰器

对于不再需要新参数的装饰器,两层就可以解决了,但是当我们的装饰器需要新的参数,如在登录的时候,我们要判断我们用户名与密码的来源,此时需要外部将来源传进来。而两层的装饰器中,第一层,是为了给原函数传参数,他的参数不能动,而第二层,因为装饰器语法@的原因,也已经定死了此处只能传一个函数名,也不能传参数,所以我们需要构造第三层来接受外部的参数,而在内部的函数可以在不改动自己依然可以获取到外层的函数.

'''有参装饰器模版'''
def outter2(x,y,z,a,b):
    def outter1(func):
        def wrapper(*args,**kwargs):
            res=func(*args,**kwargs)
            return res
        return wrapper
    return outter1

例子:

def outter(engine = 'file'):
    def deco2(func2):
        def wrapper2(*args,**kwargs):
            inp_name = input('username>>>: ').strip()
            inp_pwd = input('password>>>: ').strip()

            if engine == "file":
                print('基于file的认证')
                if inp_name == "egon" and inp_pwd == "123":
                    print('login successful')
                    res2=func2(*args,**kwargs)
                    return res2
                else:
                    print('username or password error')
            elif engine == 'mysql':
                print('基于mysql的认证')
            elif engine == 'ldap':
                print('基于ldap的认证')
            else:
                print('未知的engine')
        return wrapper2
    return deco2

@outter(engine='mysql')  # @deco2 # index=deco2(index)
def index(x,y):
    print('index=>',x,y)

index(1,2)  # index=>wrapper

六、类装饰器

我们也可以用类来装饰函数

# 利用类静态方法装饰
class MyClass:
    @staticmethod
    def zhuangshi1(func):
        def newfunc():
            print("装饰前")
            func()
            print("装饰后")
        return newfunc

    def __call__(self, *args, **kwargs):
        return self.zhuangshi2(*args, **kwargs)

    def zhuangshi2(self,func):
        def newfunc():
            print('装饰前')
            func()
            print('装饰后')
        return newfunc


# 利用类静态方法,本质和原来的用函数装饰一样
@MyClass.zhuangshi1
def func1():
    print('运行func1')

func1()

# 利用__call__方法
@MyClass()
def func2():
    print('运行了func2')

func2()
posted @ 2020-11-30 20:38  王寄鱼  阅读(82)  评论(0编辑  收藏  举报