函数装饰器

python装饰器


装饰器
  • 需求
    • 一个加法函数,想增强它的功能,能够输出被调用过以及调用的参数信息
      def add(x, y):
          return x + y
      
      下面增加信息输出功能
      def add(x, y):
           print("call add, x + y")  # 日志输出到控制台
           return x + y
      
  • 上面的加法函数是完成了需求,但是有以下的缺点
    • 打印是一个功能,这条语句和add函数耦合太高
    • 加法函数属于业务功能,而输出信息的功能,属于非业务功能代码,不该放在业务函数add中
装饰器
  • 下面代码做到了业务功能分离,但是fn函数调用传参是个问题
def add(x,y):
    return x + y
    
def logger(fn):  #测试用,拥有增强功能,但是不能更改参数,不能做到多次不同的参数测试
    print('program begin') # 增强的输出
    x = fn(4,5)
    print('program end') # 增强的功能
    return x
print(logger(add))
  • 下面解决参数传输问题
def logger(fn,* args,** kwargs):
    print('program begin')
    x = fn(* args,** kwargs)  # fn就是add函数对象,调用fn时,实际调用add(),此时参数可以在logger函数中传入,fn函数(add)拿到参数
    print('program end')
    return x
print(logger(add,5,y=60))

#将上代码柯里化

def logger(fn):
    def wrapper(* args,** kwargs):
        print('program begin')
        x = fn(* args,** kwargs) 
        print('program working')
        print('program end')
        return x
    return wrapper
#fe = logger(add)(5,y=60)  # 装饰器柯里化写法
#print(fe)
add = logger(add)  #先算等号右边,再赋值给左边,看似原来的函数add,实际上是重新定义过的函数add
print(add(x=5,y=60))
装饰器语法
def logger(fn):
    def wrapper(* args,** kwargs):
        print('program begin')
        x = fn(* args,** kwargs) 
        print('program working')
        print('program end')
        return x
    return wrapper


@logger   # 等价于add = logger(add)
def add(x,y):
    return x + y
print(add(45,40))
  • 装饰器(无参)
    • 它是一个函数
    • 函数作为它的形参。无参装饰器实际上就是一个单形参函数
    • 返回值也是一个函数
    • 可以使用@functionname方式,简化调用
  • 装饰器和高阶函数
    • 装饰器可以是高阶函数,但装饰器只是对传入函数的功能的装饰(功能增强)

举例 :

import datetime
import time

def logger(foo):
    def wrapper(*args,**kwargs):
        print("{} program start".format(foo.__name__))
        start = datetime.datetime.now()
        print("{} program args={},kwargs={}".format(foo.__name__,args,kwargs))
        cc = foo(* args,** kwargs)
        print("{} program end".format(foo.__name__))
        dalta = datetime.datetime.now()-start
        print("{} program took {}".format(foo.__name__,dalta.total_seconds()))
        return cc
    return wrapper

@logger  
def add(x,y):
    """This is a function of addition"""
    
    """The function of addition is """
    time.sleep(0.1)
    return x + y

add(10000,y=12)
print("name={},doc={}".format(add.__name__, add.__doc__))

_______________________________________________________________________
>>> add program start
>>> add program args=(10000,),kwargs={'y': 12}
>>> add program end
>>> add program took 0.101882
>>> name=target,doc=None   
  • name=target,ndoc=None 上面输出的属性,原函数的属性被改掉了,直接输出了装饰器函数target的属性,这是装饰器带来的副作用
Python的文档
  • Python文档字符串Documentation Strings
  • 在函数语句块的第一行,且习惯是多行的文本,所以多使用三引号
  • 惯例是首字母大写,第一行写概述,空一行,第三行写详细描述
  • 可以使用特殊属性__doc__访问这个文档
def add(x,y):
    """This is a function of addition"""
    
    """The function of addition is """
    return x + y
print("name={}\ndoc={}".format(add.__name__, add.__doc__))
装饰器的副作用
  • 原函数对象的属性都被替换了,而使用装饰器,我们的需求是查看被封装函数的属性

下面进行改造装饰器

import datetime
import time

def copy_properties(src, dest):  # 用此函数改造装饰器 src 为原函数,dest为需要改造的函数
    dest.__name__ = src.__name__  # 用原函数name属性赋给装饰器内层函数的name属性
    dest.__doc__ = src.__doc__  # 用原函数doc属性赋给装饰器内层函数的doc属性

def logger(foo):
    def wrapper(*args, **kwargs):
        print("{} program start".format(foo.__name__))
        start = datetime.datetime.now()
        print("{} program args={},kwargs={}".format(foo.__name__, args, kwargs))
        cc = foo(* args, ** kwargs)
        print("{} program end".format(foo.__name__))
        dalta = datetime.datetime.now() - start
        print("{} program took {}".format(foo.__name__, dalta.total_seconds()))
        return cc
    copy_properties(foo, wrapper)  # 此语句是调用函数copy_properties函数,将foo(实际上是add的属性)的属性赋给wrapper
    return wrapper   # 返回wrapper函数时,已经接受到属性的更改

@logger
def add(x, y):
    """This is a function of addition

    The function of addition is... """
    time.sleep(0.1)
    return x + y


add(10000, y=12)
print("name={},\ndoc={}".format(add.__name__, add.__doc__))
_______________________________________________________________________________________

>>>add program start
>>>add program  args=(10000,),kwargs={'y': 12}
>>>add program end
>>>add program took 0.10073
>>>name=add,
>>>doc=This is a function of addition
    
           The function of addition is... 

name=add,
doc = This is a function of addition

   The function of addition is
