函数进阶-装饰器

装饰器就是闭包的一个很好的应用拓展!!!

一丶无参装饰器

1.1 什么是装饰器

器指的是工具,而程序中的函数就是具备某一功能的工具,所以装饰器指的是为被装饰器对象添加额外功能。因此定义装饰器就是定义一个函数,只不过该函数的功能是用来为其他函数添加额外的功能。

需要注意的是:

装饰器本身其实是可以任意可调用的对象
被装饰的对象也可以是任意可调用的对象

1.2 为什么要用装饰器

如果我们已经上线了一个项目,我们需要修改某一个方法,但是我们不想修改方法的使用方法,这个时候可以使用装饰器。因为软件的维护应该遵循开放封闭原则,即软件一旦上线运行后,软件的维护对修改源代码是封闭的,对扩展功能指的是开放的。

装饰器的实现必须遵循两大原则:

不修改被装饰对象的源代码
不修改被装饰对象的调用方式
装饰器其实就是在遵循以上两个原则的前提下为被装饰对象添加新功能。

1.3 怎么用装饰器

改变源代码

import time

def index():
    start = time.time()
    print('welcome to index')
    time.sleep(1)
    end = time.time()
    print(F"index run time is {start-end}")

index()

welcome to index index run time is -1.0008180141448975
编写重复代码

import time

def index():
    print('welcome to index')
    time.sleep(1)

def f2():
    print('welcome to index')
    time.sleep(1)

start = time.time()
index()
end = time.time()
print(F"index run time is {start-end}")

start = time.time()
f2()
end = time.time()
print(F"f2 run time is {start-end}")

welcome to index index run time is -1.0046868324279785 welcome to index f2 run time is -1.000690221786499
第一种传参方式:改变调用方式

import time

def index():
    print('welcome to index')
    time.sleep(1)

def time_count(func):
    start = time.time()
    func()
    end = time.time()
    print(f"{func} time is {start-end}")

time_count(index)

welcome to index <function index at 0x102977378> time is -1.000748872756958
第二种传参方式:包给函数-外包

import time

def index():
    print('welcome to index')
    time.sleep(1)

def time_count(func):
    # func = 最原始的index
    def wrapper():
        start = time.time()
        func()
        end = time.time()
        print(f"{func} time is {start-end}")
    return wrapper

# f = time_count(index)
# f()

index = time_count(index)  # index为被装饰函数的内存地址,即index = wrapper
index()  # wrapper()

welcome to index <function index at 0x102977730> time is -1.0038220882415771

1.4 完善装饰器

上述的装饰器,最后调用index()的时候,其实是在调用wrapper(),因此如果原始的index()有返回值的时候,wrapper()函数的返回值应该和index()的返回值相同,也就是说,我们需要同步原始的index()和wrapper()方法的返回值。

import time


def index():
    print('welcome to index')
    time.sleep(1)

    return 123

def time_count(func):
    # func = 最原始的index
    def wrapper():
        start = time.time()
        res = func()
        end = time.time()
        print(f"{func} time is {start-end}")

        return res
    return wrapper

index = time_count(index)
res = index()
print(f"res: {res}")

welcome to index <function index at 0x102977620> time is -1.0050289630889893 res: 123
如果原始的index()方法需要传参,那么我们之前的装饰器是无法实现该功能的,由于有wrapper()=index(),所以给wrapper()方法传参即可。

import time


def index():
    print('welcome to index')
    time.sleep(1)
    return 123

def home(name):
    print(f"welcome {name} to home page")
    time.sleep(1)

    return name

