python--设计模式--13--结构型--装饰器模式

使用场景:给某个已有的功能函数或功能类增加功能,复用已有的功能函数或功能类。
原理:使用一个闭包函数或重新定义__init__()、__call__()方法的类作为装饰器。

一、装饰器入门

1、单装饰器
def extra_func1():
    print("I am extra_func1")


def fun_out1(func):
    def func_in():
        extra_func1()
        func()
        return None
    return func_in


@fun_out1
def foo():
    print("I am foo")
    return None


if __name__ == '__main__':
    foo()


"""
执行结果:
I am extra_func1
I am foo

Process finished with exit code 0
"""
2、多装饰器
def extra_func1():
    print("I am extra_func1")


def fun_out1(func):
    def func_in():
        extra_func1()
        func()
        return None
    return func_in


def extra_func2():
    print("I am extra_func2")


def fun_out2(func):
    def func_in():
        extra_func2()
        func()
        return None
    return func_in


@fun_out1
@fun_out2
def foo():
    print("I am foo")
    return None


if __name__ == '__main__':
    foo()


"""
执行结果:
I am extra_func1
I am extra_func2
I am foo

Process finished with exit code 0
"""
3、通用装饰器
import time


def write_log(func):
    print(f"访问方法:", func.__name__, "\t时间:", time.asctime())


def func_out(func):
    def func_in(*args, **kwargs):  # *args、**kwargs此处作文形参
        write_log(func)
        return func(*args, **kwargs)  # *args、**kwargs此处作为实参

    return func_in

"""
@func_out 解析:
相当于 add1 = func_out(add1), add1指向转变为闭包的内部函数。这也解释了:
(1)为什么加上该语句后add1的一些属性会改变为闭包的内部函数属性,如:add1.__name__、add1.__doc__,往往需要在闭包内部函数上加上@warp装饰器;
(2)为什么闭包外部函数只会执行一次;
(3)后期调用add1实际上调用的闭包内部函数
"""
@func_out  
def add1(a, b):
    return a + b


@func_out
def add2(a, b, c):
    return a + b + c


if __name__ == '__main__':	
    print("两个数的和为:", add1(1, 2))
    print("三个数的和为:", add2(1, 2, 3))


"""
执行结果:
访问方法: add1 	时间: Sat Jun 20 11:11:15 2020
两个数的和为: 3
访问方法们: add2 	时间: Sat Jun 20 11:11:15 2020
三个数的和为: 6

Process finished with exit code 0
"""

二、四类形式不同的装饰器

1、函数装饰函数
"""
Python装饰器(decorator)在实现的时候,被装饰后的函数其实已经是另外一个函数了
(函数名等函数属性会发生改变),为了不影响,Python的functools包中提供了一个叫
wraps的decorator来消除这样的副作用。写一个decorator的时候,最好在实现之前加
上functools的wrap,它能保留原有函数的名称和docstring。
"""
import time
from functools import wraps


def write_log(func):
    print("访问方法:", func.__name__, "\t时间:", time.asctime())


def func_out(func):
	"""
	功能:在add(a, b)执行前后添加额外功能。
	"""
    @wraps(func)
    def func_in(a, b):
        write_log(func)
        return func(a, b)

    return func_in

"""
@func_out 解析:
相当于 add = func_out(add), add指向转变为闭包的内部函数。这也解释了:
(1)为什么加上该语句后add的一些属性会改变为闭包的内部函数属性,如:add.__name__、add.__doc__,往往需要在闭包内部函数上加上@warp装饰器;
(2)为什么闭包外部函数只会执行一次;
(3)后期调用add实际上调用的闭包内部函数
"""
@func_out  
def add(a, b):
    return a + b


if __name__ == '__main__':
    print("两个数的和为:", add(1, 2))
    print(add.__name__)


"""
运行结果:
访问方法: add 	时间: Sat Jun 20 11:43:28 2020
两个数的和为: 3
add

Process finished with exit code 0
"""
2、函数装饰类
import time
from functools import wraps


def write_log(cls):
    print("准备创建的实例对象所属类:", cls.__name__, "\t创建时间:", time.asctime())


def func_out(cls):
    """
    功能:在实例对象创建前或后添加功能
    """
    @wraps(cls)
    def func_in(*args, **kwargs):  # *args, **kwargs ,此处作为形参,接收Add(1, 2)的参数,此处没有参数
        write_log(cls)
        return cls(*args, **kwargs)  # *args, **kwargs ,此处作为实参,传递给Add类中的__new__和__init__方法的参数
    return func_in