上面的输出属性被改过来了,可以直接输出原函数的属性


  • 通过copy_properties函数将被包装函数的属性(add函数为被包装函数)覆盖掉包装函数target函数
  • 凡是被装饰的函数都需要复制这些属性,这个函数很通用
  • 可以将复制属性的函数构建成装饰器函数,带参装饰器
带参装饰器
import datetime
import time


def copy_properties(src):
    def _copy(dest):  # 此处装饰器柯里化
        dest.__name__ = src.__name__
        dest.__doc__ = src.__doc__
        return dest
    return _copy


def test(flag):
    def logger(fn):
        @copy_properties(fn)  # 等效wrapper = copy_propertie(fn)(wrapper),此处用copy_propertie(fn)函数来装饰wrapper函数,装饰器里套装饰器,带参装饰器
        def wrapper(* args, ** kwargs):
            print("{} program start".format(fn.__name__))
            start = datetime.datetime.now()
            cc = fn(* args, ** kwargs)
            dalta = datetime.datetime.now() - start
            print("name= {},\ndoc={}".format(add.__name__, add.__doc__))
            print("{} program end had took {}".format(fn.__name__, dalta.total_seconds()))
            print(
                "{} to slow".format(fn.__name__) if dalta.total_seconds() > flag else "{} OK,fast".format(fn.__name__))
            return cc
        return wrapper
    return logger  # 返回值是一个不带参的装饰器函数

@test(3)  # 等效 add = test(3)(add),带参装饰器
def add(x, y):
    """This is a function of addition

    The function of addition is..."""
    time.sleep(2)
    return x + y

add(10, 2)  # 调用函数
_______________________________________________________________________________________
>>>add program start
>>>name= add,
>>>doc=This is a function of addition
    
   The function of addition is...
>>>add program end had took 2.000189
>>>add OK,fast
  • 带参装饰器
    • 它是一个函数
    • 函数作为它的形参
    • 返回值是一个不带参的装饰器函数
    • 使用@functionname(参数列表)方式调用
    • 可以看做在装饰器外层又加了一层函数,这个函数可以多参数

带参装饰器输出目标记录

import datetime
import time

def copy_propertie(src):
    def _copy(dest):  # 此处装饰器柯里化
        dest.__name__ = src.__name__
        dest.__doc__ = src.__doc__
        return dest
    return _copy

def test(flag, func=lambda name, dalta: print('{} took {:.2f}s'.format(name, dalta))):  # 此处设置,当函数达到要求记录时,则输出记录
    def logger(fn):
        @copy_propertie(fn)  # 等效wrapper = copy_propertie(fn)(wrapper),此处用copy_propertie(fn)函数来装饰wrapper函数,装饰器里套装饰器,带参装饰器
        def wrapper(* args, ** kwargs):
            print("{} program start".format(fn.__name__))
            start = datetime.datetime.now()
            cc = fn(* args, ** kwargs)
            dalta = (datetime.datetime.now() - start).total_seconds()
            print("name= {},\ndoc={}".format(add.__name__, add.__doc__))
            #print("{} program end had took {}".format(fn.__name__,dalta))
            #print("{} to slow".format(fn.__name__) if dalta> flag else "{} OK,fast".format(fn.__name__) )
            if dalta > flag:  # 此处设置条件,当达到条件,则输出
                func(fn.__name__, dalta)
            return cc
        return wrapper
    return logger

@test(3)  # add = test(3)(add)
def add(x, y):
    """This is a function of addition

    The function of addition is..."""
    time.sleep(3)
    return x + y

add(10, 2)
____________________________________________________________________________

>>>add program start
>>>name= add,
>>>doc=This is a function of addition
    
            The function of addition is...
>>>add took 3.00s
用模块,内建函数进行装饰
import datetime
import time
from functools import update_wrapper,wraps   #调用模块,用内建函数进行装饰

# def copy_propertie(src):
#     def _copy(dest):  # 此处装饰器柯里化
#         dest.__name__ = src.__name__
#         dest.__doc__ = src.__doc__
#         return dest
#     return _copy
#

def test(flag, func=lambda name, dalta: print('{} took {:.2f}s'.format(name, dalta))):  # 此处设置,当函数达到要求记录时,则输出记录
    def logger(fn):
         @function.wraps(fn)  # 等效wrapper = copy_propertie(fn)(wrapper),此处用copy_propertie(fn)函数来装饰wrapper函数,装饰器里套装饰器,带参装饰器
        def wrapper(* args, ** kwargs):
            print("{} program start".format(fn.__name__))
            start = datetime.datetime.now()
            cc = fn(* args, **kwargs)
            dalta = (datetime.datetime.now() - start).total_seconds()
            print("name= {},\ndoc={}".format(add.__name__, add.__doc__))
            #print("{} program end had took {}".format(fn.__name__,dalta))
            #print("{} to slow".format(fn.__name__) if dalta> flag else "{} OK,fast".format(fn.__name__) )
            if dalta > flag:  # 此处设置条件,当达到条件,则输出
                func(fn.__name__, dalta)
            return cc
       #update_wrapper(wrapper, fn)   与@wraper(fn)作用相同
        return wrapper
    return logger


@test(3)  # add = test(3)(add)
def add(x, y):
    """This is a function of addition

    The function of addition is..."""
    time.sleep(3)
    return x + y

add(10, 2)

functools 模块

  • functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES) 等效 @functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS,updated=WRAPPER_UPDATES)

    • 类似copy_properties功能
    • wrapper 包装函数、被更新者,wrapped 被包装函数、数据源
    • 元组WRAPPER_ASSIGNMENTS中是要被覆盖的属性’module’, ‘name’, ‘qualname’, ‘doc’, ‘annotations
      模块名、名称、限定名、文档、参数注解
    • 元组WRAPPER_UPDATES中是要被更新的属性,__dict__属性字典
    • 增加一个__wrapped__属性,保留着wrapped函数
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值