Python 学习笔记: 装饰器

Python 装饰器

1 装饰器程序初步印象

import time


def func():
    time.sleep(0.01)
    print("程序运行干活。。。")


def timer(f):
    def inner():
        time1 = time.time()
        f()
        time2 = time.time()
        print(time2-time1)
    return inner


func = timer(func)
func()
View Code

使用@符号

import time

def timer(f):
    def inner():
        time1 = time.time()
        f()
        time2 = time.time()
        print(time2-time1)
    return inner

@timer
def func():
    time.sleep(0.01)
    print("程序运行干活。。。")

func()

 

2 装饰器的基本定式:

def wrapper(func):  # 装饰器函数, func是被装饰的函数
    def inner(*args, **kwargs):
        '''装饰器在装饰的函数运行前要执行的工作'''
        ret = func(*args,**kwargs)
        '''装饰器在装饰的函数运行后要执行的工作 '''
        return ret  # 返回被装饰函数的执行结果
    return inner
 
@wrapper          # 语法糖, qqqing = wrapper(qqqing)
def qqqing(a,b):  # 被装饰的函数
    print(a,b)
    return max(a,b)

# 正常调用函数
print(qqqing(2,3))

 3 , 带参数的装饰器。 

  应用场景举例: 设置一个变量Flag, Flag = True 时装饰器起作用, Flag = False 时装饰器不起作用。需要使用三层嵌套来定义。

FLAG = True

def wrapper_out(flag):
    def wrapper(func):  # 装饰器函数, func是被装饰的函数
        def inner(*args, **kwargs):
            if flag:
                '''装饰器在装饰的函数运行前要执行的工作'''
                print('装饰器在函数运行 之前 做的事情。。。')
                ret = func(*args,**kwargs)
                '''装饰器在装饰的函数运行后要执行的工作 '''
                print('装饰器在函数运行 之后 做的事情。。。')
                return ret  # 返回被装饰函数的执行结果
            else:
                '''装饰器不起作用时, 走这里'''
                ret = func(*args, **kwargs)
                return ret
        return inner
    return wrapper

@wrapper_out(FLAG)          #  wrapper_out(FLAG)先执行一次, 返回wrapper, 形成 @wrapper语法糖再去装饰。
def qqqing(a,b):            # 被装饰的函数
    print(a,b)
    return max(a,b)

# 正常调用函数
print(qqqing(2,3))

 

4 装饰器的修正, 为了不影响被装饰函数的一些属性(__name__, __doc__) 等, 使用 from functools import wraps 

from functools import wraps
FLAG = True
def wrapper_out(flag):
    def wrapper(func):  # 装饰器函数, func是被装饰的函数
        @wraps(func)    # 为了不改变被装饰函数的原有属性而增加的
        def inner(*args, **kwargs):
            if flag:
                '''装饰器在装饰的函数运行前要执行的工作'''
                print('装饰器在函数运行 之前 做的事情。。。')
                ret = func(*args,**kwargs)
                '''装饰器在装饰的函数运行后要执行的工作 '''
                print('装饰器在函数运行 之后 做的事情。。。')
                return ret  # 返回被装饰函数的执行结果
            else:
                '''装饰器不起作用时, 走这里'''
                ret = func(*args, **kwargs)
                return ret
        return inner
    return wrapper

@wrapper_out(FLAG)          #  wrapper_out(FLAG)先执行一次, 返回wrapper, 形成 @wrapper语法糖再去装饰。
def qqqing(a,b):            # 被装饰的函数
    '''被装饰函数的原来的doc信息在这里'''
    print(a,b)
    return max(a,b)

# 正常调用函数
print(qqqing(2,3))      #输出: 3       ### 这是函数的返回值
print(qqqing.__name__)  # 输出: qqqing
print(qqqing.__doc__)   # 输出: 被装饰函数的原来的doc信息在这里

 

5 , 多个装饰器装饰同一个函数的情况。

  

from functools import wraps
FLAG = True
def wrapper_out(flag):
    def wrapper(func):  # 装饰器函数, func是被装饰的函数
        @wraps(func)    # 为了不改变被装饰函数的原有属性而增加的
        def inner(*args, **kwargs):
            if flag:
                '''装饰器wrapper在装饰的函数运行前要执行的工作'''
                print('装饰器wrapper在函数运行 之前 做的事情。。。')
                ret = func(*args,**kwargs)
                '''装饰器wrapper在装饰的函数运行后要执行的工作 '''
                print('装饰器wrapper在函数运行 之后 做的事情。。。')
                return ret  # 返回被装饰函数的执行结果
            else:
                '''装饰器wrapper不起作用时, 走这里'''
                ret = func(*args, **kwargs)
                return ret
        return inner
    return wrapper

