python-装饰器

一、概念

  在不修改原函数的基础上,加上额外的功能。但是他的本质也是函数

二、原则   

  1、不修改被修饰的原代码   

  2、不修改被修饰的调用方式

三、装饰器的组成

  装饰器 = 高阶函数 + 函数嵌套 + 闭包
  1、高阶函数:
    1.1、函数接收的参数是一个函数名
    1.2、函数的返回值是一个函数名
    1.3、满足上述条件任意一个,都可称之为高阶函数

  2、函数嵌套:
   函数里定义函数

 

  3、闭包:
    作用域

  

四、装饰器的实现过程

input:
# 我们要在不修改foo函数的基础上加上统计运行时间
import time
def foo():
    time.sleep(1)
    print('世界真奇妙')


def timer(func):
    start_time = time.time()
    func()  # 在函数里调用foo函数
    end_time = time.time()
    print('foo函数运行时间是:', end_time - start_time)
    return func


timer(foo)

output:
世界真奇妙
foo函数运行时间是: 1.0000574588775635

  这样我们实现了统计运行时间的功能,但是别忘了装饰器的一个原则不能修改函数的调用方式

input:
import time

# 我们要在不修改foo函数的基础上加上统计运行时间
def foo():
    time.sleep(1)
    print('世界真奇妙')


def timer(func):
    start_time = time.time()
    func()  # 在函数里调用foo函数
    end_time = time.time()
    print('foo函数运行时间是:', end_time - start_time)
    return func


# timer(foo)

# 我们实现了统计运行时间的功能,但是别忘了装饰器的一个原则不能修改函数的调用方式

foo = timer(foo)  # timer函数的返回值是foo函数的内存地址,我们赋值给一个foo,然后再调用
print(foo)
foo()

output:
世界真奇妙
foo函数运行时间是: 1.0000574588775635
<function foo at 0x00000000004CC268>
世界真奇妙
到这里我们又实现了不修改调用方式,但是还有问题,就是foo函数被多执行了一遍!
那么我们有什么办法解决呢?
我们知道函数不加()是不执行的,那么我们可不可以将foo放到一个函数中呢?也就是嵌套函数
input:

import time

def foo():
    time.sleep(1)
    print('世界真奇妙')

def timer(func):
    def wrapper(): # 在timer函数中有定义一个wrapper函数
        start_time = time.time()
        func()
        end_time = time.time()
        print('foo函数运行时间是:', end_time - start_time)
    return wrapper  # 返回的是wrapper函数的内存地址


foo = timer(foo)  # wrapper函数的内存地址
foo()  # 执行wrapper

output:
世界真奇妙
foo函数运行时间是: 1.0000572204589844

  这时候我们已经基本实现了装饰器的功能,但是我们的调用方式还是有问题,我们调用的过程其实是两步
  foo = timer(foo)
  foo()
  python提供了一个方法
  在被装饰的函数上加 @timer ,@timer 相当于foo = timer(foo)

 

input:

import time

def timer(func):
    def wrapper():
        start_time = time.time()
        func()
        end_time = time.time()
        print('foo函数运行时间是:', end_time - start_time)

    return wrapper  # 返回的是wrapper函数的内存地址

@timer    #相当于foo = timer(foo)
def foo():
    time.sleep(1)
    print('世界真奇妙')

foo()


output:
世界真奇妙
foo函数运行时间是: 1.0020573139190674

  在foo()没有返回值的时候,这样是没有问题了,但是如果有返回值的时候呢?

input:
import time

def timer(func):
    def wrapper():
        start_time = time.time()
        func()
        end_time = time.time()
        print('foo函数运行时间是:', end_time - start_time)
        return '这里是wrapper的返回值'
    return wrapper  # 返回的是wrapper函数的内存地址

@timer
def foo():
    time.sleep(1)
    print('世界真奇妙')
    return '这里是foo的返回值'


print(foo())

output:
世界真奇妙
foo函数运行时间是: 1.0000572204589844
这里是wrapper的返回值

  我们发现,执行foo()实际上是返回了wrapper函数的返回值,那我们要怎么拿到foo()的返回值呢?
  可以将foo()的返回值赋值给一个变量,然后wrapper返回这个变量

input:

import time


def timer(func):
    def wrapper():
        start_time = time.time()
        res = func()
        end_time = time.time()
        print('foo函数运行时间是:', end_time - start_time)
        return res  # 返回func的返回值

    return wrapper  # 返回的是wrapper函数的内存地址


