Python的闭包是什么意思?

闭包算是编程语言里一个比较常见的概念,但说实话,这个名词有点晦涩。在查看了半天网上的资料后,还是有点不明就里。

我疑惑的点主要是:这个东西是用来解决什么问题的?或者说,他的作用是什么?

先说作用

查阅了很多资料后,总结有下面几个作用:

  • 在某局部变量的作用外,依然可以访问到此局部变量
  • 可以避免使用全局变量,从而减少可能带来的影响(很多文章把此项也称之为:保存当前的运行环境)
  • 可以把多参数的函数变成单参数的函数(大多数文章把此项称之为:可以根据外部作用域的局部变量得到不同的结果)

下面举例子来分别说一下这几个功能。

In [33]: def test_1():
    ...:     name = 'Tom'
    ...:

In [35]: name

NameError: name 'name' is not defined

In [48]: def test_2():
    ...:     name = 'Tom'
    ...:     def test_3():
    ...:         print(name)
    ...:     return test_3

In [49]: test = test_2()

In [50]: test
Out[50]: .test_3()>

In [51]: print(test)
.test_3 at 0x000001F9195D5EE8>

In [52]: test()
Tom

可以看到第一个例子中,是没办法获取到name的值的;

第二个例子中,就可以得到name的值,当然,也不是直接访问的。

实现的原理就是利用了局部变量的作用域,既然外面访问不到局部变量name,那么就从函数里去访问,间接的得到这个值。

在第二个例子中,调用test_2()的时候,就产生了一个闭包:test_3()。这个地方其实是闭包的一个典型的表现形式,然而看起来依然晦涩。所以私以为,从作用上来讲,逻辑更顺畅。

所以也有人这么称呼闭包:

闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来。

假如函数的功能是对一个list进行append/pop操作,执行N步后,返回其结果,我们可以这样实现:

In [54]: nums = [1 ,2, 3, 4]

In [55]: def append_num(x):
    ...:     nums.append(x)
    ...:

In [56]: append_num(5)

In [57]: nums
Out[57]: [1, 2, 3, 4, 5]

这个需求中,最重要的一步就是要保存执行的中间结果,所以需要用到全局变量。

但是很显然,这样实现并不优雅,当项目庞大或者多人合作的时候,全局变量可能带来灾难性的后果。同时,在函数中对全局变量进行操作,会导致过程不可控。

此时,闭包是一个很好的选择。也就是说闭包可以保存类似需求中的中间变量,如下:

In [59]: def append_nums(nums):
    ...:     def append_x(x):
    ...:         nums.append(x)
    ...:         return nums
    ...:     return append_x
    ...:

In [60]: tmp = append_nums([1])

In [61]: tmp.append_x(2)

AttributeError: 'function' object has no attribute 'append_x'

In [62]: tmp(2)
Out[62]: [1, 2]

In [63]: tmp(3)
Out[63]: [1, 2, 3]

In [64]: tmp
Out[64]: .append_x(x)>

可以看到,在声明tmp之后,可以像调用函数一样直接把参数直接传递给tmp。同时,每次执行,都是在上一次执行的结果上append,达到了我们的目的,同时看起来比较优雅。

再看tmp,会发现tmp本身就是一个函数,所以调用的话和函数使用是一样的。

当然,这里也有一个明显的好处:可以声明不同的num,两者互不干扰。

In [65]: tmp_1 = append_nums([3])

In [66]: tmp_1(0)
Out[66]: [3, 0]

是不是感觉又有点像类和对象的关系~

一般来说,当只有一个内部方法的时候,闭包比类更合适、更简单。

同时,如果和1中的例子相比的话,是不是感觉复用性提高了很多?

有一个更常见的例子,就是棋牌游戏的

以一个类似棋盘游戏的例子来说明。假设棋盘大小为50*50,左上角为坐标系原点(0,0),我需要一个函数,接收2个参数,分别为方向(direction),步长(step),该函数控制棋子的运动。棋子运动的新的坐标除了依赖于方向和步长以外,当然还要根据原来所处的坐标点,用闭包就可以保持住这个棋子原来所处的坐标。

origin = [0, 0] # 坐标系统原点
legal_x = [0, 50] # x轴方向的合法坐标
legal_y = [0, 50] # y轴方向的合法坐标

def create(pos):
 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(origin) # 创建棋子player,起点为原点
print player([1,0],10) # 向x轴正方向移动10步
print player([0,1],20) # 向y轴正方向移动20步
print player([-1,0],10) # 向x轴负方向移动10步

这一点,其实在上面的例子中就能看出来:

在使用了闭包这个特性之后,声明变量后,再使用的话,只需要传递里面函数的参数就好了。

再说定义

维基百科:

在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。

引用了自由变量的函数是什么?

def print_msg():
    msg = "python"
    def printer():
        print(msg)
    return printer

test = print_msg()
test()

这个例子中的printer其实就是一个引用了自由变量/外部临时变量的函数。

通常情况来讲,msg属于print_msg的局部变量,只能再print_msg()执行的时候调用。

但上面的例子中,test声明的时候,就是print_msg()执行的时候;但当test()执行的时候,我们依然拿到了msg的值。这就是闭包的一个特点,现在看是不是1中的例子更清楚些了?

那什么时候用闭包呢?除去上面举例子的几个场景,还有一个更为常见的:装饰器。更详细的说,其实装饰器是闭包的一个应用场景。

