欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Python 进阶_闭包 & 装饰器

程序员文章站 2022-05-21 14:48:41
闭包 Closure: 如果内层函数引用了外层函数的局部变量(L),并且在外层函数中 return 内层函数时,这种关系就称之为闭包。 闭包的特点就是返回的内层函数还引用了外层...

闭包

Closure: 如果内层函数引用了外层函数的局部变量(L),并且在外层函数中 return 内层函数时,这种关系就称之为闭包。

闭包的特点就是返回的内层函数还引用了外层函数的局部变量,所以要想正确的使用闭包,那么就要确保这个被内层函数引用的局部变量是不变的。

EXAMPLE:

In [71]: def count():
    ...:     fs = []
    ...:     for i in range(1, 4):
    ...:         def f():
    ...:              return i*i
    ...:         fs.append(f)
    ...:     return fs
    ...:
    ...: f1, f2, f3 = count()
    ...:

In [72]: f1,f2,f3
Out[72]: (, , )

In [73]: f1(),f2(),f3()
Out[73]: (9, 9, 9)

最总 f1(),f2(),f3() 输出的是 (9,9,9) 而不是 (1,4,9)。

原因就是当 count() 函数返回了 f1(),f2(),f3() 3个函数时,这3个函数所引用的变量 i 的值已经变成了 3。由于 f1、f2、f3 并没有被调用,所以,此时他们并未计算 i*i 。返回函数不要引用任何循环变量,或者后续会发生变化的变量。

函数的实质和属性

函数是一个对象,在内存中会占用存储空间 一般来说函数执行完成后其内部的变量都会被回收。但如果函数中的变量被 return 时,那么这个变量是不会被回收的,因为其引用计数 != 0 函数具有属性 函数具有返回值 函数通过函数名来引用

EXAMPLE:

def calc_sum(lst):
    def lazy_sum():
        return sum(lst)
    return lazy_sum

调用calc_sum()并没有计算出结果,而是返回函数:

>>> func = calc_sum([1, 2, 3, 4])
>>> func

对返回的函数进行调用时,才计算出结果:

>>> func()
10

由于可以返回函数,所以我们在后续代码里就可以灵活的决定到底要不要调用该返回的函数。

再看下面一个例子:

def set_passline(passline):
    def cmp(val):
        if val >= passline:
            print "pass"
        else:
            print "failed"
    return cmp

f_100 = set_passline(60)
f_100(89)
print "f_100 type: %s" % type(f_100)
print "f_100 param: %s" % f_100.__closure__
print '*' * 20
f_150 = set_passline(90)
f_150(89)

Output:

In [37]: run demo_1.py
pass
f_100 type: 
f_100 param: 
********************
failed

set_passline() 和 cmp() 组成一个闭包,最明显的好处在于可以提高 cmp() 函数内代码实现的复用性和封装(内层函数不能在全局作用域中被直接调用)。不需要为两个不用的判断(100 | 150)而写两个相似的函数。

而且外层函数中的局部变量还可以是一个函数。

继续看下一个例子:

def my_sum(*arg):
    print "my_sum"
    print arg
    return sum(arg)

def my_average(*arg):
    return sum(arg)/len(arg)

def dec(func):
    print "dec"
    print func
    def in_dec(*arg): 
        print "in_dec"
        print func
        print arg
        if len(arg) == 0:
            return 0
        for val in arg:
            if not isinstance(val, int):
                return 0
        return func(*arg)
    return in_dec

# dec(my_sum)(1, 3, 4)
my_sum_result = dec(my_sum)
print my_sum_result(1, 3, 4)
print my_sum_result.__closure__

print '*' * 40

my_average_result = dec(my_average)
print my_average_result(1, 3, 4)
print my_average_result.__closure__

Output:

In [35]: run demo_1.py
dec

in_dec

(1, 3, 4)
my_sum
(1, 3, 4)
8
(,)
****************************************
dec

in_dec

(1, 3, 4)
2
(,)

小结

闭包能够提高代码的复用和内层函数的封装 内层函数引用外层函数的局部变量可以是基本数据类型对象,也可以是一个函数对象 调用外层函数(传递外层形参)时,返回的是一个内存函数对象,然后可以再对内层函数传递形参,这样就增强了代码的灵活性。EG. 由外层设定条件,内层进行处理。

当然我们可以能回认为这样的写法并不美观,所以我们继续看 Python 的装饰器和闭包的关系。

装饰器

装饰器用于装饰函数 ? dec() 用于装饰 my_sum() 装饰器返回一个新的函数对象 ? dec() 返回一个函数 in_dec() 被装饰的函数标识符指向返回的函数对象 ? my_sum() 指向 in_dec() my_sum_result = dec(my_sum) 即: my_sum_result 是 dec() 返回的函数对象,引用指向了 my_sum() 语法糖:@装饰器名称

从上面几点特性可以看出,装饰器就是对闭包的使用。

将上面的例子转换一下写法:

