python玄学系列(第二集):这大概是最全面最通俗易懂的python闭包了

​写在前面

鉴于天下苦 “python闭包” 久矣,今天,就由我给你们详细解释一下,百分百包教包会,童叟无欺,若看完还是不会,那你也拿我没什么办法!!!

正文

要想明白闭包,嵌套函数是迈不过去了,所以,我先讲解一下这个知点。

先来看看百度百科对嵌套函数的定义

1、嵌套函数,就是指在某些情况下,您可能需要将某函数作为另一函数的参数使用,这一函数就是嵌套函数。

2、嵌套函数(Nested function)是在另一个函数(即:封闭函数)中定义的函数

对于嵌套函数,大致有三类用途:

数据隐藏
DRY 原则
闭包

1、封装 - 数据隐藏

保护一部分代码不受函数外部变化的影响,从全局作用域中隐藏起来
示例:

def a(n):
    def b(m):
       return m+1
    add_one = b(n)
print(add_one)

调用运行结果:

>>>a(1)
2
>>>b(2)
NameError: name 'b' is not defined

可以看到,外部无法访问函数b,这就起到了数据隐藏的效果。

打个通俗的比方,龟儿子和龟爸爸玩捉迷藏,龟儿子躲进柜子,龟爸爸在外面是看不到它的龟儿子的,唯一的办法就是,打开柜子,大叫一声:“龟儿子,给我滚粗来”,然后龟儿子就真的就像个龟儿子一样慢吞吞的爬出来了。

2、DRY 原则

DRY(Don’t Repeat Yourself)- 是指在程序设计以及计算中避免重复代码,因为这样会降低灵活性、简洁性,并且有可能导致代码之间的矛盾。

DRY 更多的是一种架构设计思想,在软件开发过程中的万事万物均可能复,大到标准、框架、开发流程;中到组件、接口;小到功能、代码均纯存在自我重复。而 DRY 提倡的就是在软件开发过程中应消除所有这些自我重复。

来看一个例子:

例如,处理文件时,需要支持打开的文件对象和文件名,传统方式是采用两个函数分别实现。

(1)文件对象的方式读取
def read_file_object(f):
    for line in f:
      print(line)
​
​
​
​
f = open('a.txt', 'r', encoding='utf8')
read_file_object(f)

结果为:

我是蔡徐坤
(2)以文件名的方式读取
def read_file_name(file_name):
  if isinstance(file_name, str):
    with open(file_name, 'r', encoding='utf8') as f:
      for line in f:
        print(line)
​
​
​
​
read_file_name('a.txt')

结果为:

我是蔡徐坤

这种方式显然不好,不同的读取方式需要调用不同的函数,并且读取文件的代码重复了,显得冗余啰嗦,来看看嵌套函数怎么优雅的实现这个功能吧:

def read_file(file):
  def inner(file_):
    for line in file_:
      print(line, end='')
​
​
  if isinstance(file, str):
    with open(file, 'r', encoding='utf8') as f:
      inner(f)
  else:
    inner(file)
​
​
​
​
read_file("a.txt")

看吧,一个嵌套函数实现了所有功能,是不是很pythonic,代码不仅优雅,并且简单,适用行强,也符合了DRY原则

3、闭包

今天的主角就这样闪亮登场了(前排鼓掌)

我们先看看维基百科中对闭包的解释:

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

看看就行了,刚学闭包别想着能看懂它,因为它说的就不是人话。下面就让我一步一步把这句话翻译成人话给你听(手动狗头)

公主号: “暮秋梵星”    后台有大量资料给你

开始之前,为了避免像我一样的数学专业生引起误会,区别一下数学中的闭包,同样,来看一下数学中的闭包的概念:

数学中,若对某个集合的成员进行一种运算,生成的仍然是这个集合的成员,则该集合被称为在这个运算下闭合。当一个集合 S 在某个运算下不闭合的时候,我们通常可以找到包含 S 的最小的闭合集合。这个最小闭合集合被称为 S 的(关于这个运算的)闭包。

我知道你看不懂,放出来也不是给你看的,知道他俩不是一个东西就行了,如果您非要问我这两个闭包有什么关系,很简单,就像java和javascript的关系。什么?你还是看不懂?给你一张图片感受一下

在这里插入图片描述

下面正式开始闭包的讲解,不然你们要说我在开始忽悠了

(1)作用域

在python中,一个py文件称之为一个模块,在模块最外层定义的变量称之为全局变量,作用域是整个模块,定义在函数内部的变量称之为菊部变量,不对,是局部变量(别想歪了,好好听课),它的作用域是整个函数。

num = 1 # 全局作用域变量
def a():
  print(num)

这里的num是全局变量,函数外部是能访问到的

def b():
  num = 2
print(num)

这里的num是局部变量,函数外部是不能访问的,如果你运行程序,会报错:
NameError: name 'num' is not defined

