飞道的博客

Python装饰器入门教程

491人阅读  评论(0)

最近学Spring开发,但是由于我的Java水平巨差,还不了解Java设计模式中的装饰器模式,在这里拿Python去自我讲解一下装饰器到底是啥。

Python一切皆对象

我们平时总能听说一句话叫做Python一切皆对象,但是这句话到底啥意思?很少有人能真正理解。
有些公司的Python面试题会出现一道叫做是否了解过猴子补丁。这其实就是问的就是你对Python对象的认知。
我们首先看一段代码:

a = abs
num = a(-1)
print(num)

这段代码很短,仅仅有三行,但是有谁知道他的最终结果是什么嘛。答案是1。
我们首先将abs变量赋值给a变量。这里的abs其实是个Python内置函数绝对值,作用是将数字类型的参数取绝对值。
此时的a对象就变成了和abs函数一样的功能。


闭包

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。

使用Python代码讲解一下闭包的作用

def fun1():
    def fun2():
        return "这是内层函数"
    return fun2

fun2 = fun1()
print(fun2())

Python一切皆对象的特点在这里体现出来了。
我们首先定义一个fun1函数,在fun1函数中定义一个fun2函数,然后将fun2函数的对象值在fun1中返回回去,其次在外部定义一个fun2变量让他等于fun1()这个函数,返回值为fun2对象,之后调用了fun2()这个方法。就能将fun2中的函数返回值给打印出来了。

简单来说就是一个函数定义中引用了函数外定义的变量,并且该函数可以在其定义环境外被执行。这样的一个函数我们称之为闭包。实际上闭包可以看做一种更加广义的函数概念。因为其已经不再是传统意义上定义的函数。


装饰器

我们首先先出一道题吧:
我想在一个函数运行之前输出一个before,在函数运行之后输出一个after。
函数如下:

def fun():
    print("函数运行")
    return "运行完成"

大家的写法在不使用装饰器的方法为:

def fun():
    print("before")
    print("函数运行")
    print("after")
    return "运行完成"
fun()

这个方法的确能实现我的要求,但是我不会选择这么做。
我会使用一个闭包的方法实现这个功能。

def fun():
    print("函数运行")
    return "运行完成"
def fun1(fun):
    def fun2():
        print('before')
        return_fun = fun()
        print('after')
        return return_fun
    return fun2

fun = fun1(fun)
fun()

这个方法也能实现上述的功能。

那么大家觉得上述两种方法相比,谁会更显优势呢?
我觉得大家的方法会比我的方法更好一些,因为我的代码过于冗余。大家的方法清晰可见。
那么第一回合,大家胜出。


@方法的作用

上一回合的大家胜出导致我不服气。我便攒够了知识再去挑战大家。
还是第一回合的题目。在前后加before和after。
但是这回我会选择另一种方法与大家PK。
这次的代码我会选择这么写:

def fun1(fun):
    def fun2():
        print('before')
        return_fun = fun()
        print('after')
        return return_fun
    return fun2

@fun1
def fun():
    print("函数运行")
    return "运行完成"

fun()

我这次使用的代码和上回合相差并不大,首先还是定义了一个闭包操作为了封装两个单词的输出,但是我并不会选择上述代码的fun = fun1(fun),而是使用了Python装饰器自带的@符号
@符号的作用就相当于上述的代码。我们使用了fun1(fun)方法将fun函数传递到fun1函数中但是我们还是返回了一个fun变量去接受闭包给我传递的内层函数对象。而@负责取代我们这一行操作,在fun函数上方使用@后紧跟闭包函数,这样就可以将我们的fun函数传递给了fun1函数去做参数传递进去后返回了fun这个变量。
之后就可以直接使用fun()函数去调用了。

那么大家觉得上述两种方法相比,谁会更显优势呢?
我认为大家的代码仍然比我的代码更美观一些,这回合虽说我使用了一个更高级的语法,但是还是无法和大家的代码一样简洁。
那么第二回合,大家胜出。


装饰器的优势

经过上两个回合,我既然比不过大家,那我就改题。
我想在多个函数运行之前输出一个before,在函数运行之后输出一个after。

大家的解题思路如下:

def fun_1():
    print('before')
    print("函数运行1")
    print('after')
    return "运行完成"

def fun_2():
    print('before')
    print("函数运行2")
    print('after')
    return "运行完成"

def fun_3():
    print('before')
    print("函数运行3")
    print('after')
    return "运行完成"

def fun_4():
    print('before')
    print("函数运行4")
    print('after')
    return "运行完成"

fun_1()
fun_2()
fun_3()
fun_4()

我的方法如下:

def fun1(fun):
    def fun2():
        print('before')
        return_fun = fun()
        print('after')
        return return_fun
    return fun2

@fun1
def fun_1():
    print("函数运行1")
    return "运行完成"
@fun1
def fun_2():
    print("函数运行2")
    return "运行完成"
@fun1
def fun_3():
    print("函数运行3")
    return "运行完成"
@fun1
def fun_4():
    print("函数运行4")
    return "运行完成"

fun_1()
fun_2()
fun_3()
fun_4()

我的代码与大家的代码相比之下,我认为我的代码会更胜一筹,当函数的个数更多的时候,大家的代码会显得很重复。而我的代码仅仅使用@即可将功能封装实现。

总结一下

  • 函数数量较少的情况下使用大家的方法。
  • 函数数量较多的情况下使用装饰器方法。

装饰器内添加参数

我们以上的代码均为函数中无参数的结果,但我们平时的业务中都会含有一些函数内的参数要写,比如如下:

@fun1
def fun_1(arg1):
    print(arg1)
    return "运行完成"

我们将fun_1传递到装饰器中会发现装饰器中的fun函数并没有参数可以填写,所以说我们需要改造一下装饰器内部结构。

def fun1(fun):
    def fun2(*args, **kwargs):
        print('before')
        return_fun = fun(*args, **kwargs)
        print('after')
        return return_fun
    return fun2

我们使用Python自带的*args**kwargs去接受不定量的参数集合,这样就能保证我们的函数无论接受什么样的参数都可以使用我们的装饰器了。

这一段装饰器代码务必要记住,因为这是最基础的装饰器实现代码。



转载:https://blog.csdn.net/qq_45414559/article/details/115721688
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场