@timer
def foo():
    time.sleep(1)
    print('世界真奇妙')
    return '这里是foo的返回值'


print(foo())

output:
世界真奇妙
foo函数运行时间是: 1.0000569820404053
这里是foo的返回值

 

  到这来还有一个问题,如果foo()需要传入参数呢?

 

input:
import time


def timer(func):
    def wrapper():
        start_time = time.time()
        res = func()
        end_time = time.time()
        print('foo函数运行时间是:', end_time - start_time)
        return res  # 返回func的返回值

    return wrapper  # 返回的是wrapper函数的内存地址


@timer
def foo(name):  # 需要传入一个参数
    time.sleep(1)
    print('%s说世界真奇妙啊' %name)
    return '这里是foo的返回值'


print(foo('马云'))

output:
Traceback (most recent call last):
  File "D:/pythonproject/work/venv/day20/装饰器.py", line 145, in <module>
    print(foo('马云'))
TypeError: wrapper() takes 0 positional arguments but 1 was given

 


这来报了一个wrapper()不需要参数但是我们传入了一个,但是我们其实是想把参数入到给foo()的啊?

有什么办法解决呢?

闭包,我们可以给wrapper一个参数,在函数里的作用域了只要没有给这个参数重新赋值,那foo就可以接收到这个参数

input:
import time


def timer(func):
def wrapper(*args):
start_time = time.time()
res = func(args)
end_time = time.time()
print('foo函数运行时间是:', end_time - start_time)
return res # 返回func的返回值

return wrapper # 返回的是wrapper函数的内存地址


@timer
def foo(name): # 需要传入一个参数
time.sleep(1)
print('%s说世界真奇妙啊' % name)
return '这里是foo的返回值'


print(foo('马云'))
 output: 马云说世界真奇妙啊 foo函数运行时间是: 1.0000288486480713 这里是foo的返回值

 



耶!!!可以传参了。但是问题又来了,如果是多个参数呢?

可变参数!!!

input:
import time


def timer(func):
    def wrapper(*args,**kargs): # 可变参数,是一个数组*('马云',24)和字典**{'gender': '男'}
        start_time = time.time()
        res = func(*args,**kargs)
        print(args)
        print(kargs)
        end_time = time.time()
        print('foo函数运行时间是:', end_time - start_time)
        return res  # 返回func的返回值

    return wrapper  # 返回的是wrapper函数的内存地址


@timer
def foo(name,age,gender):  # 需要传入一个参数
    time.sleep(1)
    print('%s在他%s岁的时候说,说%s人的世界真奇妙啊'  %(name,age,gender))
    return '这里是foo的返回值'


print(foo('马云',24,gender=''))

output:
马云在他24岁的时候说,说男人的世界真奇妙啊
('马云', 24)
{'gender': ''}
foo函数运行时间是: 1.0002455711364746
这里是foo的返回值

 

五、实例

  带验证功能装饰器

input:

# 假设目前系统的注册用户如下:
user_lis = [{'name': '刘备', 'passwd': '123'}, {'name': '张飞', 'passwd': '234'}, {'name': '关羽', 'passwd': '345'}]

# 登录状态
user_dic = {'name': None, 'login': False}


def auth_func(func):
    def wrapper(*args, **kwargs):
        if user_dic['name'] and user_dic['login']:  # 先判断当前用户登录状态,如果已经登录,直接执行func()
            res = func(*args, **kwargs)
            return res
        username = input('用户名:')  # 如果没有登录,则要求输入用户名和密码
        passwd = input('密码:')
        for i in user_lis:  # 遍历判断用户名、密码是否正确
            if i['name'] == username and i['passwd'] == passwd:
                user_dic['name'] = username  # 正确则修改用户名和状态
                user_dic['login'] = True
                res = func(*args, **kwargs)  # 并执行func
                return res
        else:
            print('用户名或密码错误')

    return wrapper


@auth_func
def index():
    print('欢迎来到京东首页')


@auth_func
def shoping_car(name):
    print('%s京东购物车' % name)


@auth_func
def home(name):
    print('%s的个人中心' % name)


index()

shoping_car('用户')

home('嘎嘎')

output:
用户名:张飞
密码:234
欢迎来到京东首页
用户京东购物车
嘎嘎的个人中心

 

验证用户名和密码有不同的方式,如果我们要处理不同的认证方式,我们就需要给 auth_func()传入认证方式auth_type

