【Python装饰器极简入门】

我们刚接触Python装饰器时,有点不容易理解,我们从面向对象的角度一起来学习装饰器。要了解装饰器,先要理解闭包。

一、闭包

闭包,又称闭包函数或者闭合函数,闭包中外部函数返回的不是一个具体的值,而是一个函数。一般情况下,返回的函数会赋值给一个变量,这个变量可以在后面被继续执行调用。

使用闭包是为了更方便的复用函数,在函数式编程里面应用特别广泛,所以在javasript、scala、Python等语言里面经常看到闭包的身影。

先来看一个需求:

如何从外部读取函数内部的局部变量?

我们有时候需要获取到函数内部的局部变量, 但是正常情况下,这是办不到的!只有通过变通的方法才能实现。

那就是在函数内部,再定义一个函数。

例1:

def f1():             # 这是外部函数
    n=1
    def f2():         # 这是内部函数
        print(n+1)
    return(f2) 
    
var= f1()             #将内部函数返回,供外部调用
var()

#输出
 2

在上文的代码中,函数 f2 就被包括在函数 f1 内部,这时 f1 内部的所有局部变量,对 f2 都是可见的。但是反过来就不行,f2 内部的局部变量,对 f1 就是不可见的。

这就是 "链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

既然 f2 可以读取 f1 中的局部变量,那么只要把 f2 作为返回值,我们不就可以在 f1 函数外部读取它的内部变量F2了吗!

再来看一个例子

例2:

def func1():   # 这是外部函数
    a = 1
    print("This is func1")
    def func2(num):  # 这是闭包函数
        print("This is func2")
        print(num+a)
    return func2

var = func1() #此时var= func2,func1执行完毕后,func2函数依然保存在内存空间中。
var(1)
#输出
This is func1
This is func2
2

首先定义fun1函数,在fun1函数内部再定义fun2函数,将func1函数赋值给var变量,此时func1函数返回的就是func2函数,func2就是闭包函数,通过闭包函数实现了对函数内部变量的访问。

变量作用域

要理解闭包,首先要理解变量作用域。

变量的作用域无非就两种:全局变量局部变量

定义一个变量,Python语言自动为其分配内存空间。

全局变量一直存在内存空间中;局部变量是有生命周期的,随着函数执行完毕分配的内存空间自动被回收。

在函数内部可以直接读取全局变量,但是在函数外部无法读取函数内部的局部变量。

【Python装饰器极简入门】_第1张图片

闭包的概念

上文代码中的 f2 函数,就是闭包。

用通俗的话理解: 闭包就是能够读取其他函数内部变量的函数,就是把闭包函数的作用域保存了下来

由于只有函数内部的子函数才能读取局部变量,所以说,闭包可以简单理解成"定义在一个函数内部的函数"。

在本质上,闭包是将函数内部和函数外部连接起来的桥梁

闭包的用途

闭包可以用在许多地方。它的最大用处有两个,① 可以读取函数内部的变量,②让这些变量的值始终保持在内存中,不会在 f1 函数调用后被自动清除。

为什么会这样呢?原因就在于 f1函数 是 f2 的父函数,而 f2 函数被赋给了一个全局变量,这导致 f2函数 始终在内存中,而 f2 函数的存在依赖于 f1函数,因此 f1函数 也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

二、装饰器

装饰器简介

装饰器(decorator)就是在不修改被装饰器对象源代码以及调用方式的前提下为被装饰对象添加新功能。

在Python中,我们有多种方法对函数和类进行加工,相对于其它方式,装饰器语法简单,代码可读性高。因此,装饰器在Python项目中有广泛的应用。修饰器经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理、Web权限校验、Cache等。

装饰器的优点是能够抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。即,可以将函数“修饰”为完全不同的行为,可以有效的将业务逻辑正交分解。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

装饰器本质上也是一种函数,它可以让其它函数在不经过修改的情况下增加额外的功能

