社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

一详解Python装饰器,真香!

Python极客专栏 • 2 年前 • 202 次点击  
点击关注公众号,Python干货及时送达


今天写篇文章,详解 Python 装饰器。文末有我的微信二维码,打算建个读者群,想交流 Python 的朋友可以加一下。
装饰器(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/Django开发社区
本文地址:http://www.python88.com/topic/123944
 
202 次点击