0%

20、函数高级.md

一、闭包

1、闭包的概念

在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。闭包就是能够读取其他函数内部变量的函数。在本质上,闭包是将函数内部和函数外部连接起来的桥梁

用比较容易懂的人话说,就是当某个函数被当成对象返回时,夹带了外部变量,就形成了一个闭包,例如:

1
2
3
4
5
6
7
8
9
def parent():
msg = '局部变量'
def child():
print(msg) # 外部变量
return child # 返回的是函数,带外部变量的

if __name__ == '__main__':
p = parent()
p()

2、闭包的用途

闭包可以用在许多地方。它的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中,不会在被调用后被自动清除。

为什么会这样呢?原因就在于parent是child的父函数,而child被赋给了一个全局变量,这导致child始终在内存中,而child的存在依赖于parent,因此parent也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

3、闭包条件

  • 必须是一个嵌套的函数。
  • 闭包必须返回嵌套函数。
  • 嵌套函数必须引用一个外部的非全局的局部自由变量。

4、优点

  • 避免使用全局变量
  • 可以提供部分数据的隐藏
  • 可以提供更优雅的面向对象实现

5、举个栗子

  1. 使用闭包代替类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    # 用类实现一个加法的类是这样
    class Test(object):
    def __init__(self, a, b):
    self.a = a
    self.b = b

    def add(self):
    return self.a + self.b

    # 用闭包实现
    def test(a):
    def add(b):
    return a + b
    return add

    ad = test(1) # 是不是很像类的实例化
    print(ad(1)) # out:2
    print(ad(2)) # out:3
    print(ad(3)) # out:4
  2. 栗子1(惰性求值)

    1
    2
    3
    4
    5
    6
    def foo(msg):
    def say():
    print("hello " + str(msg))
    return say
    say = foo("志玲")
    say()
  3. 栗子2(使用闭包保持状态)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    def count_down(num):
    def next():
    nonlocal num
    temp = num
    num -= 1
    return temp
    return next

    if __name__ == '__main__':
    n = count_down(10)
    while True:
    # 每调用一次就会减少一次计数
    value = n()
    print(value)
    if value == 0:
    break
  4. 栗子3

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    """
    比如,如果你希望函数的每次执行结果,都是基于这个函数上次的运行结果。我以一个类似棋盘游戏的例子来说明。假设棋盘大小为50*50,左上角为坐标系原点(0,0),我需要一个函数,接收2个参数,分别为方向(direction),步长(step),该函数控制棋子的运动。棋子运动的新的坐标除了依赖于方向和步长以外,当然还要根据原来所处的坐标点,用闭包就可以保持住这个棋子原来所处的坐标。
    """

    origin = [0, 0] # 坐标系统原点
    legal_x = [0, 50] # x轴方向的合法坐标
    legal_y = [0, 50] # y轴方向的合法坐标
    def create(pos=origin):
    def player(direction,step):
    # 这里应该首先判断参数direction,step的合法性,比如direction不能斜着走,step不能为负等
    # 然后还要对新生成的x,y坐标的合法性进行判断处理,这里主要是想介绍闭包,
    new_x = pos[0] + direction[0]*step
    new_y = pos[1] + direction[1]*step
    pos[0] = new_x
    pos[1] = new_y
    #注意!此处不能写成 pos = [new_x, new_y]
    return pos
    return player

    player = create() # 创建棋子player,起点为原点
    print(player([1,0],10)) # 向x轴正方向移动10步
    print player([0,1],20) # 向y轴正方向移动20步
    print player([-1,0],10) # 向x轴负方向移动10步

4、使用闭包的注意点

  1. 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成性能问题,甚至有可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
  2. 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

二、装饰器(Decorator)

1、前期基础

1.1、高阶函数

  1. 说明
  • 把一个函数名当做形实传给另外一个函数(不修改被装饰的函数源代码的情况下为其添加功能)
  • 返回值中包含函数名(返回值中包含函数名(不修改函数的调用方式))
  1. 举个栗子

    1. 把函数作为参数传递

      1
      2
      3
      4
      5
      6
      7
      8
        def fun():
      print("这个是一个函数")

      def test1(func):
      print(func)
      func()

      test1(fun)
  2. 在函数前面添加代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    def fun():
    print("核心代码")

    def test1(func):
    print('在原始核心代码之前添加功能')
    func()
    print('在原始核心代码之后添加功能')
    if __name__ == '__main__':
    test1(fun)

    #缺点调用方式发生改变,不能像原来的方法去调用被修饰的函数
  3. 返回值中包含函数名(不修改函数的调用方式)

    1
    2
    3
    4
    5
    6
    7
    8
    def bar():
    print("核心代码")
    def test3(func):
    print('在原始核心代码之前添加功能')
    return func
    if __name__ == '__main__':
    bar = test3(bar)
    bar()

1.2、嵌套函数

  1. 说明

    在一个函数体内,用def去声明一个函数

  2. 举个栗子

    • 栗子1(函数嵌套)

      1
      2
      3
      4
      5
      6
      7
      def foo():
      print("在核心代码之前调用")
      def bar():
      print("核心代码")
      bar()
      if __name__ == '__main__':
      foo()
    • 栗子2(函数调用)

      1
      2
      3
      4
      5
      6
      7
      8
      #注意两者之间的区别
      def foo():
      print("in the foo")
      def bar():
      print("in the bar")
      bar()
      if __name__ == '__main__':
      foo()

2、什么是装饰器

装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能,也是闭包的一种应用场景

装饰器 = 高阶函数 + 嵌套函数

3、作用

为其他函数添加附加功能

4、原则

不能修改被装饰的函数的源代码,不能修改被装饰的函数的调用方式。

即:装饰器对待被修饰的函数是完全透明的。

5、定义装饰器

  1. 定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    def strong(fun):  # fun 将来就是被替换的 hello
    def new_hello():
    print("我是装饰器中的代码, 在 hello 之前执行的")
    fun()
    print("我是装饰器中的代码, 在 hello 之后...执行的")
    return new_hello

    @strong
    def hello():
    print("我是 hello 函数中的代码")

    if __name__ == '__main__':
    # 这里调用的其实是装饰器返回的函数.
    hello()
  1. 说明
    • 在需要添加的装饰函数上面一行使用@来添加装饰器
    • @后面紧跟中装饰器名strong, 当然你可以定于任何的名字.
    • strong是装饰器, 本质上是一个函数. 他接收函数hello作为参数, 并返回一函数来替换掉hello(当然也可以不替换).
    • hello使用装饰器之后, 相当于hello函数使用下面的代码被替换掉了hello = strong(hello)
    • 在调用hello的时候, 其实是调用的strong()返回的那个函数.
  2. 满足条件
    • 装饰器函数运行在函数定义的时候
    • 装饰器需要返回一个可执行的对象
    • 装饰器返回的可执行对象要兼容函数f的参数

三、附

1、语法糖

就相当于汉语里的成语。即,用更简练的言语表达较复杂的含义。指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会,例如5*5等同于5+5+5+5+5