Python进阶-装饰器

本章分为三个部分,分别是:

  1. 函数作用域
  2. 闭包
  3. 装饰器

函数作用域

函数内部作用域 local > 函数内部与内嵌函数之间 enclosing > 全局作用域 global > 内置作用域 build-in

下面看个示例

SCORE = 60 #100分的及格线

def check(score):
    if score >= SCORE:
        print('A')
    else:
        print('B')

SCORE = 60 #100分的及格线

def check(score):
    if score >= SCORE:
        print('A')
    else:
        print('B')

if __name__=='__main__':
    check(90)
#打印结果
A

解释一下:首先查找内部作用域,并没有SCORE的定义,然后函数也没用内嵌函数,之后查找全局区域找到了SCORE,并使用。
另外例如:len,max,min这些函数python标准库中,属于内置的build-in,可以直接调用,但是是到最后一步才会调用。

内嵌函数

在一个函数内部定义,只能该函数使用。

SCORE = 60 #100分的及格线

def check(score):
    if score >= SCORE:
        print('A')
    else:
        print('B')
    def print_score(): #声明内嵌函数
        print(score) 

    print_score() #调用内嵌函数

if __name__=='__main__':
    check(90)
#打印结果
A
90

闭包

Closure:内部函数中对enclosing作用域的变量进行引用。
需要讲一下在python中函数的实质和属性:

  1. 函数是一个对象
  2. 函数执行完毕之后内部变量被回收
  3. 函数具有属性
  4. 函数具有返回值

函数是一个对象,他可以复制给另一个变量。

Max = max #python内置函数
print(Max(3,2))
> 3

我们来看一个例子

SCORE = 60 #100分的及格线

def check(score):
    if score >= SCORE:
        print('A')
    else:
        print('B')
    def in_check():
        print('score:',score)

    #打印该函数中变量的地址
    print(in_check.__closure__)
    in_check()
    return in_check

if __name__=='__main__':
    func = check(90)
    print('---------------------------')
    func()
    print('---------------------------')
    print(func.__closure__) #打印该函数中变量的地址
#打印结果
A
(0x0000000002277498: int object at 0x000000005322AF80>,)
score: 90
---------------
score: 90
---------------
(0x0000000002277498: int object at 0x000000005322AF80>,)

从打印结果我们可以得出以下结论:

  1. func()就是in_check()
  2. in_check()中使用了变量score,它和func()中使用的是同一个变量,之所以内部变量没有被回收是因为函数的引用计数不为0

闭包的作用

说了闭包,但是他有什么用呢?
先看一个示例

SCORE_90 = 90  #150分的平均线是90
SCORE_60 = 60  #100分的平均线是60

def check_150(score):
    if score > SCORE_90:
        print('A')
    else:
        print('B')

def check_100(score):
    if score > SCORE_60:
        print('A')
    else:
        print('B')

if __name__=='__main__':
    check_150(78)
    check_100(78)
#打印结果
B
A

在这里假如又有了120分数的制度是不是又要加一份代码,或者A等级提高到了总分数的80%,这样改来改去就很麻烦。

所以使用闭包的方法就很轻松了,这就相当于控制了二维的变化,可以同时接受分数线的变化以及需要比较的分数变化。

def set_aver_score(score):
    def cmp(val):
        if val > score:
            print('A')
        else:
            print('B')

    return cmp

if __name__=='__main__':
    f_100 = set_aver_score(60)
    print(f_100.__closure__, type(f_100))
    f_100(86)
    f_100(59)
#打印结果
#可以看出f_100拥有一个局部变量也就是score,同时也是一个函数对象
(0x0000000002857498: int object at 0x0000000050CEABC0>,) 'function'>
A
B

这就体现出了,封装性和代码复用性

闭包的使用示例

假如我们现在需要一个求和函数。

def my_sum(*args):
    print('sum!')
    return sum(args)

    my_sum(1, 2, 3, 4, 5, 6)
    my_sum(1, 2, 3, 4, 5, 'A')
#打印结果
TypeError: unsupported operand type(s) for +: 'int' and 'str'

这样执行显然会有一个TypeError,因为就没有对参数检查。类似的假如我们还有一个求平均数的函数,他也不能要求一个str类型的参数,这样下来我们就需要写两份类型检查代码,所以我们是不是应该简化一下呢。

def my_sum(*args):
    print('sum!')
    return sum(args)

def dec(function):
    print('dec')
    def in_dec(*args):
        print('in_dec')
        if len(args) == 0:      #空参数
            return 0
        for val in args:
            if not isinstance(val, int):    #假如参数中函数有不匹配的类型,这里选择直接return
                return 0
        print('return function(*args)', function.__name__)  #查看function的类型
        return function(*args)      

    print('return in_dec')
    return in_dec

if __name__=='__main__':

    my_sum = dec(my_sum)                    #步骤1
    print(type(my_sum), my_sum.__name__)    #步骤2

    print('--------------')
    my_sum(1, 2, 3, 4, '5')                 #步骤3
    print('--------------')
    my_sum(1, 2, 3, 4, 5)                   #步骤4

#打印结果
dec
return in_dec
'function'> in_dec
--------------
in_dec
--------------
in_dec
return function(*args) my_sum
sum!

分析运行过程
步骤一:dec(my_sum)中将my_sum函数作为参数传入,然后打印print(‘dec’),定义了一个in_dec内嵌函数,然后运行打印print(‘return in_dec’),返回in_dec。然后将in_dec赋值给my_sum,特别注意现在的my_sum已经不是原来的my_sum了,这从步骤二的打印也可以看出来,类型已经改变。
步骤三:由于现在my_sum其实是in_dec所以执行代码由于参数不符合直接return
步骤四:正常打印print(‘in_dec’),判断语句符合,然后打印print(‘return function(*args)’, function.__name__),注意此时function是原来的my_sum(我又回来了!),最后将参数传入,计算结果,并将结果返回。

在这里我们绕大了一圈,也就是相当与给my_sum添加了一个额外的判断代码,而不需要修改原来的代码,并且其他函数也都可以使用。

装饰器

  1. 装饰器用来装饰函数
  2. 返回一个函数对象
  3. 被装饰的函数标识符指向返回的函数对象
  4. 语法糖 @dec

代码示例:

def dec(function):
    print('dec')
    def in_dec(*args):
        print('in_dec')
        if len(args) == 0:      #空参数
            return 0
        for val in args:
            if not isinstance(val, int):    #假如参数中函数有不匹配的类型,这里选择直接return
                return 0
        print('return function(*args)', function.__name__)  #查看function的类型
        return function(*args)

    print('return in_dec')
    return in_dec

@dec
def my_sum(*args):
    print('sum!')
    return sum(args)


if __name__=='__main__':

    print('__main__')
    print(type(my_sum), my_sum.__name__)
    res = print(my_sum(1, 2, 3, 4, '5'))
    print(res)
    print('--------------------------')
    res = my_sum(1, 2, 3, 4, 5)
    print(res)

#打印结果
dec
return in_dec
__main__
'function'> in_dec
in_dec
0
None
--------------------------
in_dec
return function(*args) my_sum
sum!
15

可以看出在执行main函数第一条语句之前装饰器就已经执行完毕了。也就是此时my_sum已经被替换了。接下来代码执行就和之前的是一样的。

你可能感兴趣的:(Python)