Python装饰器为何难理解?

不管项目中仍是面试都离不开装饰器话题,装饰器的强大在于它可以在不修改原有业务逻辑的状况下对代码进行扩展,权限校验、用户认证、日志记录、性能测试、事务处理、缓存等都是装饰器的绝佳应用场景,它可以最大程度地对代码进行复用。html

但为何初学者对装饰器的理解如此困难,我认为本质上是对Python函数理解不到位,由于装饰器本质上仍是函数python

函数定义

理解装饰器前,须要明白函数的工做原理,咱们先从一个最简单函数定义开始:面试

def foo(num):
    return num + 1复制代码

上面定义了一个函数,名字叫foo,也能够把 foo 可理解为变量名,该变量指向一个函数对象缓存

调用函数只须要给函数名加上括号并传递必要的参数(若是函数定义的时候有参数的话)闭包

value = foo(3)
print(value) # 4复制代码

变量名 foo 如今指向 <function foo at 0x1030060c8> 函数对象,但它也能够指向另一个函数。函数

def bar():
    print("bar")
foo = bar
foo() # bar复制代码

函数做为返回值

在Python中,一切皆为对象,函数也不例外,它能够像整数同样做为其它函数的返回值,例如:性能

def foo():
    return 1

def bar():
    return foo

print(bar()) # <function foo at 0x10a2f4140>

print(bar()()) # 1 
# 等价于
print(foo()) # 1复制代码

调用函数 bar() 的返回值是一个函数对象 ,由于返回值是函数,因此咱们能够继续对返回值进行调用(记住:调用函数就是在函数名后面加 ())调用 bar()()至关于调用 foo(),由于 变量 foo 指向的对象与 bar() 的返回值是同一个对象。 测试

函数做为参数

函数还能够像整数同样做为函数的参数,例如:spa

def foo(num):
    return num + 1

def bar(fun):
    return fun(3)

value = bar(foo)
print(value)  # 4复制代码

函数 bar 接收一个参数,这个参数是一个可被调用的函数对象,把函数 foo 传递到 bar 中去时,foo 和 fun 两个变量名指向的都是同一个函数对象,因此调用 fun(3) 至关于调用 foo(3)。.net

函数嵌套

函数不只能够做为参数和返回值,函数还能够定义在另外一个函数中,做为嵌套函数存在,例如:

def outer():
    x = 1
    def inner():
        print(x)
    inner()

outer() # 1复制代码

inner作为嵌套函数,它能够访问外部函数的变量,调用 outer 函数时,发生了3件事:

  1. 给 变量 x 赋值为1
  2. 定义嵌套函数 inner,此时并不会执行 inner 中的代码,由于该函数还没被调用,直到第3步
  3. 调用 inner 函数,执行 inner 中的代码逻辑。

闭包

再来看一个例子:

def outer(x):
    def inner():
        print(x)

    return inner
closure = outer(1)
closure() # 1复制代码

一样是嵌套函数,只是稍改动一下,把局部变量 x 做为参数了传递进来,嵌套函数再也不直接在函数里被调用,而是做为返回值返回,这里的 closure就是一个闭包,本质上它仍是函数,闭包是引用了自由变量(x)的函数(inner)。

装饰器

继续往下看:

def foo():
    print("foo")复制代码

上面这个函数这多是史上最简单的业务代码了,虽然没什么用,可是能说明问题就行。如今,有一个新的需求,须要在执行该函数时加上日志:

def foo():
    print("记录日志开始")
    print("foo")
    print("记录日志结束")复制代码

功能实现,惟一的问题就是它须要侵入到原来的代码里面,把日志逻辑加上去,若是还有好几十个这样的函数要加日志,也必须这样作,显然,这样的代码一点都不Pythonic。那么有没有可能在不修改业务代码的提早下,实现日志功能呢?答案就是装饰器。

def outer(func):
    def inner():
        print("记录日志开始")
        func() # 业务函数
        print("记录日志结束")
    return inner

def foo():
    print("foo")

foo = outer(foo) 
foo()复制代码

我没有修改 foo 函数里面的任何逻辑,只是给 foo 变量从新赋值了,指向了一个新的函数对象。最后调用 foo(),不只能打印日志,业务逻辑也执行完了。如今来分析一下它的执行流程。

这里的 outer 函数其实就是一个装饰器,装饰器是一个带有函数做为参数并返回一个新函数的闭包,本质上装饰器也是函数。outer 函数的返回值是 inner 函数,在 inner 函数中,除了执行日志操做,还有业务代码,该函数从新赋值给 foo 变量后,调用 foo() 就至关于调用 inner()

foo 从新赋值前:

从新赋值后,foo = outer(foo)

另外,Python为装饰器提供了语法糖 @,它用在函数的定义处:

@outer
def foo():
    print("foo")

foo()复制代码

这样就省去了手动给foo从新赋值的步骤。

到这里不知你对装饰器理解了没有?固然,装饰器还能够更加复杂,好比能够接受参数的装饰器,基于类的装饰器等等。下一篇能够写写装饰器的应用场景。

同步发表于:foofish.net/understand-…

公众号:python之禅
相关文章
相关标签/搜索