Python基础语法-05-装饰器

闭包

闭包是装饰器实现的底层部分,要想明白装饰器底层怎么实现,必须要搞懂闭包

闭包是什么?

将一个函数定义到一个函数内部,外函数的返回是内函数,这时这两个函数就构成了一个闭包

闭包举例:

def test(number):
    def test_in(number_in):
        print("in test_in 函数, number_in is %d" % number_in)
        return number + number_in
    #返回内函数tset_in的引用    
    return test_in

# test函数返回一个函数test_in,所以这里t变量保存的是一个函数的引用
t = test(20)
# 使用指向test_in函数的引用t调用test_in函数
print(t(100))
# 使用指向test_in函数的引用t调用test_in函数
print(t(200))

这里写图片描述

闭包的优点及其应用

闭包的使用可以增加代码复用性,减少内存空间的开辟,如下例子:

#程序一
def creatNum(a,b,x):
    return a*x + b

a=1
b=1
x=0
line1 = creatNum(a,b,x)
print(line1)                #line1是一个变量
x=1
line1 = creatNum(a,b,x)
print(line1)

a=4
b=5
x=0
line2 = creatNum(a,b,x)
print(line2)                #line2是一个变量
x=1
line2 = creatNum(a,b,x)
print(line2)
#程序二
def line_conf(a, b):
    def line(x):
        return a*x + b
    return line

line1 = line_conf(1, 1)     #line1是一个指向函数的引用
line2 = line_conf(4, 5)     #line2是一个指向函数的引用

print(line1(0))         
print(line1(1))

print(line2(0))
print(line2(1))

这里写图片描述

以上两段程序的运行结果相同,但是程序二开辟了更少的内存空间,因为程序一每次调用函数都需要定义函数需要使用的变量,而程序二只需要一句line1 = line_conf(1, 1) 创建一个函数并使用变量保存其引用,则每次只需通过这个函数引用即可使用函数,不需要多次创建函数需要的参数,因此闭包减少了内存的开辟

装饰器

Python中的装饰器的主要思想类似设计模式中的装饰器模式(Decorator Pattern),简单的说就是对原来的方法进行功能增强

装饰器的定义和使用

装饰器使用场景比较常见,比如常见的站点后台操作,执行操作前一般需要进行权限的检验,检查用户是否登录,是否有进行该操作的权限等。如下例子,执行f1方法的操作前必须进行check_login方法(是否登录)的权限检验

def check_login():
    # 验证1
    # 验证2
    # 验证3
    pass
def f1():
    check_login()
    print('f1')
def f2():
    check_login()
    print('f2')
def f3():
    check_login()
    print('f3')
def f4():
    check_login()
    print('f4')

但是面例子的设计方式有一个非常大的缺陷,就是需要在方法的内部修改代码。还是上述例子,如果需要加上其他的检验操作,如是否有操作权限,限制条件是否满足等,就需要在f1等方法内部再加上一个权限检验函数,如果这样的验证操作很多,那么f1等方法的代码就需要不断地修改

一个好的程序是要遵守开放封闭原则的,封闭原则是指上述f1等方法在其功能完成后不应该再修改其内部代码,开放原则是指已完成的功能要能够方便的进行功能扩展同时不违背封闭原则。这时需要一个比上述方式更好的解决方案

def w1(func):
    def inner():
        # 验证1
        # 验证2
        # 验证3
        func()
    return inner
@w1
def f1():
    print('f1')
@w1
def f2():
    print('f2')
@w1
def f3():
    print('f3')
@w1
def f4():
    print('f4')

以上就是Python装饰器的使用,装饰器就是闭包和@w1

装饰器的底层原理

def w1(func):
    def inner():
        # 验证1
        # 验证2
        # 验证3
        func()
    return inner
@w1
def f1():
    print('f1')

上述程序,@w1是一个Python可以识别的特殊的符号,Python解释器在遇到@w1时将其解释为w1(f1),即把f1的引用作为参数传递给函数w1,并执行。w1是一个闭包方法,inner方法在w1方法执行后加载进内存(但是不会执行),f1为w1方法的参数,和inner方法一起加载进内存。这时内存中会有一个经过装饰的inner方法:


def inner():
    # 验证1
    # 验证2
    # 验证3
    f1()

接着Python解析器会将进行一个赋值,将上述这个经过修饰的inner方法的引用赋值给函数指针f1,即:

f1 = inner;

#inner函数为:
def inner():
    # 验证1
    # 验证2
    # 验证3
    f1()