(2)闭包的本质

先让我们来看一下这段代码:

def outer():
  arg_1 = "我是外部函数的变量"
​
​
  def inner():
    print(arg_1)
  inner()
​
​
outer()

运行一下:

我是外部函数的变量

这里的arg_1是outer函数的局部变量,当我们调用outer函数的时候,内部函数inner也会被调用,arg_1的值被访问到并且打印出来,而当outer函数被调用结束过后,arg_1变量所占用的内存空间会被释放,就相当于从这个世界消失了,任何地方想要访问都不可能。这还不是闭包,但是已经离闭包很近了。

没看懂?别急,继续看下去,你就明白了。

现在,我们把上面的代码的某一行变一下:

def outer():
  arg_1 = "我是外部函数的变量"
​
​
  def inner():
    print(arg_1)
  return inner
​
​
a = outer()
a()

结果为:

我是外部函数的变量

仔细看看有什么区别,你会发现,我们把inner函数的引用,作为outer函数的返回值了,意思就是说,outer函数的返回值也是一个函数,这个函数就是内部函数inner。这,就是闭包。

看到这里,估计你会骂我,说的是什么屁话,这有个锤子区别啊!

安静!让我慢慢解答你的疑点

你仔细看一下代码,会发现一个很奇怪的现象,a = outer()这行代码已经调用了outer函数,也就是说,outer函数已经执行完毕了,那是不是arg_1变量也应该随之消失呢,可是,当我执行a()这句代码时,发现还能打印出arg_1变量的值,这是违反常理的。这里面的关键就在于inner函数被作为返回值了,并且在inner函数中引用了引用了arg_1变量,把变量值inner函数包在了一起,也就是我们所说的闭包,这样,arg_1变量就脱离了原本创建它的函数(outer函数)而存在。

好了,这就是闭包的本质,你应该能看懂了吧。

但这样你又会问了,这个闭包有球用啊!

大有用处,有了闭包,我们可以不使用全局变量,要知道,在任何一门语言中,全局变量都是被谨慎使用了,稍不注意,就会出问题。还有一个很大的用处,把一些数据和函数联系起来,这大大简化了代码,也提升了可读性。比如, 你想要实现这样的功能,可以用类来实现:

class Test():
  def __init__(self, a):
    self.a = a
​
​
  def add(self, b):
    print(self.a + b)
​
​
test = Test(1)
test.add(2)
test.add(3)
            

结果为:

3
4

有木有感觉一个简单的功能还得用类来实现,很麻烦,看着也不舒服,看看闭包怎么不用类实现的:

def outer(a):
  def inner(b):
    print(a+b)
  return inner
​
​
test = outer(1)
test(2)
test(3)

看吧,是不是很简单呢!

这里,我再深入剖析一下闭包中,外部函数里定义的变量为什么能脱离外部函数而存在。

其实所有的函数,都有一个closure属性,如果这个函数是一个闭包的话,那么它返回的是一个由 cell 对象 组成的元组对象。cell 对象的cell_contents 属性就是闭包中的自由变量。

>>>test.__closure__
(<cell at 0x000001C388850A68: int object at 0x0000000074E96C40>,)
>>>test.__closure__[0].cell_contents
1

这下看明白了吧,其实也就是python把这个变量存起来了。

回过头来,我们再看一下维基百科对闭包的解释,就so easy了。

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

闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。

归纳一下,闭包必须满足的三个条件:

1、需要有一个内嵌函数(也就是上面例子中的inner函数)

2、内嵌函数需要引用定义在外部函数中变量(自由变量)

3、内嵌函数需要被返回(这一点最重要,是区别普通嵌套函数与闭包的本质)

好了,今天的内容就全部结束了,其实闭包还有一个最重要的用途,那就是“装饰器”,这一点估计得再写一篇文章,大家敬请期待!!!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • 部分细节自己改了点,也加了点自己例子,基本上属于转载。转载出处:https://my.oschina.net/le...
    洛克黄瓜阅读 1,970评论 0 3
  • http://blog.csdn.net/ablo_zhou/article/details/5471952 Py...
    王江涛_6000阅读 325评论 0 1
  • 1、引言 最近在刷leetcode题的时候,遇到一个求最长回文子串的题目,于是,我写了如下的代码: 哎呀,写了两个...
    文哥的学习日记阅读 14,302评论 6 32
  • 我与孟达认识第六年,说的话也许比前五年加起来都多。为何会产生这种局面尚不清楚,也许是两人都意识到七年之痒即将到来,...
    春飞_阅读 136评论 0 1
  • 概念:师徒制 师徒制,就是所谓的“传帮带”。 这个制度,其实在农耕时代,工匠文化盛行时,在中国就非常普遍。只是在当...
    洋_葱头阅读 932评论 0 1