def dec(func):
    print 'call dec()'
    def in_dec(*arg): 
        print "call in_dec()"
        if len(arg) == 0:
            return 0
        for val in arg:
            if not isinstance(val, int):
                return 0
        return func(*arg)
    return in_dec

# @dec 执行的过程:
# 1. dec(my_sum) --> return in_dec
# 2. my_sum = in_dec
# 为了丰富被装饰函数的功能(这里是丰富了一个参数验证功能),是一种抽象的编程思维
@dec        
def my_sum(*arg):
    print 'call my_sum()'
    return sum(arg)

def my_average(*arg):
    return sum(arg)/len(arg)

print my_sum(1,2,3,4,5)

Output:

In [43]: run demo_1.py
call dec()
call in_dec()
call my_sum()
15

更加深入的看看装饰器的执行过程

chocolate() 只是一个普通的函数
def chocolate():
    print "call chocolate()"
chocolate()

Output:

In [65]: run demo_1.py
call chocolate()

正常的调用了 chocolate() 函数

chocolate() 加上了一个没有返回内存函数的装饰器
def jmilkfan(func):
    def in_jmilkfan():
        print "call in_jmilkfan()"
        func()
    print "call jmilkfan()"

@jmilkfan
def chocolate():
    print "call chocolate()"
chocolate()

Output:

In [51]: run demo_1.py
call jmilkfan()  # 首先调用了 jmilkfan 装饰器对应的 jmilkfan() 函数

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
D:\Development\Python27\workspace\demo_1.py in ()
      9     print "call chocolate()"
     10 #print 'typr chocolate:%s' % type(chocolate)
---> 11 chocolate()

TypeError: 'NoneType' object is not callable

**ERROR:**chocolate 是一个’NoneType’对象

因为 jmilkfan() 默认 return 了一个 None, 而不是内层函数 in_jmilkfan(),这里说明装饰器对应的 jmilkfan() 的确被调用了。但却没有返回内层函数给 chocolate 接收。

def jmilkfan(func):

    print 'func id:%x' % id(func)

    def in_jmilkfan():
        print "call in_jmilkfan()"
        func()

    print "call jmilkfan()"
    print 'in_jmilkfan():%s' % in_jmilkfan.__closure__
    return in_jmilkfan

@jmilkfan
def chocolate():
    print 'chocolate():%s' % chocolate.__closure__
    print "call chocolate()"
chocolate()

Output:

In [67]: run demo_1.py
func id:490ecf8
call jmilkfan()
in_jmilkfan():
call in_jmilkfan()
chocolate():
call chocolate()

可看出 choholate()、 in_jmilkfan()、 func 都是引用了同一个函数对象。即: in_jmilkfan() 被 chocolate() 接收了。

注意当被装饰的函数拥有形参时,装饰器的内层函数必须定义相同的形参,否则会报错。

def jmilkfan(func):

    print 'func id:%x' % id(func)

    def in_jmilkfan(x, y):
        print "call in_jmilkfan()"
        func(x, y)

    print "call jmilkfan()"
    print 'in_jmilkfan():%s' % in_jmilkfan.__closure__
    return in_jmilkfan

@jmilkfan
def chocolate(x, y):
    print 'chocolate():%s' % chocolate.__closure__
    print "call chocolate(), the value is:%d" % (x + y)

chocolate(1, 2)

Output:

In [70]: run demo_1.py
func id:490edd8
call jmilkfan()
in_jmilkfan():
call in_jmilkfan()
chocolate():
call chocolate(), the value is:3

小结

装饰器的作用: 在提供了闭包的工作之外,优雅了语法

装饰器的使用过程

调用了装饰器对应的外层函数,并且将被装饰函数作为实参传递给外层函数。目的是为了能够让内存函数使用这个作为外层函数的局部变量的被装饰函数,从而执行被装饰函数的代码实现。 外层函数将内层函数的引用返回给被装饰函数,实现闭包。

装饰器能解决什么问题?

问题:如果我们定义了一个函数后,又想在运行时动态的为这个函数增加功能,但是又不希望改动这个函数本身的代码?那么我们能否将这个函数作为一个参数传递给另一个函数,从而产生一个拥有更多功能的新函数呢 ?

这就用到了所谓的高阶函数:

1. 可以接受函数作为参数(函数又函数名来引用,函数名的本质就是一个变量)

2. 可以返回一个函数

这样高阶函数就能够到达我们希望接收一个函数并为其进行包装后再返回一个新的函数的效果。

EXAMPLE:

func = new_func(func)
# new_func 是一个高阶函数,也可以成为一个装饰器
# 因为函数是通过函数名来引用的,所以我们可以将一个由高阶函数返回的新的函数再次赋值给原来的函数名
# 这样的话 func 的原始定义函数就被彻底隐藏起来了,达到了封装的效果
# Python 还可以通过 @ 来简化装饰器的调用, EG:
@new_func
def func(x):
    ...

小结

装饰器可以极大的简化代码,避免每个函数编写重复性的代码。

我们可以将需要被重复且希望动态调用的代码写成装饰器,EG:

打印日志:@log

检测性能:@performance

数据库事务:@transaction

URL 路由:@post('/register')