Python社区  »  Python

一详解Python装饰器,真香!

Python爱好者社区 • 2 周前 • 20 次点击  
装饰器(decorator)是一种高级Python语法,也是 Python 函数式编程的一部分。写法是 @xxx,在 Pandas、Django 框架源码里经常能见到,应用非常广泛。

虽然不用装饰器也不影响开发,但用了装饰器可以让你的代码“秀”起来,当然装饰器的作用不仅仅是“秀”,更重要的是它可以让你的代码更简洁,可读性更高;业务逻辑解耦,维护更容易。

1

 函数基础


学习装饰器之前,我们先学习下 Python 函数的两个特性,函数体内可以定义函数函数可以被引用。有基础的朋友可以略过,直接看下一小节。

例1. 函数体内定义函数

def func1():
    def func2():
        return "in func2"

    res = func2()

    return res


在 func1 函数体内, 定义了一个 func2 函数并调用, 然后返回调用的结果。
其实很多静态编程语言,如 C、Java,不支持在函数体内定义函数。但在 Python 中是支持的。
例2.函数可以被引用
new_func = func1
print(new_func())


func1 是个函数,定义跟 例1 是一样的。可以看到函数名可以像变量那样使用。赋值后,new_func 就引用了 func1,就可以直接通过 new_func() 方式来调用函数。
既然函数名可以向变量一样赋值,那自然就可以被 return 返回。
def func1():
    def func2():
        return "in func2"

    print(func2)
    return func2


new_func = func1()
print(new_func)
print(new_func())


输出:
.func2 at 0x102499b80>
.func2 at 0x102499b80>
in func2


print(func1) 和 print(new_func) 输出结果一样,说明他们是同一个函数。

2

 函数基础



有了上面的基础,再学习装饰器就很容易了。下面通过一个 “炒土豆丝” 的例子来学习装饰器。

例3. “炒土豆丝”基础版

def tu_dou_si():
    """
    "清炒土豆丝" 流程
    """

    print('清洗土豆')
    print('去皮,切丝')
    print('清炒')
    print('出锅')


这是最基础的版本。为了跟国际接轨, 例子中使用中文拼音定义函数名或变量名。

下面我们改造 例3,将其变成装饰器版本。

例4. “炒土豆丝”装饰器版

def my_decorator(func):
    def wrapper():
        func()
        print('清炒')
        print('出锅')

    return wrapper


def tu_dou_si():
    print('清洗土豆')
    print('去皮,切丝')


有没有发现它跟 例2 特别像。

my_decorator 函数就是一个装饰器,它参数 func 表示可以传入一个函数,它里面定义了一个函数 wrapper,然后将 wrapper 函数返回。

wrapper 的中文含义是包裹、包装的意思,所以在这里表示对 func() 进行了包装,包装的效果就是多了两个 print 输出。

tu_dou_si 函数,只保留跟土豆丝相关的 print 语句。

定义好了装饰器,就可以按照下面的方式,实现 例3 中“清炒土豆丝”的流程


qing_chao_tu_dou_si = my_decorator(tu_dou_si)
qing_chao_tu_dou_si()


输出:

清洗土豆
去皮,切丝
清炒
出锅


对于上面的代码 Python 有一种更简洁的方式来实现




    
@my_decorator
def tu_dou_si():
    print('清洗土豆')
    print('去皮,切丝')
tu_dou_si()


就是 Python 的装饰器。因为 tu_dou_si 函数已经被 my_decorator 装饰过了, 所以,直接调用 tu_dou_si 函数即可完成 “清炒土豆丝”的流程。

3

 装饰器的优势


虽然我们已经实现了一个装饰器,但你心里可能会有疑问,装饰器写法不简单,代码量比 例3 也不少,它的优势究竟在哪。
从“清炒土豆丝”这个单点确实看不出装饰器优势,如果从线再到面角度来看,就能看出装饰器的优势了。
“清炒土豆丝”流程可以拆分为两个业务,一个是准备食材业务,即: print('清洗土豆')print('去皮,切丝');另一个是制作食材的业务,即: print('清炒') 和 print('出锅')。
之所以这样拆,是因为这两部分是相互独立的子业务,并且能方便看到装饰器的优势。
问题1. 要再实现一个“清炒山药”的流程,怎么做
@my_decorator
def shan_yao():
    print('清洗山药')
    print('去皮,切丝')