def time_count(func):
    # func = 最原始的index
    def wrapper(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        end = time.time()
        print(f"{func} time is {start-end}")

        return res
    return wrapper

home = time_count(home)

res = home('egon')
print(f"res: {res}")

welcome egon to home page <function home at 0x102977378> time is -1.0039079189300537 res: egon

1.5装饰器语法糖

在被装饰函数正上方,并且是单独一行写上@装饰器名

import time

def time_count(func):
    # func = 最原始的index
    def wrapper(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        end = time.time()
        print(f"{func} time is {start-end}")

        return res
    return wrapper

@time_count  # home = time_count(home)
def home(name):
    print(f"welcome {name} to home page")
    time.sleep(1)

    return name

@time_count  # index = time_count(index)
def index():
    print('welcome to index')
    time.sleep(1)

    return 123

res = home('egon')
print(f"res: {res}")

welcome egon to home page <function home at 0x102977620> time is -1.0005171298980713 res: egon

1.6装饰器模板

双层装饰器模板

def deco(func): def wrapper(*args,**kwargs): res = func(*args,**kwargs) return res return wrapper

三层装饰器模板(有参装饰器)

一般参数器最多3层,也可以套更多层,但没有意义,3层足够装饰器本身的实现想要的功能添加的需求,当然也可以给某个想实现功能函数或类装饰更多的功能装饰器来实现不同场景情况
提前说下哈...


二丶有参装饰器

无参装饰器只套了两层,本节将讲一个套三层的装饰器——有参装饰器,但现在我们先实现一个用户登录注册的装饰器。

import time

current_user = {'username': None}

def login(func):
    # func = 最原始的index
    def wrapper(*args, **kwargs):
        if current_user['username']:
            res = func(*args, **kwargs)
            return res
        user = input('username: ').strip()
        pwd = input('password: ').strip()

        if user == 'nash' and pwd == '123':
            print('login successful')
            current_uesr['usre'] = user
            res = func(*args, **kwargs)
            return res
        else:
            print('user or password error')
    return wrapper

@login
def home(name):
    print(f"welcome {name} to home page")
    time.sleep(1)
    return name

@login
def index():
    print('welcome to index')
    time.sleep(1)
    return 123

res = index()

username: nash password: 123 login successful welcome to index
对于上面的登录注册,我们把用户登录成功的信息写入内存当中。但是在工业上,用户信息可以存在文本中、mysql中、mongodb当中,但是我们只让用户信息来自于file的用户可以认证。因此我们可以改写上述的装饰器。

import time

current_user = {'username': None}


def login(func):
    # func = 最原始的index
    def wrapper(*args, **kwargs):

        if current_user['username']:
            res = func(*args, **kwargs)
            return res

        user = input('username: ').strip()
        pwd = input('password: ').strip()
        
        engine = 'file'

        if engine == 'file':
            print('base of file')
            if user == 'nash' and pwd == '123':
                print('login successful')
                current_uesr['usre'] = user
                res = func(*args, **kwargs)
                return res
            else:
                print('user or password error')
        elif engine == 'mysql':
            print('base of mysql')
        elif engine == 'mongodb':
            print('base of mongodb')
        else:
            print('default')
    return wrapper

@login
def home(name):
    print(f"welcome {name} to home page")
    time.sleep(1)

@login
def index():
    print('welcome to index')
    time.sleep(1)


res = index()

username: nash password: 123 base of file login successful welcome to index

2.1三层闭包

def f1(y):
    def f2():
        x = 1
        def f3():
            print(f"x: {x}")
            print(f"y: {y}")
        return f3
    return f2

f2 = f1(2)
f3 = f2()
f3()

x: 1 y: 2
现在需求改了,我们需要判断用户动态的获取用户密码的方式,如果是file类型的,我们则让用户进行认证。因此我们可以使用有参装饰器。

import time

current_uesr = {'username': None}


def auth(engine='file'):

    def login(func):
        # func = 最原始的index
        def wrapper(*args, **kwargs):
            if current_user['username']:
                res = func(*args, **kwargs)

                return res

            user = input('username: ').strip()
            pwd = input('password: ').strip()

            if engine == 'file':
                print('base of file')
                if user == 'nash' and pwd == '123':
                    print('login successful')
                    current_uesr['usre'] = user
                    res = func(*args, **kwargs)
                    return res
                else:
                    print('user or password error')
            elif engine == 'mysql':
                print('base of mysql, please base of file')
            elif engine == 'mongodb':
                print('base of mongodb, please base of file')
            else:
                print('please base of file')

        return wrapper

    return login

@auth(engine='mysql')
def home(name):
    print(f"welcome {name} to home page")
    time.sleep(1)

@auth(engine='file')
def index():
    print('welcome to index')
    time.sleep(1)

res = index()

username: nash password: 123 base of file login successful welcome to index
由于两层的装饰器,参数必须得固定位func,但是三层的装饰器解除了这个限制。我们不仅仅可以使用上述单个参数的三层装饰器,多个参数的只需要在三层装饰器中多加入几个参数即可。也就是说装饰器三层即可,多加一层反倒无用。


三丶装饰器应用于类

回顾什么是装饰器:
  装饰器定义:本质就是函数(高阶函数),功能是为其他函数(对象)添加新功能

一、类的装饰器基本实现原理如下:

def deco(cls):
    print('类的装饰器=========》')
    print('='*20)
    return cls

@deco      #====> Foo = deco(Foo)
class Foo:
    pass

二、上述的简单装饰器是没有参数的,现在我们加上参数

def cls_decorator(**kwargs):            #支持传入参数(属性和对应的值),字典形式
    def deco(cls):
        for key,val in kwargs.items():
            setattr(cls,key,val)        #给类设置对应的属性和值
        return cls
    return deco

@cls_decorator(name='Menawey',age=24,gender='male')        # 1 运行cls_decorator(...),返回deco;2 @deco===> Peolple = deco(People)
#相当于给People类设置了name、age、gender属性
class People:
    pass

print(People.__dict__)                                   #查看被装饰过的类的属性字典

通过这样就可以动态的给不同的类在他实例化前增加属性

三、结合描述符
  通过描述符和类的装饰器组合使用,可以完成很多功能,比如为类添加属性,并且可以限定属性的类型。

#描述符
class Desc:
    def __init__(self,key,value_type):
        self.key = key
        self.value_type = value_type
    def __get__(self, instance, owner):
        return instance.__dict__[self.key]
    def __set__(self, instance, value):
        if not isinstance(value,self.value_type):
            raise TypeError('%s 传入的类型不是 %s'%(self.key,self.value_type))
        instance.__dict__[self.key] = value
    def __delete__(self, instance):
        instance.__dict__.pop(self.key)

#装饰器
def cls_decorator(**kwargs):            #支持传入参数(属性和对应的值),字典形式
    def deco(cls):
        for key,val in kwargs.items():    #这里需要用到描述符对属性进行代理,但是val是指定的类型,所以要用Desc(key,val)来描述为对应的值
            setattr(cls,key,Desc(key,val))        #给类设置对应的属性和值
        return cls
    return deco

@cls_decorator(name=str,age=int,gender=str,salary=float)    #使用装饰器

#被装饰和描述的类
class People:
    def __init__(self,name,age,gender,salary):
        self.name = name
        self.age = age
        self.gender = gender
        self.salary = salary

p1 = People('Menawey',24,'male',11.1)  #因为gender属性指定的是sstr,但是TypeError: age 传入的类型不是 <class 'int'>
print(People.__dict__)


四丶脑大洞开

What??!! python中类作为装饰器!!!??
参考链接:
https://blog.csdn.net/qq_29767317/article/details/80799410

da



#重写了__call__方法,类的对象就能够被调用,直接使用t()来调用类,并打印__call__方法里面的内容
class Test(object):
    print("--------test1----------")
    def __call__(self, *args, **kwargs):
        print(da) #注意一点,__call__方法里面的是最后执行。
    def  eat(self):
        print("-----------eat-------------")
    print("---------test2---------")
t = Test()
t() 

执行结果:
D:\python3.6\pythonw.exe F:/python项目/09Day/03python高级/01类作为装饰器.py
--------test1----------
---------test2---------
---------test---------

类做为装饰器:

class Test(object):
    def __init__(self,func):
        print("-------初始化-------")
        print("func name is %s" %func.__name__)
        self.__func = func #类的私有属性self.__func也指向了test1函数的内存地址。
    def __call__(self, *args, **kwargs): #test1 = Test(test1) #调用类的对象。就会调用call方法。
        print("------装饰器中的功能-------")
        self.__func() #self.__func指向了函数test1的内存地址。这句话相当于执行test1()函数。


#使用类作为装饰器,需要重写Call方法,没有调用test1()方法的时候,执行代码得到下面的结果
# -------初始化-------
# func name is test1

@Test #相当于 test1 = Test(test1)  也相当于func指向了下面函数test1的名字, 前面的test1指向了 Test()这个对象。
# 调用test1对象的时候相当于调用类方法Test(),调用类方法必调用__call方法,调用call方法的时候,先执行    print("------装饰器中的功能-------")
#然后在执行self.__func() ,因为self.__func函数指向的是test1函数,test1()相当于执行self.__func().
def test1():
    print("----test1---------")

test1() #调用test1

D:\python3.6\pythonw.exe F:/python项目/09Day/03python高级/02类作为装饰器.py
-------初始化-------
func name is test1
------装饰器中的功能-------
----test1---------

猜你喜欢

转载自www.cnblogs.com/suren-apan/p/11374767.html