前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python decorator的那些事

Python decorator的那些事

原创
作者头像
风之泪
修改2020-01-16 10:15:20
5460
修改2020-01-16 10:15:20
举报
文章被收录于专栏:巫山跬步

1.摘要

Python语言中有一个decorator的语法,中文翻译过来为装饰器。首次接触decorator不免让Python 的学习者理解起来有些困难。本文主要从Python 引入decorator的动机,作用,语法来源以及几个简单的使用例子方面介绍decorator这个概念。希望读完本文后能从根本上理解decorator的本质,在使用的时候能顺手拈来,而不是死记硬背它的语法和用法。

2.Python 引入decorator的动机

Python引入decorator之前函数转换方式(transforming functions and methods)比较难懂并且会使得代码难以理解,所以引入decorator主要是为了简化函数转换的方式。

首先我们看一下什么是函数转换:

例如:

代码语言:javascript
复制
def foo():
    print('foo function')

def print_func_run_time(func):
    def f():
        print('start at %s' % datetime.now())
        func()
        print('end at %s' % datetime.now())
    
    return f
foo = print_func_run_time(foo)

通过这样一段代码,我们就改变了程序第一行定义的foo函数的函数体内容,每次在调用foo函数的时候,在原来函数功能基础上,还会打印出函数运行的开始和结束时间。

如果print_func_run_time这个函数有很多行话,这么写就会让foo = print_func_run_time(foo)这一行离函数的定义很远,不容易阅读。所以Python为了解决这个问题,就引入了decorator这个语法。现在只要像下面这么写就可以了。

代码语言:javascript
复制
def print_func_run_time(func):
    def f():
        print('start at %s' % datetime.now())
        func()
        print('end at %s' % datetime.now())
    return f
    
@print_func_rume_time
def foo()
    print('foo funtion')

3.引入decorator的起源

在10th Python Conference DevDay 上, Python的作者Guido在他的keynote演讲中提到Python要支持decorator的语法,后来他说当时只是半开玩笑式地说decorator只是他提出Python几个扩展功能中的一个。

4.Python decorator与设计模式中的decorator

熟悉decorator设计模式的人,乍一看可能会以为Python中的decorator与设计模式Gang of Four中的decorator是一样的,但其实并不一致。这也是Python decorator让人迷惑不解的一个主要原因。decorator这个名字起的并不好,很多人都抱怨过这个名字。

5.decorator的语法

代码语言:javascript
复制
@dec2
@dec1
def func(arg1, arg2, ...):
    pass

根据前面的函数转换,这几行代码等价于下面的代码:

代码语言:javascript
复制
def func(arg1, arg2, ...):
    pass
func = dec2(dec1(func))

5.1多个decorator的顺序问题

多个decorator的执行顺序是自底向上,但是很多人都搞不清楚这个顺序。如果看了上面那个例子,这个问题很容易理解。另外在我们可以从数学上来理解这个问题,我们初高中时都学过函数传递,如:y = g(f(x)),我相信学过初中数学的人都能理解这个方程的含义:先算f(x)的结果r,把r作为g函数的参数,最后算出y。这么一类比,上例中的多个decorator其实就是这样写的,func = dec2(dec1(func))。所以我们就知道要先执行dec1,再执行dec2。

5.2 decorator函数的定义

为什么decorator函数要以一个函数作为参数,而且内部还要定义一个函数,最后还要返回一个函数?在我们理解decorator的本质后,这个问题就迎刃而解了。我们还以print_func_run_time为例。

代码语言:javascript
复制
def print_func_run_time(func):
    def f():
        print('start at %s' % datetime.now())
        func()
        print('end at %s' % datetime.now())
    return f
    
@print_func_rume_time
def foo()
    print('foo funtion')

这段代码等价于:

代码语言:javascript
复制
foo = print_func_run_time(foo)

首先,foo是一个函数,foo作为入参传递给print_func_run_time,而后者又返回一个新的函数赋值给foo。从上面这行等价的代码倒推,我们也就理解了为什么入参是一个函数,返回值也是一个函数这两个问题。

那为什么print_func_run_time中还要定义一个函数呢?由于函数入参是局部变量,如果不在print_func_run_time中定义f函数,那么就无法使用入参这个局部变量。其中定义的f函数一定要调用foo函数,才能完成原来foo函数最初实现的功能,然后在f函数中再增加一些额外的功能,最后再返回。实际上decorator函数中定义的内部函数更是一个wrapper函数。

6.decorator为什么是现在这种语法形式?

在最初Python要引入decorator这个语法时候,社区曾经有好几种decorator的语法定义形式。

例如:

decorator与函数定义放到一行:

代码语言:javascript
复制
 def @classmethod foo(arg1,arg2):
     pass
     
 def @accepts(int,int),@returns(float) bar(low,high):
     pass
 
 def foo @classmethod (arg1,arg2):
     pass
 
 def bar @accepts(int,int),@returns(float) (low,high):
     pass

decorator放到函数定义的下面

代码语言:javascript
复制
 def foo(arg1,arg2):
     @classmethod
     pass

 def bar(low,high):
     @accepts(int,int)
     @returns(float)
     pass

以及用decorate这个关键字来定义

代码语言:javascript
复制
decorate:
 classmethod
 def foo(arg1,arg2):
     pass

 decorate:
     accepts(int,int)
     returns(float)
     def bar(low,high):
         pass 

那么最后为什么采用了现在这种语法形式呢?

因为Python的作者Guido更喜欢这种做法,所以决定采用这种将@decorator放到def 前面的做法。同时如果一个函数有很多行decorator,那么当你读到函数代码的时候还可能会有一种将decorator‘隐藏’起来的感觉。

7.例子

打印函数的名字:

代码语言:javascript
复制
def print_func_name(func):
        def f(*args, **kwargs):
            print('call func %s' % func.__name__)
            return func(*args, **kwargs)

        return f

    @print_func_name
    def add(a, b):
        return a + b

    @print_func_name
    def sub(a, b):
        return a - b

    print(add(1, 2))
    print(sub(1, 2)) 

输出结果:

代码语言:javascript
复制
call func add
3
call func sub
-1

总结

Python decorator本质就是函数转换(transforming functions and method,为了理解它我们可以对比数学上函数传递y = g(f(x))的概念。同时我们也可以看到很多知识也有其不合理和容易混淆地方以及为什么是这种形式,这很可能就是他的设计者当时的第一感觉,这也是很有意思的地方。

参考文献

PEP 318 – Decorators for Functions and Methods https://www.python.org/dev/peps/pep-0318/

Design Patterns https://en.wikipedia.org/wiki/Design_Patterns

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.摘要
  • 2.Python 引入decorator的动机
  • 3.引入decorator的起源
  • 4.Python decorator与设计模式中的decorator
  • 5.decorator的语法
    • 5.1多个decorator的顺序问题
      • 5.2 decorator函数的定义
      • 6.decorator为什么是现在这种语法形式?
      • 7.例子
      • 总结
      • 参考文献
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档