import time

def wrapper_out2(flag):
    def wrapper2(func):  # 装饰器函数, func是被装饰的函数
        @wraps(func)    # 为了不改变被装饰函数的原有属性而增加的
        def inner2(*args, **kwargs):
            if flag:
                start =  time.time()
                print('装饰器wrapper2在装饰的函数运行前要执行的工作')
                ret = func(*args,**kwargs)
                '''装饰器wrapper2在装饰的函数运行后要执行的工作 '''
                print('装饰器wrapper2在装饰的函数运行后要执行的工作')
                time.sleep(1)
                end = time.time()
                print('被装饰的函数运行时间为 %f'%(end-start))
                return ret  # 返回被装饰函数的执行结果
            else:
                '''装饰器wrapper2不起作用时, 走这里'''
                ret = func(*args, **kwargs)
                return ret
        return inner2
    return wrapper2


@wrapper_out2(FLAG)
@wrapper_out(FLAG)          #  wrapper_out(FLAG)先执行一次, 返回wrapper, 形成 @wrapper语法糖再去装饰。
def qqqing(a,b):            # 被装饰的函数
    '''被装饰函数的原来的doc信息在这里'''
    print(a,b)
    return max(a,b)

# 正常调用函数
print(qqqing(2,3))      #输出: 3       ### 这是函数的返回值
print(qqqing.__name__)  # 输出: qqqing
print(qqqing.__doc__)   # 输出: 被装饰函数的原来的doc信息在这里

输出结果为:

  装饰器wrapper2在装饰的函数运行前要执行的工作
  装饰器wrapper在函数运行 之前 做的事情。。。
  2 3
  装饰器wrapper在函数运行 之后 做的事情。。。
  装饰器wrapper2在装饰的函数运行后要执行的工作
  被装饰的函数运行时间为 1.000576
  3
  qqqing
  被装饰函数的原来的doc信息在这里

---------------

注意:被多个装饰器装饰的函数执行顺序。 

 

如果存在下面的装饰顺序:

 

执行顺序为:

 

6 下面做一些练习题来加深印象

  练习6.1 为正常调用的函数增加调用前验证用户名和密码功能, 如果通过验证则正常调用, 如果没有通过验证则不调用函数,并且一次通过后, 其它函数的调用不再验证, 验证的用户信息存储在文件中。

Flag = False


def wrapper(func):
    def inner(*args, **kwargs):
        global Flag
        if not Flag:
            username = input('请输入用户名:')
            password = input('请输入密码:')
            with open('loginfo.txt', mode='r', encoding='utf-8') as f:
                for line in f:
                    info = line.split('|')
                    if info[0].strip() == username and info[1].strip() == password:
                        Flag = True
                        break
        if Flag:
            ret = func(*args, **kwargs)
            return ret
    return inner


@wrapper
def shop_add():
    print('增加一件商品')


@wrapper
def shop_del():
    print('删除一件商品')

#正常调用
shop_add()
shop_del()

loginfo.txt 的内容如下:(中间以‘ | ’ 分开, 表示用户名 和密码), 当然啦, 密码可以转为MD5加密的存储, 在此就不弄了。这里主要是为了学习装饰器。

alex|alex3721
jacky|jacky3721

练习6.2 编写装饰器, 在文件中把调用的函数名记录。

def log(func):
    def inner(*args, **kwargs):
        with open('log', mode='a', encoding='utf-8') as f:
            f.write(func.__name__ + '\n')     # 记录调用函数的名字, \n 是为了每写一次换行
        ret = func(*args, **kwargs)
        return ret
    return inner

@log
def shop_add():
    print('增加一件商品')


@log
def shop_del():
    print('删除一件商品')

#正常调用
shop_add()
shop_del()
shop_add()
shop_del()
shop_add()
shop_del()

 

练习6.3 从网上读取一个页面, 如果本地有缓存的页面, 就直接从本地读。

import os
from urllib.request import urlopen

def webcache(func):                    #装饰器
    def inner(*args, **kwargs):
        if os.path.getsize('webcache'):
            with open('webcache', mode='rb') as f:
                return f.read()
        ret = func(*args, **kwargs)
        with open('webcache', mode='wb') as f:
            f.write(b'===========\n'+ret)  #“===========\n" 是为了输出时能看明白是否从本地读的文件内容。
        return ret
    return inner

@webcache  
def get(url):
    code = urlopen(url).read()
    return code

#开始正常调用
print(get('http://www.baidu.com'))

 

7 类装饰器 (待续)

 

posted @ 2018-11-14 17:21  程序猿🌽  阅读(108)  评论(0编辑  收藏  举报