python 中的闭包以及装饰器

650 阅读5分钟
原文链接: sunms.codefly.top

这里总结一波python中的闭包以及装饰器:

学习闭包以及装饰器首先要了解函数的作用域,在python中函数中变量的作用域优先级如下:

python中的变量作用域满足LEGB原则:
L:local函数内部作用域
E:enclosing函数内部与内嵌函数之间
G:global全局作用域
B:build_in内置作用域
优先级关系如下: L>E>G>B

注意:不是所有的语言都是支持函数内部可以访问函数外部的变量。比如说PHP中函数内部查找不到变量的时候是不会去函数外部查找的,除非使用global指定全局。在这方面,python和javascript比较类似。

了解了函数的作用域以后,接着了解一下什么是闭包:

闭包就是内部函数中对enclosing作用域中的变量进行引用。内部函数中如果含有外部函数定义的变量,则会将这些变量加入到内部函数的属性中来。

写个简单的python闭包程序,如下:

def get_value(passline):
    print passline
    print "%x"%id(passline)
    def in_func():
        print passline
        print "%x"%id(passline)
    return in_func
f=get_value(60)

get_value()函数中的内部函数in_func()中引用了外部定义的变量passline,所以就构成了一个简单的闭包。接下来需要简单分析一下,函数执行过程之中,变量以及内存的关系:

  (1)f=get_value(60),get_value()函数执行完毕,理论上get_value()所占用的内存会被python解释器回收。但是因为get_value()函数返回的是in_func()函数,in_func()函数中调用了变量passline,所以get_value()函数执行完毕,资源没有完全释放掉。

(2):f是由get_value()返回的in_func()函数。可以查看f.__closure__属性,这个属性返回内部函数所占用的外部函数的,没有释放内存资源。

说了一大堆,闭包有什么用那?闭包可以进行代码的复用,减少代码的冗余。

举例说明一个:定义一个函数用来计算传递过来的参数之和,另外定义一个函数用来计算传递过来的参数的平均值,一般情况下,可以这样实现:

#求和函数
def my_sum(*args):
      return sum(args)
#求平均值函数
def my_avg(*args):
      return sum(*args)/len(args)
#调用
print(my_sum(1,2,3,4))
print(my_avg(1,2,3,4))

上述的实现方式没有问题,但是每个函数都不健壮,比如说缺少对数据类型的判断,缺少预防除零操作的手段等等,下面就进行一下修改:

#求和函数
def my_sum(*args):
      if len(args) == 0:
            return 0
      for val in args:
            if not isinstance(val,"int"):
                return 0
      return sum(args)
#求平均值函数
def my_avg(*args):
      if len(args) == 0:
            return 0
      for val in args:
            if not isinstance(val,"int"):
                return 0
      return sum(*args)/len(args)
#调用
print(my_sum(1,2,3,4))
print(my_avg(1,2,3,4))

上面对两个函数进行了优化,解决了上述提出的类型匹配,除零问题。但是可以发现,两个函数之中存在完全相同的部分。这也就是代码冗余了,为了提高代码的复用性以及健壮性,我们还需要解决冗余的问题。

如何解决上面的代码的冗余问题那?可以看一下,下面的代码:

#求和函数
def my_sum(*args):
      return sum(args)
#求平均值函数
def my_avg(*args):
      return sum(*args)/len(args)
#闭包
def dec(func):
    def in_dec(*args):
        if len(args) == 0:
            return 0
        for val in args:
            if not isinstance(val,"int"):
                return 0
        func(*args)
    return in_dec
my_sum_one=dec(my_sum)
my_avg_one=dec(my_avg)
#调用
print(my_sum_one(1,2,3,4))
print(my_avg_one(1,2,3,4))

上面的代码可以看到,我们将求和函数以及求平均值函数两者之间相同的代码放到了dec()函数中的内部函数in_dec()函数中了。这样也就解决了代码冗余的问题了。

上述代码虽然解决的问题,但是是怎么实现的那,这里分析一下:

(1):dec(my_sum)—执行dec()函数,传递的参数是my_sum()函数

(2):执行dec()函数,return in_dec()也就是说,dec()函数将返回in_dec()函数,所以dec(my_sum) 就等于 in_dec() —-其实是等于函数中的func变量等于my_sum的in_dec()

(3):又因为my_sum_one=dec(my_sum),所以my_sum_one 就等于函数中的func变量等于my_sum的in_dec()

(4):调用my_sum_one(1,2,3,4)就相当于调用in_dec(1,2,3,4),并且in_dec中的func变量等于my_sum

(5):执行in_dec(1,2,3,4),所以会进行参数类型的判断,参数数量的判断以及执行func()函数,也就是传入的my_sum()函数

(6):下面的执行求平均值,同样的道理。不是同一个函数,但是因为闭包,可以共用同一代码,这也就不会造成代码的冗余了

解释完了闭包,再来解释一下python中的装饰器。python中的装饰器其实就是闭包,只不过python在语法上是支持闭包的,python通过@闭包函数名称这种语法糖(装饰器)来实现闭包。下面还是举例说明一下:

#定义一个闭包函数
def abc_out(func):
    def in_abc(*args):
          if "fuck" in args:
              print("")
              return False
          if "shit" in args:
              print("")
              return False
          func(*args)
    return in_abc

#通过装饰器这种语法糖的形式调用闭包函数
@abc_out
def print_value(value):
    print(value)

#调用函数
print_value("fuck") ---打印为""

装饰器的调用:在调用print_value()函数的时候,python解释器会将函数print_value传递到闭包函数

abc_out()中执行,这也就实现了闭包。

在python中,装饰器不仅仅是普通函数的专利,类中的函数同样可以进行使用,使用方式与普通函数完全相同,这里就不在赘述了。