装饰器详解

Posted midworld

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了装饰器详解相关的知识,希望对你有一定的参考价值。

装饰器(Decorator)本质是函数,功能是为其他函数添加附加功能,定义一个装饰器需要满足以下两个原则:

  • 不修改被修饰函数源代码(开放封闭原则)
  • 不修改被修饰函数的调用方式

装饰器 = 高阶函数 + 函数嵌套 + 闭包

1. 高阶函数

高阶函数定义:

  • 函数接收的参数是一个函数
  • 函数的返回值是一个函数

满足以上任意一个条件就是高阶函数。

def calc():
    print(‘calc 运行完毕~‘)
f = calc
f()

由于函数也是对象,因此函数也可以赋值给一个变量,调用这个变量就是调用函数。函数对象有一个 __name__属性,用于查看函数的名字:

calc.__name__
f.__name__

‘calc‘
‘calc‘

需求:

在不修改 calc 源代码的前提下,为 calc函数添加一个代码计时功能(计算 calc运行时间)。

1.1 把函数当做参数传给另一个函数

使用高阶函数模拟装饰器实现calc计时功能。

import time

def timmer(func):
    start_time = time.time()
    func()
    stop_time = time.time()
    print(‘函数 %s 运行时间为:%s‘ % (func.__name__, stop_time - start_time))
    
def calc():
    time.sleep(2)
    print(‘calc 运行完毕~‘)

timmer(calc)
calc 运行完毕~
calc 运行时间为:2.0011146068573

虽然为 calc 增加了计时功能,但 calc 原来的执行方式是 calc(),而现在是调用高阶函数timmer(calc),改变了调用方式。

1.2 函数的返回值是函数

import time

def timmer(func):       # func: calc
    start_time = time.time()
    func()              # func(): calc()
    stop_time = time.time()
    print(‘函数 %s 运行时间为:%s‘ % (func.__name__, stop_time - start_time))
    return func         # func: calc (<function calc at 0x00000000052D6D90>) 内存地址
    
def calc():
    time.sleep(2)
    print(‘calc 运行完毕~‘)

calc = timmer(calc)    # calc = timmer(calc): calc = <function calc at 0x00000000052D6D90>
calc()
calc 运行完毕~
calc 的运行时间是:2.0001144409179688
calc 运行完毕~

没有改变 calc 的调用方式,但也没为其添加新功能。

1.3 总结

使用高阶函数实现装饰器功能 :

  • 函数接收的参数是一个函数名
    • 作用:在不修改函数源代码的前提下,为函数添加新功能。
    • 不足:会改变函数的调用方式
  • 函数返回值是一个函数名
    • 作用:不修改函数调用方式
    • 不足:不能添加新功能

2. 函数嵌套

函数中嵌套另一个函数

def father(name):
    print(‘I am %s‘ % name)
    def son():
        print(‘My father is %s‘ % name)
        def grandson():
            print(‘My grandfather is %s‘ % name)
        grandson()
    son()
father(‘tom‘)
I am tom
My father is tom
My grandfather is tom

3. 闭包

闭包也是函数嵌套,如果在一个内部函数里调用外部作用域(不是全局作用域)的变量,那么这个内部函数就是闭包(closure)

def father(name):
    print(‘I am %s‘ % name)
    def son():
        name = ‘john‘
        def grandson():
            print(‘My father is %s‘ % name)
        grandson()
    son()
father(‘tom‘)
I am tom
My father is john

内部函数 grandson 调用了它的外部函数 son 的变量 name=‘john‘,那么 grandson就是一个闭包。

4. 无参数装饰器

无参数装饰器 = 高阶函数 + 函数嵌套

4.1 基本框架

# 实现一个装饰器的基本框架
def timmer(func):
    def wrapper():
        func()
    return wrapper

4.2 加上参数

def timmer(func):
    def wrapper(*args, **kwargs):
        func(*args, **kwargs)
    return wrapper

4.3 加上功能

import time

def timmer(func):
    def wrapper(*args, **kwargs):
        """计时功能"""
        start_time = time.time()
        func(*args, **kwargs)
        stop_time = time.time()
        print(‘函数 %s 运行时间:%s‘ % (func, stop_time - start_time))
    return wrapper

4.4 加上返回值

import time

