zoukankan      html  css  js  c++  java
  • 装饰器

    装饰器

    一、装饰器的引入

    有一个需求,一个加法函数,想要增强它的功能,能够输出被调用过以及调用的参数信息,这种需求应该如何解决呢?

    1 # 加法函数 add 的定义
    2 def add(x, y):
    3     return x+y
    4 # 增强它的功能,输出被调用过以及调用的参数信息
    5 def add(x, y):
    6     print("call add, x = {}, y = {}".format(x, y))
    7     return x+y

    思考:功能是增强了,但是这种直接在原来只计算值的函数中增加属于非业务功能的代码,这种方式好吗?肯定是不好的,缺点如下:

    1. 首先打印语句的耦合性太高了

    2. 加法函数属于业务的逻辑,输出信息属于非业务功能的代码,不应该直接放在该函数中。

    代码改进,重新定义一个函数,在这个函数中来做输出信息的功能:

     1 def add(x, y):
     2     return x+y
     3  4 def message_show(fn):
     5     print("begin...call add")
     6     result = fn(4, 5)
     7     print("end...")
     8     return result
     9 10 print(message_show(add))

    思考:这种方式做到了业务功能的分离,但是 add 函数的传参是一个问题,这种方式很不灵活,继续改进。

    代码改进,解决参数的问题:

     1 def add(x, y):
     2     return x+y
     3  4 def message_show(fn, *args):
     5     print(*args)
     6     print("begin...call add")
     7     result = fn(*args)
     8     print("end...{} + {}".format(*args))
     9     return result
    10 11 print(message_show(add, 4, 5))

    至此,成功做到了新增业务功能的分离,可以看到并没有在原来的 add 函数中加入任何的代码,从而增强了它显示信息的功能。

    二、装饰器的改造

    (一)柯里化

    要写出装饰器,首先要对函数进行柯里化。柯里化的过程如下代码:

     1 def add(x, y):
     2     return x+y
     3  4 def message_show(fn):
     5     def wrapper(*args):
     6         print(*args)
     7         print("begin...call add")
     8         result = fn(*args)
     9         print("end...{} + {}".format(*args))
    10         return result
    11     return wrapper
    12 13 print(message_show(add)(4, 5))

    思考:message_show(add)(4, 5)这种调用方式能不能改造成add(4, 5),这种调用方法是不是就看不出调用过 message_show 函数了,而是直接调用的 add 函数,继续改造。

    (二)装饰器定义

    代码改造,柯里化之后,就可以使用装饰器的语法糖:

     1 def message_show(fn):
     2     def wrapper(*args):
     3         print(*args)
     4         print("begin...call add")
     5         result = fn(*args)
     6         print("end...{} + {}".format(*args))
     7         return result
     8     return wrapper
     9 10 @message_show  # 等效于 add = message_show(add)
    11 def add(x, y):
    12     return x+y
    13 14 print(add(4, 5))

    注意:装饰函数的代码,必须要定义在被装饰函数的代码之前。

    至此,一个简单的装饰器就出来了。

    三、无参装饰器

    (一)无参装饰器的理解

    • 装饰器本身就是一个函数,而且是一个高阶函数

    • 它的形参是一个函数,ps:要被装饰的函数

    • 它的返回值是一个函数

    • 可以使用 @function_name 的方式调用

    (二)装饰器的例子

     1 def fn_strengthen(fn):
     2     def wrapper(*args, **kwargs):
     3         print("========start=======")
     4         print("args = {}, kwargs = {}".format(args, kwargs))
     5         result = fn(*args, **kwargs)
     6         print(result)
     7         print("========end=========")
     8         return result
     9     return wrapper
    10 11 @fn_strengthen  # add = fn_strengthen(add)
    12 def add(x, y):
    13     print("==========call add=============")
    14     return x + y
    15 16 add(4, y=5)

    (三)装饰器的副作用

     1 def fn_strengthen(fn):
     2     def wrapper(*args, **kwargs):
     3         print("========start=======")
     4         print("args = {}, kwargs = {}".format(args, kwargs))
     5         result = fn(*args, **kwargs)
     6         print(result)
     7  8         print("========end=========")
     9         return result
    10     return wrapper
    11 12 @fn_strengthen  # add = fn_strengthen(add)
    13 def add(x, y):
    14     """This is a function to add"""
    15     print("==========call add=============")
    16     return x + y
    17 18 print("name = {}, doc = {}".format(add.__name__, add.__doc__))  # name = wrapper, doc = None

    注意:可以看出被装饰函数的属性都被替换了,这种问题应该如何解决?

    代码改进,再定义一个函数,用来记录被装饰函数的属性:

     1 def fn_strengthen(fn):
     2     def wrapper(*args, **kwargs):
     3         print("========start=======")
     4         print("args = {}, kwargs = {}".format(args, kwargs))
     5         result = fn(*args, **kwargs)
     6         print(result)
     7  8         print("========end=========")
     9         return result
    10     add_property(fn, wrapper)
    11     return wrapper
    12 13 def add_property(fn, wrapper):
    14     wrapper.__name__ = fn.__name__
    15     wrapper.__doc__ = fn.__doc__
    16 17 @fn_strengthen  # add = fn_strengthen(add)
    18 def add(x, y):
    19     """This is a function to add"""
    20     print("==========call add=============")
    21     return x + y
    22 23 print("name = {}, doc = {}".format(add.__name__, add.__doc__))

    上面的方法解决了属性被覆盖的问题,考虑到add_property这个函数的通用性,可以将这个复制属性的函数也改造成装饰器函数,只不过是带参的装饰器,代码改造如下:

     1 def add_property(fn):
     2     def _copy(wrapper):
     3         wrapper.__name__ = fn.__name__
     4         wrapper.__doc__ = fn.__doc__
     5         return wrapper
     6     return _copy
     7  8 def fn_strengthen(fn):
     9     @add_property(fn)  # wrapper = add_property(fn)(wrapper)
    10     def wrapper(*args, **kwargs):
    11         print("========start=======")
    12         print("args = {}, kwargs = {}".format(args, kwargs))
    13         result = fn(*args, **kwargs)
    14         print(result)
    15 16         print("========end=========")
    17         return result
    18     return wrapper
    19 20 @fn_strengthen  # add = fn_strengthen(add)
    21 def add(x, y):
    22     """This is a function to add"""
    23     print("==========call add=============")
    24     return x + y
    25 26 print("name = {}, doc = {}".format(add.__name__, add.__doc__))

    四、带参装饰器

    (一)带参装饰器的引入

    有一个需求,获取函数的执行时长,对时长超过阈值的函数记录一下:

     1 import datetime, time
     2  3 def add_property(fn):
     4     def _copy(wrapper):
     5         wrapper.__name__ = fn.__name__
     6         wrapper.__doc__ = fn.__doc__
     7         return wrapper
     8     return _copy
     9 10 def time_detection(timeout):
    11     def fn_strengthen(fn):
    12         @add_property(fn)  # wrapper = add_property(fn)(wrapper)
    13         def wrapper(*args, **kwargs):
    14             print("========start=======")
    15             start = datetime.datetime.now()
    16             result = fn(*args, **kwargs)
    17             print(result)
    18             delta = (datetime.datetime.now() - start).total_seconds()
    19             print("so slow") if delta > timeout else print("so fast")
    20             print("========end=========")
    21             return result
    22         return wrapper
    23     return fn_strengthen
    24 25 @time_detection(5)  # add = time_detection(5)(add)
    26 def add(x, y):
    27     """This is a function to add"""
    28     print("==========call add=============")
    29     time.sleep(6)
    30     return x + y
    31 32 add(4, 5)

    (二)带参装饰器的理解

    • 带参装饰器本身是一个函数,而且是一个高阶函数

    • 它的形参可以是一个函数,返回值是一个不带参数的装饰器函数

    • 使用 @function_name(参数) 方式调用

    • 可以看做是在装饰器的外层又加了一层函数

    (三)带参装饰器的例子

    将上一个例子中的记录时长的功能提取出来,能不能定义成一个参数来灵活的控制输出,代码改造如下:

     1 import datetime, time
     2  3 def add_property(fn):
     4     def _copy(wrapper):
     5         wrapper.__name__ = fn.__name__
     6         wrapper.__doc__ = fn.__doc__
     7         return wrapper
     8     return _copy
     9 10 def time_detection(timeout, func=lambda name, delta:print("{} spend {}s".format(name, delta))):
    11     def fn_strengthen(fn):
    12         @add_property(fn)  # wrapper = add_property(fn)(wrapper)
    13         def wrapper(*args, **kwargs):
    14             print("========start=======")
    15             start = datetime.datetime.now()
    16             result = fn(*args, **kwargs)
    17             print(result)
    18             delta = (datetime.datetime.now() - start).total_seconds()
    19             if delta > timeout:
    20                 func(fn.__name__, delta)
    21             print("========end=========")
    22             return result
    23         return wrapper
    24     return fn_strengthen
    25 26 @time_detection(5)  # add = time_detection(5)(add)
    27 def add(x, y):
    28     """This is a function to add"""
    29     print("==========call add=============")
    30     time.sleep(6)
    31     return x + y
    32 33 add(4, 5)

    五、functools模块

    这个模块就省了自己重新写复制属性的那个函数,functools.update_wrapper(包装函数, 原函数)使用这个模块来改造前面获取原函数属性的函数:

     1 import datetime, time, functools
     2  3 def time_detection(timeout, func=lambda name, delta:print("{} spend {}s".format(name, delta))):
     4     def fn_strengthen(fn):
     5         def wrapper(*args, **kwargs):
     6             print("========start=======")
     7             start = datetime.datetime.now()
     8             result = fn(*args, **kwargs)
     9             print(result)
    10             delta = (datetime.datetime.now() - start).total_seconds()
    11             if delta > timeout:
    12                 func(fn.__name__, delta)
    13             print("========end=========")
    14             return result
    15         return functools.update_wrapper(wrapper, fn)
    16     return fn_strengthen
    17 18 @time_detection(5)  # add = time_detection(5)(add)
    19 def add(x, y):
    20     """This is a function to add"""
    21     print("==========call add=============")
    22     time.sleep(6)
    23     return x + y
    24 25 add(5, 6), add.__name__, add.__doc__

    还可以使用这个模块的装饰器的方法来复制属性,@functools.wraps(原函数)

     1 import datetime, time, functools
     2 
     3 def time_detection(timeout, func=lambda name, delta:print("{} spend {}s".format(name, delta))):
     4     def fn_strengthen(fn):
     5         @functools.wraps(fn)
     6         def wrapper(*args, **kwargs):
     7             print("========start=======")
     8             start = datetime.datetime.now()
     9             result = fn(*args, **kwargs)
    10             print(result)
    11             delta = (datetime.datetime.now() - start).total_seconds()
    12             if delta > timeout:
    13                 func(fn.__name__, delta)
    14             print("========end=========")
    15             return result
    16         return wrapper
    17     return fn_strengthen
    18 
    19 @time_detection(5)  # add = time_detection(5)(add)
    20 def add(x, y):
    21     """This is a function to add"""
    22     print("==========call add=============")
    23     time.sleep(6)
    24     return x + y
    25 
    26 print(add(5, 6), add.__name__, add.__doc__)
  • 相关阅读:
    gridview 数据绑定函数
    GridView详述
    gridview 数据绑定函数
    GridView详述
    c# windows服务
    "System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35”或它的某一个依赖项。系统找不到指定的文件。
    启动网站时,IIS发生意外错误0x8ffe2740
    访问 IIS 元数据库失败
    vs2010运行时提示:无法启动程序:http://......
    realplayer、wmp 网页内嵌代码
  • 原文地址:https://www.cnblogs.com/Sweltering/p/9655085.html
Copyright © 2011-2022 走看看