可以看到, 用装饰器来实现可以复用 my_decorator 中制作食材的业务过程,增加代码复用性,使整体代码更简洁。
问题2. 想把“清炒土豆丝”改成“凉拌土豆丝”,怎么办
def my_decorator(func):
    def wrapper():

        func()
        print('凉拌')
        print('出锅')

    return wrapper


直接修改 my_decorator 业务就可以了,不会对其他业务产生副作用。
理解了上面两个问题,我们也就知道了装饰器的作用。如果不使用装饰器(如:例3 )要么会产生代码冗余,要么就业务耦合在一起改一个业务影响到另外的业务。

4

 装饰器的进阶


接下来,来看看装饰器高级一些的用法。
例5. 装饰器的参数



    
def cai_factory(cai_type='清炒'):
    def my_decorator(func):
        def wrapper():
            func()
            print(f'{cai_type}')
            print('出锅')

        return wrapper
    return my_decorator


@cai_factory('凉拌')
def tu_dou_si():
    print('清洗土豆')
    print('去皮,切丝')


定义了一个 cai_factory 函数(即:菜工厂),它里面包含了之前定义的 my_decorator 装饰器。cai_factory 函数接收 cai_type 参数用来指定如果制作食材。
定义 tu_dou_si 函数时,使用新的装饰器,并且传入参数即可。
例6. 被装饰函数的参数
def cai_factory(cai_type='清炒'):
    def my_decorator(func):
        def wrapper(cai_name):
            func(cai_name)
            print(f'{cai_type}')
            print('出锅')

        return wrapper
    return my_decorator


@cai_factory()
def cai(cai_name='土豆'):
    print(f'清洗{cai_name}')
    print('去皮,切丝')


定义一个用 cai_factory 修饰的函数 cai,接收 cai_name 参数来指定制作的食材。同时,在 cai_factory 装饰器中的 wrapper 函数中增加 cai_name 参数,并在调用 func 时,传入该参数。
例7. 被装饰函数的不定参数
def cai_factory(cai_type='清炒'):
    def my_decorator(func):
        def wrapper(*args, **kwargs):
            func(*args, **kwargs)
            print(f'{cai_type}')
            print('出锅')

        return wrapper
    return my_decorator


@cai_factory()
def cai(cai_name, cai_cnt):
    print(f'清洗{cai_name} {cai_cnt}')
    print('去皮,切丝')

cai('山药', '两个')


例6 只能支持1个参数,本例中的 wrapper 函数的参数改为 *args 和 **kwargs,就可以支持任意参数,把装饰器变得更通用。

5

 装饰类


类装饰器的用法跟函数是一样的。这次我们不做菜了,开始玩游戏了。游戏里面皮肤和英雄的关系就完美地表现了装饰类与装饰类的关系。
下面我们就定义一个英雄类,然后定义一个皮肤类装饰它。
def pi_fu(cls):
    class PiFuClass:
        def __init__(self, name, pi_fu_name):
            self.wrapped = cls(name, pi_fu_name)
            self.pi_fu_name = pi_fu_name

        def display(self):
            self.wrapped.display()
            print(f'展示皮肤{self.pi_fu_name}')

    return PiFuClass


@pi_fu
class YingXiong:
    def __init__(self, name, pi_fu_name):
        self.name = name
        self.pi_fu_name = pi_fu_name

    def display(self):
        print(f'展示英雄{self.name}')


ya_se = YingXiong('亚瑟', '死亡骑士')
ya_se.display()


写法和用法跟函数装饰器类似,这里就不再赘述了。

6

 内置装饰器


Python 内置了一些装饰器,这里列几个我们经常会用到的一些:
  • @property:修饰类方法,使得我们可以像访问属性一样来获取一个函数的返回值

  • @staticmethod:修饰类方法,表示该方法是一个静态方法,可以直接被调用无需实例化

  • @wraps:函数来进行装饰,可以让我们在装饰器里面访问在装饰之前的函数的属性

简单总结一下,Python 装饰器支持修饰方法和类,可能增加被修饰方法和方法的功能。同时又能起到业务之间解耦、增加代码复用性和简化代码的作用。
如果本文对你有用就点个 在看 鼓励一下吧。

重磅!Python交流已成立


公众号运营至今,离不开小伙伴们的支持。
为了给小伙伴们提供一个互相交流的技术平台,特地开通了Python交流群。
群里有不少技术大神,不时会分享一些技术要点,更有一些资源收藏爱好者不时分享一些优质的学习资料。(免费,不卖课!)
需要进群的朋友,可长按扫描下方二维码。


▲长按扫码

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/123053
 
20 次点击  
分享到微博