"""
@func_out 解析:
相当于 Add = func_out(Add), add1指向转变为闭包的内部函数。这也解释了:
(1)为什么加上该语句后Add的一些属性会改变为闭包的内部函数属性,如:Add.__name__、Add.__doc__,往往需要在闭包内部函数上加上@warp装饰器;
(2)为什么闭包外部函数只会执行一次;
(3)后期调用Add实际上调用的闭包内部函数
"""
@func_out
class Add(object):
    def __init__(self):
        pass

    def add(self, a, b):
        return a + b


if __name__ == '__main__':
    add = Add()
    print("两个数的和为:", add.add(1, 2))

"""
运行结果:
准备创建的实例对象所属类: Add 	创建时间: Sat Jun 20 12:30:08 2020
两个数的和为: 3

Process finished with exit code 0
"""

该方法可用于实现单例模式。

3、类装饰函数
import time


def write_log(func):
    print(f"访问方法:", func.__name__, "\t时间:", time.asctime())


class Decorator(object):
    """
    功能:在add(a, b)执行前后添加额外功能。
    """
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):  # __call__(self, a, b) 也可以
        write_log(self.func)
        return self.func(*args, **kwargs)  # self.func(a,b)


"""
@Decorator 解析:
相当于 add = Decorator(add), add指向转变为所创建的Decorator可调用实例对象。这也解释了:
(1)为什么加上该语句后add的一些属性会改变为所创建的Decorator可调用实例对象属性,如:出现了print(add.__class__)为Decorator;
(2)为什么Decorator.__init__只会执行一次;
(3)后期调用add实际上调用的该add实例所绑定的__call__方法。
"""
@Decorator  
def add(a, b):
    return a + b


if __name__ == '__main__':
    print(type(add))
    print("两个数的和为:", add(1, 2))

"""
运行结果:
<class '__main__.Decorator'>
访问方法: add 	时间: Wed Jul  8 12:55:31 2020
两个数的和为: 3

Process finished with exit code 0
"""
4、类装饰类
import time


def write_log(cls):
    print("准备创建的实例对象所属类:", cls.__name__, "\t创建时间:", time.asctime())


class Decorator(object):
    """
       功能:在实例对象创建前或后添加功能
    """
    def __init__(self, cls):
        self.cls = cls

    def __call__(self, *args, **kwargs):  # *args, **kwargs ,此处作为形参,接收Add(1, 2)的参数,此处没有参数
        write_log(self.cls)
        return self.cls(*args, **kwargs)  # *args, **kwargs ,此处作为实参,传递给Add类中的__new__和__init__方法的参数


"""
@Decorator 解析:
相当于 Add = Decorator(Add), Add指向转变为所创建的Decorator可调用实例对象。这也解释了:
(1)为什么加上该语句后Add的一些属性会改变为所创建的Decorator可调用实例对象属性,如:出现了print(Add.__class__)为Decorator;
(2)为什么Decorator.__init__只会执行一次;
(3)后期调用Add实际上调用的该Add实例所绑定的__call__方法。
"""
@Decorator
class Add(object):
    def __init__(self):
        pass

    def add(self, a, b):
        return a + b


if __name__ == '__main__':
    add = Add()
    print("两个数的和为:", add.add(1, 2))


"""
运行结果:
准备创建的实例对象所属类: Add 	创建时间: Sat Jun 20 17:02:59 2020
两个数的和为: 3

Process finished with exit code 0
"""

该方法可用于实现单例模式。

5、总结

(1)解释器执行时的传参过程

装饰函数装饰类
功能函数功能函数函数名→装饰函数的外函数参数,功能函数参数→装饰函数的内函数参数。例如:1功能函数函数名→装饰类__init__(self)的参数,功能函数参数→装饰类__call(self)__的参数。例如:2
功能类功能类类名→装饰函数的外函数参数,功能类参数→装饰函数的内函数参数。例如:3功能类类名→装饰类的__init__(self)的参数,功能类参数→装饰类__call(self)__的参数。例如:4

(2)装饰函数、装饰类的功能
装饰函数、装饰类可以在功能函数执行前后增加额外功能。
装饰函数、装饰类可以在功能类的实例对象产生前后增加额外功能。

(3)原理
装饰函数的原理是闭包函数。
装饰类:重新定义__init__()、__call__()方法的类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值