那我们怎么给修饰传参呢?

还是用到闭包

我们在原来的基础的在外再加一个函数,带参数的修饰器

input:
def auth(auth_type='file'):
    def auth_func(func):
        def wrapper(*args, **kwargs):
            if auth_type == 'file':
                print('这是文件验证')
                if user_dic['name'] and user_dic['login']:  # 先判断当前用户登录状态,如果已经登录,直接执行func()
                    res = func(*args, **kwargs)
                    return res
                username = input('用户名:')  # 如果没有登录,则要求输入用户名和密码
                passwd = input('密码:')
                for i in user_lis:  # 遍历判断用户名、密码是否正确
                    if i['name'] == username and i['passwd'] == passwd:
                        user_dic['name'] = username  # 正确则修改用户名和状态
                        user_dic['login'] = True
                        res = func(*args, **kwargs)  # 并执行func
                        return res
                else:
                    print('用户名或密码错误')
            elif auth_type == 'oracle':
                print('这是数据库验证,没有数据库认证个毛线,直接看吧')
                res = func(*args,**kwargs)
                return res
            else:
                print('其它验证方式,也可以直接看')
                res = func(*args,**kwargs)
                return res
        return wrapper
    return auth_func


# @auth_func
@auth(auth_type='file')     # auth_func=auth(auth_type='filedb')-->@auth_func 附加了一个auth_type  --->index=auth_func(index)
def index():
    print('欢迎来到京东首页')


# @auth_func
@auth(auth_type='oracle')
def shoping_car(name):
    print('%s京东购物车' % name)


# @auth_func
@auth(auth_type='gg')
def home(name):
    print('%s的个人中心' % name)


index()

shoping_car('用户')

home('嘎嘎')


output:
这是文件验证
用户名:z张飞
密码:232
用户名或密码错误
这是数据库验证,没有数据库认证个毛线,直接看吧
用户京东购物车
其它验证方式,也可以直接看
嘎嘎的个人中心

 

 六、类的装饰器

  1、类装饰器的基本原理

def desc(obj):
    print("执行desc")
    obj.x = 3   #添加类属性
    obj.y = 4
    return obj

@desc # 等于Foo = desc(Foo)
class Foo:
    pass

print(Foo.__dict__)
类装饰器基本原理

 

  运行结果

执行desc
{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None, 'x': 3, 'y': 4}

 

 

   2、类装饰器添加类属性实例

def demo(**kwargs):
def wapper(obj):
for key,val in kwargs.items():
setattr(obj,key,val)
return obj
return wapper

@demo(name="江南七怪") #demo(name = "江南七怪") → 返回wapper并执行 wapper(Foo) Foo = wapper(Foo)
class Foo:
pass


print(Foo.__dict__)

 

   运行结果

{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None, 'name': '江南七怪'}

 

 

  3、类装饰器的应用

  类装饰器与描述符想结合,实现实例化过程中传入参数的数据类型限制

 

class Type:

    def __init__(self, key, data_type):
        self.key = key
        self.data_type = data_type  # 传入需要限制数据类型

    def __get__(self, instance, owner):
        return instance.__dict__[self.key]

    def __set__(self, instance, value):
        if not isinstance(value, self.data_type):
            raise TypeError("传入的%s不是%s类型" % (value, self.data_type))
        instance.__dict__[self.key] = value  # 将实例的属性赋值

    def __delete__(self, instance):
        print("运行delete")
        instance.__dict__.pop(self.key)

def demo(**kwargs):
    def wapper(obj):
        for key, val in kwargs.items():
            setattr(obj, key, Type(key, val))  # 调用描述符并添加属性 setattr(People,"name",Type("name",str))
        return obj

    return wapper


@demo(name=str, age=int, salary=int)
class People:

    def __init__(self, name, age, salary):
        self.name = name
        self.age = age
        self.salary = salary


p = People("张无忌", 25, 10000)
print(People.__dict__)
print(p.name, p.age, p.salary)

 

  运行结果

{'__module__': '__main__', '__init__': <function People.__init__ at 0x00000000021BBE18>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None, 'name': <__main__.Type object at 0x00000000021CD2B0>, 'age': <__main__.Type object at 0x00000000021CD358>, 'salary': <__main__.Type object at 0x00000000021CD630>}
张无忌 25 10000

 

posted on 2019-11-06 23:25  别动我的锅  阅读(91)  评论(0)    收藏  举报

导航

levels of contents