「装饰」本身代表着一种功能,用它修饰不同的函数,为这些函数增加这种功能。

简单装饰器

一般而言,我们可以使用装饰器提供的 @ 语法糖(Syntactic Sugar)来修饰其它函数或对象。如下所示我们用 @dec 装饰器修饰函数 func ():

@dec
def func():
  pass

从一个例子入手,打印正在运行的程序名称。

def now():
   print('I am now')

现在增加一个新的需求,希望可以记录下函数的执行日志。在代码中添加日志代码:

def now():
    print('I am now')
    print("now is running")

如果其他函数也有类似的需求,怎么做?再写一个函数会造成大量雷同的代码。

为了高效使用代码,我们重新定义一个新的函数:专门打印日志 ,日志打印完之后再执行真正的业务代码。

def logging(func):
    print("%s is running" % func.__name__)
    func()

def now():
    print('I am now')

logging(now)
#输出 
now is running
I am now

这样做功能是实现了,但是我们调用的时候不再是调用真正的业务逻辑 now函数,而是换成了 logging 函数,这就破坏了原有now函数的代码结构, 现在我们每次都要把now 函数作为参数传递给 logging 函数。

要增强now()函数的功能,在函数调用前后自动打印日志,但又不希望修改now()函数的代码,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)

我们现在定义一个能打印日志的装饰器,代码如下:

def log(func):
    def wrapper(*args, **kw):
        print('%s is running' % func.__name__)
        return func(*args, **kw)
    return wrapper

上文的log是一个装饰器函数,它接受一个函数作为参数,并返回一个函数。它把执行真正业务逻辑的函数 func 作为参数传递进来,log装饰器返回的也是一个函数,这个函数的名字叫 wrapper。

wrapper()函数的参数定义是(*args, **kw),意思是wrapper()函数可以接受任意个数、任意位置的参数的调用。在wrapper()函数内,首先打印日志,再调用业务逻辑函数func。

@log
def now():
    print('I am now')
now()
#输出
now is running
I am now

@ 符号就是装饰器的语法糖,把@log放到now()函数的定义处,相当于执行了语句:

now = log(now)

这样就可以省略最后一步再次赋值的操作。

带参数的装饰器

如果装饰器decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数。比如,想要自定义装饰器log的文本:

def log(text):
    def decorator(func):
        def wrapper(*args, **kw):
            print('%s, %s is running' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

@log('Hello')
def now():
    print('I am now')
now()
#输出
Hello, now is running
I am now

上文的log是允许带参数的装饰器,是对原有装饰函数的封装,并返回一个装饰器我们可以将它理解为一个含有参数的闭包。当我们使用@log('Hello')调用的时候,Python 能够发现这一层的封装,并把参数传递到装饰器的环境中。@log('Hello')等价于@decorator。

类装饰器

装饰器不仅可以是函数,还可以是类,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器主要依靠类的__call__方法,当使用语法糖 @ 将装饰器附加到函数上时,就会调用类装饰器

我们定一个类装饰器Foo, 使用语法糖 @将装饰器附加到业务函数bar,然后调用业务函数bar,看看会发生什么。

class Foo(object):
    def __init__(self, func):                 # 将业务函数作为参数传入
        self._func = func                     # 类参数初始化
    def __call__(self):
        print ('class decorator runing')
        self._func()                          # 调用业务函数
        print ('class decorator ending')

#使用语法糖调用类装饰器
@Foo
def bar():
    print ('bar')

bar()
#输出
class decorator runing
bar
class decorator ending

装饰器的分享就到这里了,谢谢大家!

参考目录:

[1] (长文收藏) 如何理解 Python 装饰器?钱魏Way [Crossin的编程教室]

[2] 读懂系列 | 一文读懂Python装饰器!Pierre Ouannes [机器学习算法与Python学习]

[3] 廖雪峰Python教程

[4] 看完这篇文章你还不理解 Python 装饰器,只有一种可能...

你可能感兴趣的:(Python,python,算法)