def make_wrap(func):
    def wrapper(*args):
        print("before function")
        func(*args)
        print("after function")
    return wrapper

@make_wrap
def print_msg(msg):
    print(msg)

>>> print_msg("Hello")
before function
Hello
after function

最后说一些坑

In [69]: def outer():
    ...:     x = 1
    ...:     def inner():
    ...:         x = 2
    ...:         print(x)
    ...:     print(x)
    ...:     inner()
    ...:     print(x)
    ...:

In [70]: outer()
1
2
1

`python
In [71]: def outer():
…: x = 1
…: def inner():
…: x = x + 2
…: return x
…: return inner
…:

In [72]: f = outer()

In [73]: f()

in inner()
2 x = 1
3 def inner():

Original: https://www.cnblogs.com/wswang/p/13175740.html
Author: wswang
Title: Python的闭包是什么意思?

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/585803/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • ThinkPHP5 远程命令执行漏洞

    一、ThinkPHP介绍 轻量级框架,内部OOP和面向过程代码都存在,是国人自己开发的框架。ThinkPHP是一个快速、兼容而且简单的轻量级国产PHP开发框架,诞生于2006年初,…

    Linux 2023年6月14日
    078
  • docker-部署jumpserver

    Docker 部署 jumpserver 堡垒机 容器部署 jumpserver-1.4.10 服务端 #最好单一个节点 容器运行Mysql 5.6.46 #myql , redi…

    Linux 2023年6月14日
    099
  • 常见网络安全设备

    一、防火墙定位:访问控制类产品,网络出现后的第一类安全产品。功能:隔离内网、外网以及DMZ区(业务系统对外发布区,Web应用服务器,邮件服务器等)并控制用户访问。部署方式:通常部署…

    Linux 2023年6月14日
    087
  • termius好用的shell终端

    ipad下可用 posted @2022-07-23 12:41 jiftle 阅读(75 ) 评论() 编辑 Original: https://www.cnblogs.com/…

    Linux 2023年5月28日
    0112
  • centos8 安装kudu

    1、安装cmake yum install cmake -y 2、安装python yum install python39 -y 设置默认Python版本 alternative…

    Linux 2023年6月8日
    066
  • 保姆教程系列一、Linux搭建Nacos

    前言: 请各大网友尊重本人原创知识分享,谨记本人博客:南国以南i 简介: Nacos是阿里巴巴开源的一款支持服务注册与发现,配置管理以及微服务管理的组件。用来取代以前常用的注册中心…

    Linux 2023年6月14日
    091
  • 操作系统实战45讲- 02 几行汇编几行C:实现一个最简单的内核

    本节源代码位置https://gitee.com/lmos/cosmos/tree/master/lesson02/HelloOS Hello OS 之前,我们先要搞清楚 Hell…

    Linux 2023年6月7日
    093
  • 数据库简单查询

    简单查询 语法句式如下: SELECT filed1,filed2 … filedn FROM tablename [WHERE CONDITION11] [GROUP BY …

    Linux 2023年6月7日
    0132
  • Django补充

    django配置文件相关操作 django实际上有两个配置文件 一个是提供给用户可以自定义的基本配置 from 项目名 import settings 一个是全局的系统默认的配置 …

    Linux 2023年6月7日
    0104
  • Tomcat 介绍及使用教程

    镜像下载、域名解析、时间同步请点击阿里云开源镜像站 1. Tomcat 介绍 Apache Tomcat 是由 Apache Software Foundation(ASF)开发的…

    Linux 2023年5月27日
    077
  • Spring的循环依赖

    本文简要介绍了循环依赖以及Spring解决循环依赖的过程 循环依赖是指对象之间的循环依赖,即2个或以上的对象互相持有对方,最终形成闭环。这里的对象特指单例对象。 对象之间的循环依赖…

    Linux 2023年6月8日
    0145
  • 音视频技术入门课-02 音频从采集到输出涉及哪些关键参数?

    我们平常听到的自然界的声音,比如说鸟鸣、水流,其实是一种模拟信号,声音是振动产生的一种声波,通过气态、液态、固态的物理介质传播并能被人或动物感知的波动现象。声音的频率一般会以赫兹(…

    Linux 2023年6月7日
    098
  • 多表查询练习题

    十道多表查询练习题 准备数据: 查询所有的课程的名称以及对应的任课老师姓名 查询姓李老师的个数 查询挂科超过两门(包括两门)的学生姓名和班级、查询选修了所有课程的学生姓名 post…

    Linux 2023年6月7日
    098
  • 跨平台(32bit和64bit)的 printf 格式符 %lld 输出64位的解决方式

    在 C/C++ 开发中,使用 printf 打印 64 位变量比较常用,通常在 32 位系统中使用 %lld 输出 64 位的变量,而在 64 位系统中则使用 %ld; 如果在 3…

    Linux 2023年6月7日
    090
  • PyTorch 介绍 | TENSORS

    Tensor是一种特殊的数据结构,非常类似于数组和矩阵。在PyTorch中,我们使用tensor编码模型的输入和输出,以及模型的参数。 Tensor类似于Numpy的ndarray…

    Linux 2023年6月16日
    0166
  • JuiceFS 在 Elasticsearch/ClickHouse 温冷数据存储中的实践

    企业数据越存越多,存储容量与查询性能、以及存储成本之间的矛盾对于技术团队来说是个普遍难题。这个难题在 Elasticsearch 与 ClickHouse 这两个场景中尤为突出,为…

    Linux 2023年6月14日
    0122
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球