Echoshell
  • Selfservice book
  • MacBook Setup
  • Linux SystemAdmin
    • Linux useful commands
    • Analyze tools
      • Hardware overview
      • Vmstat
      • Top
      • Sar
      • ltrace
      • strace
      • Netstat/SS
      • lsof
      • iostat
      • iotop
    • Performance Tuning
      • CPU
      • Momory
      • Disk/Storage
      • Network
      • Process
  • SRE vs DevOps
    • Ansible
    • Git
    • Jenkins
    • HashiCorp-Terraform
      • Terraform VS. CloudFormation
      • Main elements
        • Provider
        • State Storage and Locking
        • Backends
      • Basic Elements
        • Install
        • Resource
        • Data
        • Output
        • Locals
      • Commands
        • terraform plan
        • terraform apply
        • terraform import
    • HashiCorp-Vault
    • HashiCorp-Consul
    • CI/CD
      • GoCD
  • Automation/Script Language
    • Shell&Bash
      • Useful links
    • Python
      • 基础
        • py2 vs py3
        • 字符编码
        • 输入和输出
      • 数据类型Data Types
        • 字符串String
        • 列表List
        • 元组Tuple
        • 字典Dict
        • 集合Set
      • 函数Function
        • 定义函数
        • 函数参数
      • 函数式编程
        • 匿名函数lamba
        • map/reduce/filter
        • 闭包
        • 装饰器
      • 类Class
        • 类和实例
        • 继承和多态
        • 类方法和静态方法
        • 定制类和魔法方法
        • 元类-陌生的 metaclass
      • 高级特性
        • 迭代器iterator
        • 生成器generator
        • 上下文管理
      • 文件和目录
        • 读写文本文件
        • os 模块
      • 进程、线程和协程
        • 进程
        • 线程
        • ThreadLocal
        • 协程
      • 异常处理Exception
      • 正则表达式Regular-Expressions
        • re 模块
      • 标准模块
        • datetime
        • argparse
      • 第三方模块Three Parts
        • Click
        • JIRA
        • Email
        • Jenkins
      • 系统相关模块System modules
        • Subprocess
        • shutil
  • Learning notes from organization
  • IT Tech Team Leader/Senior System administrator
    • Helpdesk
    • Windows Servers
      • Radius Server
      • NFS
      • Munki
      • 802.1X
    • Networks
  • Personal views and ideas from my careen path
    • Team management
    • Career path change from IT to devops
Powered by GitBook
On this page
  • 闭包的作用
  • 常见误区
  • 小结
  • 参考资料

Was this helpful?

  1. Automation/Script Language
  2. Python
  3. 函数式编程

闭包

在 Python 中,函数也是一个对象。因此,我们在定义函数时,可以再嵌套定义一个函数,并将该嵌套函数返回,比如:

from math import pow

def make_pow(n):
    def inner_func(x):     # 嵌套定义了 inner_func
        return pow(x, n)   # 注意这里引用了外部函数的 n
    return inner_func      # 返回 inner_func

上面的代码中,函数 make_pow 里面又定义了一个内部函数 inner_func,然后将该函数返回。因此,我们可以使用 make_pow 来生成另一个函数:

>>> pow2 = make_pow(2)  # pow2 是一个函数,参数 2 是一个自由变量
>>> pow2
<function inner_func at 0x10271faa0>
>>> pow2(6)
36.0

我们还注意到,内部函数 inner_func 引用了外部函数 make_pow 的自由变量 n,这也就意味着,当函数 make_pow 的生命周期结束之后,n 这个变量依然会保存在 inner_func 中,它被 inner_func 所引用。

>>> del make_pow         # 删除 make_pow
>>> pow3 = make_pow(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'make_pow' is not defined
>>> pow2(9)     # pow2 仍可正常调用,自由变量 2 仍保存在 pow2 中
81.0

像上面这种情况,一个函数返回了一个内部函数,该内部函数引用了外部函数的相关参数和变量,我们把该返回的内部函数称为闭包(Closure)。

在上面的例子中,inner_func 就是一个闭包,它引用了自由变量 n。

闭包的作用

  • 闭包的最大特点就是引用了自由变量,即使生成闭包的环境已经释放,闭包仍然存在。

  • 闭包在运行时可以有多个实例,即使传入的参数相同。

>>> pow_a = make_pow(2)
>>> pow_b = make_pow(2)
>>> pow_a == pow_b
False
  • 利用闭包,我们还可以模拟类的实例。

这里构造一个类,用于求一个点到另一个点的距离:

from math import sqrt

class Point(object):
    def __init__(self, x, y):
        self.x, self.y = x, y

    def get_distance(self, u, v):
        distance = sqrt((self.x - u) ** 2 + (self.y - v) ** 2)
        return distance

>>> pt = Point(7, 2)        # 创建一个点
>>> pt.get_distance(10, 6)  # 求到另一个点的距离
5.0

用闭包来实现:

def point(x, y):
    def get_distance(u, v):
        return sqrt((x - u) ** 2 + (y - v) ** 2)

    return get_distance

>>> pt = point(7, 2)
>>> pt(10, 6)
5.0

可以看到,结果是一样的,但使用闭包实现比使用类更加简洁。

常见误区

闭包的概念很简单,但实现起来却容易出现一些误区,比如下面的例子:

def count():
    funcs = []
    for i in [1, 2, 3]:
        def f():
            return i
        funcs.append(f)
    return funcs

在该例子中,我们在每次 for 循环中创建了一个函数,并将它存到 funcs 中。现在,调用上面的函数,你可能认为返回结果是 1, 2, 3,事实上却不是:

>>> f1, f2, f3 = count()
>>> f1()
3
>>> f2()
3
>>> f3()
3

为什么呢?原因在于上面的函数 f 引用了变量 i,但函数 f 并非立刻执行,当 for 循环结束时,此时变量 i 的值是3,funcs 里面的函数引用的变量都是 3,最终结果也就全为 3。

因此,我们应尽量避免在闭包中引用循环变量,或者后续会发生变化的变量。

那上面这种情况应该怎么解决呢?我们可以再创建一个函数,并将循环变量的值传给该函数,如下:

def count():
    funcs = []
    for i in [1, 2, 3]:
        def g(param):
            f = lambda : param    # 这里创建了一个匿名函数
            return f
        funcs.append(g(i))        # 将循环变量的值传给 g
    return funcs

>>> f1, f2, f3 = count()
>>> f1()
1
>>> f2()
2
>>> f3()
3

小结

  • 闭包是携带自由变量的函数,即使创建闭包的外部函数的生命周期结束了,闭包所引用的自由变量仍会存在。

  • 闭包在运行可以有多个实例。

  • 尽量不要在闭包中引用循环变量,或者后续会发生变化的变量。

参考资料

Previousmap/reduce/filterNext装饰器

Last updated 5 years ago

Was this helpful?

返回函数 - 廖雪峰的官方网站
Why aren't python nested functions called closures? - Stack Overflow