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)

大家都在看

  • [Linux]LVS(Linux Virtual Server)

    LVS(Linux Virtual Server) LVS(Linux Virtual Server) 什么是LVS? 为什么要用LVS? LVS的组成及作用 LVS相关术语 LV…

    Linux 2023年6月13日
    0103
  • 【前端】【探究】HTML input类型为file时如何实现自定义文本以更好的美化

    想到英语四级考了两次都没过,我觉得要多使用英文,所以本文使用英文书写。 本文讲述了遇到的问题,解决的思路,并讲述了解决方案,也许对你会有帮助。 Problem descriptio…

    Linux 2023年6月14日
    0154
  • shell handle

    !/bin/bash qinrui set -e commitId =” repoPath =” x1 =” if [-f changes15….

    Linux 2023年5月28日
    0119
  • cobbler离线安装脚本

    cobbler离线安装脚本 配套离线安装cobbler教程 需要手动上传镜像包和离线安装包 #!/bin/bash #上传cobbler离线安装包和centos镜像包 解压离线安装…

    Linux 2023年6月7日
    0116
  • linux中软件的安装方式

    linux中软件的安装方式 四种方式 ​ 源码编译安装 ​ rpm安装 ​ yum安装 解压、配置(hadoop、hive等) 1.源码编译安装 1.为了编译nginx源码 yum…

    Linux 2023年6月11日
    0106
  • U盘如何安装centos7系统?U盘安装centos7详细安装图解教程

    一般来说,无论是Windows还是linux的IOS系统镜像,我们都可以使用UltraIOS(软碟通)这款软件制作U盘启动工具,不过考虑到不少小白依然不会如何操作,所以今天考虑写一…

    Linux 2023年6月8日
    0118
  • DDL(操作表和数据库)

    数据定义语言,用来定义数据库对象:数据库,表,列等 readme 注意本博客中的 操作数据库 查询 show databases; 创建 创建数据库 create database…

    Linux 2023年6月7日
    091
  • sql注入

    一.原理 SQL注入攻击指的是通过构建特殊的输入作为参数传入Web应用程序,而这些输入大都是SQL语法里的一些组合,通过执行SQL语句进而执行攻击者所要的操作,其主要原因是程序没有…

    Linux 2023年6月6日
    0107
  • SSH 完全教程 2

    SSH 默认采用密码登录,这种方法有很多缺点,简单的密码不安全,复杂的密码不容易记忆,每次手动输入也很麻烦。密钥登录是b比密码登录更好的解决方案。 密钥是什么 密钥(key)是一个…

    Linux 2023年6月7日
    080
  • go redis锁

    redis经常用作分布式锁,这里记录一个简单的锁代码如下: package main import ( "crypto/rand" "encoding…

    Linux 2023年5月28日
    0133
  • 服务管理与通信,基础原理分析

    涉及轻微的源码展示,可放心参考; 一、基础简介 服务注册发现是微服务架构中最基础的能力,下面将从源码层面分析实现逻辑和原理,在这之前要先来看下依赖工程的基础结构,涉及如下几个核心组…

    Linux 2023年6月14日
    0158
  • BKT的胡测题解:第一套第一题parts

    博客园 :当前访问的博文已被密码保护 请输入阅读密码: Original: https://www.cnblogs.com/Grharris/p/11530239.htmlAuth…

    Linux 2023年6月6日
    0112
  • Spring 4 集成 redis 实现缓存 一

    随着Web项目的复杂程度逐渐增加,可能会涉及诸如高并发、海量数据查询的的业务场景也逐渐增多;若频繁的操作数据库,会触发数据库的I/O瓶颈,因此需要加入缓存,尽量减少直接操作数据库的…

    Linux 2023年6月14日
    0103
  • 双绞线

    双绞线简介 双绞线(twisted pair,TP)是一种综合布线工程中最常用的传输介质,双绞线一般由两根22~26号绝缘铜导线相互缠绕而成,在一个电缆套管里的,不同线对具有不同的…

    Linux 2023年6月7日
    0102
  • 5.5 Vim移动光标命令汇总

    Vim 文本编辑器中,最简单的移动光标的方式是使用方向键,但这种方式的效率太低,更高效的方式使用快捷键。 Vim 移动光标常用的快捷键及其功能如下面各表所示,需要注意的是,表中所有…

    Linux 2023年6月7日
    0114
  • 005 Linux 命令三剑客之-sed

    grep:数据查找定位 awk:数据切片,数据格式化,功能最复杂 *sed:数据修改 三剑客各有所长,就从锅碗瓢盆一一开始吧! [En] The three swordsmen h…

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