之后调用f1方法,其实就是调用内存中的一个在inner方法基础上进行f1方法装饰的方法

@w1
def f1():
    print('f1')

装饰器的各种形式

上面列举的都是无参装饰器,其实装饰器还可以是有参数并且有返回值的,以下是一个无参装饰器:

from time import ctime, sleep

def timefun(func):
    def wrappedfunc():
        print("%s called at %s"%(func.__name__, ctime()))
        func()
    return wrappedfunc

#使用装饰器修饰foo函数
@timefun
def foo():
    print("I am foo")

foo()
sleep(2)
foo()

这里写图片描述

1.使用装饰器装饰有参数的函数

当需要修饰的方法(foo方法)有参数时,装饰器就需要做相应的修改,闭包中的内部方法wrappedfunc方法需要添加和foo数量相同的参数,否则就不能使用闭包中的外部方法timefun修饰foo方法。原因就在装饰器的工作原理中:Python解析器将wrappedfunc方法增加了foo方法的功能生成的新方法(相当于增加了几句代码的wrappedfunc方法)的引用赋值给foo函数指针,完成这次赋值的前提是wrappedfunc方法的参数和foo方法的参数个数和参数类型必须相同

from time import ctime, sleep
def timefun(func):
    #为wrappedfunc添加参数,参数名可以和foo方法不相同,但是参数个数和参数类型必须和foo方法相同
    def wrappedfunc(a,b):
        print("%s called at %s"%(func.__name__, ctime()))
        #注意这里也要修改
        func(a,b)
    return wrappedfunc

@timefun
def foo(a,b):
    print(a+b)

foo(1,2)
sleep(2)

这里写图片描述

2.使用一个装饰器装饰有不同参数的多个函数

如果想用同一个装饰器修饰多个函数,同时这些函数的参数个数又各不相同,这时可以使用*args,**kwargs 表示不定长度不定类型的参数

from time import ctime, sleep
def timefun(func):
    #修改wrappedfunc函数参数
    def wrappedfunc(*args,**kwargs):
        print("%s called at %s"%(func.__name__, ctime()))
        #注意这里也要修改
        func(*args,**kwargs)
    return wrappedfunc

@timefun
def foo1(a,b):
    print(a+b)

@timefun
def foo2(a,b,c):
    print(a+b+c)

foo1(1,2)
foo2(1,2,3)

3.使用装饰器装饰有返回值的函数

如果需要装饰的函数有返回值,那么wrappedfunc函数为了和被装饰函数格式相同,也需要返回值,这个返回值不是乱取的,而是应当将被修饰函数(即下例中wrappedfunc函数中的func函数)的返回值作为wrappedfunc函数的返回值
即使被修饰函数没有返回值,这样的修改也不会错误,因为没有返回值可以认为其返回一个none

from time import ctime, sleep
def timefun(func):
    def wrappedfunc(*args,**kwargs):
        print("%s called at %s"%(func.__name__, ctime()))
        #将被修饰函数的返回值返回
        return func(*args,**kwargs)
    return wrappedfunc

@timefun
def foo1(a,b):
    return a+b

@timefun
def foo2(a,b,c):
    print(a+b+c)

print(foo1(1,2))
foo2(1,2,3)

这里写图片描述

通用装饰器

根据以上各例,可以总结出一个能够修饰任意格式的函数的装饰器,即通用装饰器

def fun(func):
    def innerFunc(*args,**kwargs):
        ...
        return func(*args,**kwargs)
    return innerFunc

带参数的装饰器

装饰器在使用时,还可以加参数,传递信息,例如:

@fun("massage")
def hw():
    print("hello world")

这种带参数的装饰器,和上边举例的装饰器又有所不同,带参数的装饰器由两个闭包构成,其原理和不带参数的装饰器是相同的,结构也不难分析

from time import ctime, sleep
#定义一个变量pre接收传给装饰器的参数,同时将参数默认值设为"hello",如果没有传递参数,则pre为默认值"hello"
def timefun_arg(pre="hello"):
    def timefun(func):
        def wrappedfunc(*args,**kwargs):
            print("%s called at %s"%(func.__name__, ctime()))
            print(pre)
            return func(*args,**kwargs)
        return wrappedfunc
    return timefun

@timefun_arg("massage")
def foo1(a,b):
    return a+b

@timefun_arg()
def foo2(a,b,c):
    print(a+b+c)

print(foo1(1,2))
foo2(1,2,3)

这里写图片描述

猜你喜欢

转载自blog.csdn.net/eagleuniversityeye/article/details/80112813