def timmer(func):
    def wrapper(*args, **kargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        stop_time = time.time()
        print(‘函数 %s 运行时间:%s‘ % (func, stop_time - start_time))
        return res
    return wrapper

4.5 使用装饰器

def calc():
    time.sleep(2)
    print(‘calc 运行完毕~‘)
    return ‘calc 返回值‘
calc = timmer(calc)
calc()

4.6 语法糖 @

@timmer        # 相当于 calc = timmer(calc)
def calc():
    time.sleep(2)
    print(‘calc 运行完毕~‘)
calc()

4.7 示例

使用无参数装饰器,为 calc添加计时功能(统计 calc代码运行时间)

import timmer

def timmer(func):               # func: calc
    """装饰器函数"""
    def wrapper(*args, **kwargs):       # args:<class ‘tuple‘>:(‘rose‘, 18, ‘female‘)
        start_time = time.time()        # kwargs={}
        res = func(*args, **kwargs)     # res: ‘calc 返回值‘
        stop_time = time.time()
        print(‘函数 %s 运行时间:%s‘ % (func.__name__, stop_time - start_time))
        return res       # func: calc (<function calc at 0x00000000052D6D90>) 内存地址
    return wrapper

@timmer                 # @timmer: calc=timmer(calc)  ——>calc = <function wrapper at 0x00000000052D6D90>
def calc(name, age, gender):
    """被修饰函数"""
    time.sleep(2)
    print(‘calc 运行完毕~‘)
    print(‘名字:%s,年龄:%d,性别:%s‘ % (name, age, gender))
    return ‘calc 返回值‘

s = calc(‘rose‘, 18, ‘female‘)      # 相当于执行 s = wrapper(‘rose‘, 18, ‘female‘)
print(s)    
calc 运行完毕
名字:rose,年龄:18,性别:female
函数 calc 运行时间:2.0001144409179688
calc 返回值

由于 timmer() 是一个装饰器函数,返回一个函数 wrapper。所以 calc()函数仍然存在,只是现在同名的 calc 变量指向了新的函数,于是调用 calc() 执行的是wrapper()函数。

5. 有参数装饰器

有参数装饰器 = 高阶函数 + 函数嵌套 + 闭包

如果装饰器本山需要传入参数,就需要再编写一个 decorator的高阶函数。比如给 calc函数添加一个日志功能,能够打印日志,其基本框架如下:

def log(text):
    def timmer(func):
        def wrapper(*args, **kwargs):
            func(*args, **kwargs)
        return wrapper
    return timmer

@log(‘文本内容‘)
def calc():
    pass

示例:

import timmer

def log(text):          # text: ‘文本内容‘
    def timmer(func):               # func: calc
        """装饰器函数"""
        def wrapper(*args, **kwargs):   # args:<class ‘tuple‘>:(‘rose‘, 18, ‘female‘)
            start_time = time.time()        # kwargs={}
            res = func(*args, **kwargs)     # res: ‘calc 返回值‘
            stop_time = time.time()
            print(‘函数 %s 运行时间:%s‘ % (func.__name__, stop_time - start_time))
            return res     # func: calc (<function calc at 0x00000000052D6D90>) 内存地址
        return wrapper
    return timmer

@log(‘文本内容‘)     # 相当于 calc = log(‘自定义文本‘)(calc)   ——> timmer(calc)  ——> calc=wrapper
def calc(name, age, gender):
    """被修饰函数"""
    time.sleep(2)
    print(‘calc 运行完毕~‘)
    print(‘名字:%s,年龄:%d,性别:%s‘ % (name, age, gender))
    return ‘calc 返回值‘

s = calc(‘rose‘, 18, ‘female‘)      # 相当于执行 s = wrapper(‘rose‘, 18, ‘female‘)
print(s)    

与两层嵌套效果的decorator相比,三层效果是这样的:

calc = log(‘自定义文本‘)(calc)       # 即 calc = wrapper

首先执行 log(‘自定义文本‘),返回timmer函数,再调用返回参数(timmer(calc)),参数是 calc,返回值是wrapper函数,最后再调用wrapper()

6. 对象属性

函数也是对象,也有__name__属性(返回函数名)。但经过装饰的calc函数,它的__name__

从原来的calc变成了wrapper

>>> calc.__name__
‘wrapper‘

有些需要依赖函数签名的代码因为__name__改变,而出现某些错误,所以需要将原calc__name__属性复制到wrapper()函数中。

import functools
....
@functools.wraps(func)
def wrapper(*args, **kwargs)
...

7. 实例

实例 1

请设计一个decorator,它可作用于任何函数上,并打印该函数的执行时间:

import time
import functools


def metric(fn):
    @functools.wraps(fn)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        res = fn(*args, **kwargs)
        stop_time =time.time()
        print(‘%s executed in %s ms‘ % (fn.__name__, stop_time - start_time))
        return res
    return wrapper


@metric         # fast=metric(calc)
def fast(x, y):
    time.sleep(0.0012)
    return x + y


@metric
def slow(x, y, z):
    time.sleep(0.1234)
    return x * y * z

f = fast(11, 22)
s = slow(11, 22, 33)
print(f, s)
fast executed in 0.0019998550415039062 ms
slow executed in 0.1240072250366211 ms
33 7986

实例 2

实现一个购物网站基本功能,其功能如下:

  • 提示用户输入用户名和密码
  • 用户个人界面和购物车不需要登录(保持会话)
import time
import functools

# 模拟存储用户名、密码数据库
user_list=[
    {‘name‘:‘alex‘,‘passwd‘:‘123‘},
    {‘name‘:‘linhaifeng‘,‘passwd‘:‘123‘},
    {‘name‘:‘wupeiqi‘,‘passwd‘:‘123‘},
    {‘name‘:‘yuanhao‘,‘passwd‘:‘123‘},
]

current_dic = {‘username‘: None, ‘login‘: False}   # 用于保存登录记录,None,False 为没有登录

def auth_func(func):
    """装饰器函数"""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # 仅供 home、shopping 调用(因为不需要再输入用户名和密码)
        if current_dic[‘username‘] and current_dic[‘login‘]:
            res = func(*args, **kwargs)
            return res
        
        username = input(‘用户名:‘).strip()
        passwd = input(‘密码:‘).strip()
        # 遍历循环用户名、密码数据库,比对用户输入的用户名和密码
        for user_dic in user_list:
            if username == user_dic[‘name‘] and passwd == user_dic[‘passwd‘]:
                # 用户保持会话,即保持用户登录状态,赋值
                current_dic[‘username‘] = username
                current_dic[‘login‘] = True
                res = func(*args, **kwargs)
                return res
        else:
            print(‘用户名或密码错误‘)
    return wrapper

@auth_func
def index():
    """主页(需要登录)"""
    print(‘欢迎来到xxx主页!‘)
    
@auth_func    
def home(name):
    """用户登录成功后的界面"""
    print(‘欢迎回家 %s‘ % name)

@auth_func
def shopping_car(name):
    """购物车"""
    print(‘%s购物车里有:[%s,%s,%s]‘ % (name, ‘游艇‘, ‘车子‘, ‘飞机‘))
    
print(‘before-->‘,current_dic) 
index()
print(‘after-->‘,current_dic)
home(‘rose‘)
shopping_car(‘rose‘)
before--> {‘username‘: None, ‘login‘: False}
用户名:alex
密码:123
欢迎来到京东主页!
after--> {‘username‘: ‘alex‘, ‘login‘: True}
欢迎回家 rose
rose购物车里有:[游艇,车子,飞机]

带参数装饰器(需要认证类型):

import time
import functools

user_list=[
    {‘name‘:‘alex‘,‘passwd‘:‘123‘},
    {‘name‘:‘linhaifeng‘,‘passwd‘:‘123‘},
    {‘name‘:‘wupeiqi‘,‘passwd‘:‘123‘},
    {‘name‘:‘yuanhao‘,‘passwd‘:‘123‘},
]
current_dic={‘username‘:None,‘login‘:False}

def auth(auth_type=‘filedb‘):
    def auth_func(func):
        @funtools.wraps(func)
        def wrapper(*args,**kwargs):
            print(‘认证类型是‘,auth_type)
            if auth_type == ‘filedb‘:
                if current_dic[‘username‘] and current_dic[‘login‘]:
                    res = func(*args, **kwargs)
                    return res
                
                username=input(‘用户名:‘).strip()
                passwd=input(‘密码:‘).strip()
                for user_dic in user_list:
                    if username == user_dic[‘name‘] and passwd == user_dic[‘passwd‘]:
                        current_dic[‘username‘]=username
                        current_dic[‘login‘]=True
                        res = func(*args, **kwargs)
                        return res
                else:
                    print(‘用户名或者密码错误‘)
            elif auth_type == ‘ldap‘:
                print(‘ldap 认证类型‘)
                res = func(*args, **kwargs)
                return res
            else:
                print(‘不知道什么认证方式‘)
                res = func(*args, **kwargs)
                return res

        return wrapper
    return auth_func

# 相当于 auth_func = auth(auth_type=‘filedb‘)(auth_func)
@auth(auth_type=‘filedb‘) 
def index():
    print(‘欢迎来到xxx主页‘)

@auth(auth_type=‘ldap‘)
def home(name):
    print(‘欢迎回家%s‘ %name)
#
@auth(auth_type=‘sssssss‘)
def shopping_car(name):
    print(‘%s的购物车里有[%s,%s,%s]‘ %(name,‘奶茶‘,‘妹妹‘,‘娃娃‘))

# print(‘before-->‘,current_dic)
# index()
# print(‘after--->‘,current_dic)
# home(‘产品经理‘)
shopping_car(‘产品经理‘)

以上是关于装饰器详解的主要内容,如果未能解决你的问题,请参考以下文章

Python装饰器详解

python装饰器详解

python装饰器详解

python类装饰器详解

python装饰器详解

装饰器详解