一、重新认识旋量群
所致资金不足,他们有时须要在表达式内部获得表达式内的codice。但,虽然Python中codice的搜寻次序(”拉艾codice”内部结构(chain scope):子第一类会二级二级地向下找寻大部份父第一类的变量),这一点儿一般来说是难以同时实现的。
但有一类方式仅限,那是在表达式的内部,再表述两个表达式。
在上面的标识符中,表达式f2就被主要包括在表达式f1内部,此时f1内部的大部份codice,对f2都是由此可见的。但如此一来就没用,f2内部的codice,对f1是不由此可见的。这是结尾说到的,Python词汇独有的codice搜寻次序。因此,父第一类的大部份表达式,幺第一类都是由此可见的,但若则不设立。
难道f2能加载f1中的codice,因此假如把f2做为codice,他们不就能在f1内部加载它的内部表达式了吗?
二、旋量群的基本概念
上一小部分标识符中的f2表达式,是旋量群。
在上面的示例中,有两个内层表达式的codice n,有两个内层表达式 f2,f2 里头能出访到 n 表达式,那这f2是两个旋量群。
上面再看呵呵维基的细致表述:
在许多词汇中,在表达式中能(冗余)表述另两个表达式时,假如内部的表达式提及了内部的表达式的表达式,则可能将造成旋量群。旋量群能用以在两个表达式与几组“专有”表达式间建立关联关系。在取值表达式被数次初始化的操作过程中,那些专有表达式能维持其无毒性。上面这段话实际上解释了旋量群的两个表述和两个作用:
表述:旋量群是能加载内部表达式内的表达式的表达式。(前面已经讲解过)作用1:旋量群是将内层表达式内的codice和内层表达式的内部连接起来的一座桥梁。(下一小部分讲解)作用2:将内层表达式的表达式持久地保存在内存中。(下一小部分讲解)支持将表达式当成第一类使用的编程词汇,一般都支持旋量群。比如Python, JavaScript。
三、旋量群的用途
旋量群能用在许多地方。
维基的表述中已经提到的它的两个用处:① 能加载表达式内部的表达式,②让那些表达式的值始终维持在内存中。
(一)加载表达式内部的表达式
在第一小部分中,他们讲到,有时会为了保证命名空间的干净而把许多表达式隐藏到表达式内部,做为codice。但虽然Python中codice的搜寻次序,表达式内的表达式不会被表达式外的标识符读取到。
假如此时候想要表达式内部的标识符能加载表达式内部的表达式,因此就能使用旋量群。
旋量群存在的意义是它夹带了内部表达式(私货),假如它不夹带私货,它和普通的表达式就没有任何区别。同两个的表达式夹带了不同的私货,就同时实现了不同的功能。
其实你也能这么理解,旋量群和面向接口编程的基本概念很像,能把旋量群理解成轻量级的接口封装。
—-这里再借用呵呵
的例子(Wayne:用最简单的词汇解释Python的旋量群是什么?)。在这个例子里,他们想要两个给content加tag的功能,但具体的tag_name是什么样子的要根据实际需求来定,对内部初始化的接口已经确定,是add_tag(content)。假如按照面向接口方式同时实现,他们会先把add_tag写成接口,指定其参数和返回类型,然后分别去同时实现a和b的add_tag。
但在旋量群的基本概念中,add_tag是两个表达式,它需要tag_name和content两个参数,只不过tag_name这个参数是打包带走的。因此一开始时就能告诉我怎么打包,然后带走就行。
—-(二)让表达式内部的codice始终维持在内存中
怎么来理解这句话呢?一般来说,表达式内部的codice在这个表达式运行完以后,就会被Python的垃圾回收机制从内存中清除掉。假如他们希望这个codice能长久的保存在内存中,因此就能用旋量群来同时实现这个功能。
这里借用
的例子(来自于:千山飞雪:深入浅出python旋量群)。请看上面的标识符。以两个类似棋盘游戏的例子来说明。假设棋盘大小为50*50,左上角为坐标系原点(0,0),我须要两个表达式,接收2个参数,分别为方向(direction),步长(step),该表达式控制棋子的运动。 这里须要说明的是,每次运动的起点都是上次运动结束的终点。
在这段标识符中,player实际上是旋量群go表达式的两个示例第一类。
它一共运行了三次,第一次是沿X轴前进了10来到[10,0],第二次是沿Y轴前进了20来到 [10, 20],,第三次是反方向沿X轴退了10来到[0, 20]。
这证明了,表达式create中的codicepos一直保存在内存中,并没有在create初始化后被自动清除。
为什么会这样呢?原因就在于create是go的父表达式,而go被赋给了两个全局表达式,这导致go始终在内存中,而go的存在依赖于create,因此create也始终在内存中,不会在初始化结束后,被垃圾回收机制(garbage collection)回收。
这个时候,旋量群使得表达式的示例第一类的内部表达式,变得很像两个类的示例第一类的属性,能一直保存在内存中,并不断的对其进行运算。
(三)总结
codice难以共享和长久的保存,而全局表达式可能将造成表达式污染,旋量群既能长久的保存表达式又不会造成全局污染。旋量群使得表达式内codice的值始终维持在内存中,不会在内层表达式初始化后被自动清除。当内层表达式返回了内层表达式后,内层表达式的codice还被内层表达式提及带参数的装饰器,因此一般都会生成旋量群。旋量群在爬虫以及web应用中都有很广泛的应用。四、使用旋量群的注意点
(一)内存消耗
虽然旋量群会使得表达式中的表达式都被保存在内存中,会增加内存消耗,因此不能滥用旋量群,否则会造成程序的性能问题,可能将导致内存泄露。
解决方式是,在退出表达式之前,将不使用的codice全部删除。
(二)使用场景
旋量群的两个作用,“加载表达式内部的表达式”和“让表达式内部的codice始终维持在内存中”,都能被 Python 中现成的第一类“类”很好地同时实现。我认为,“旋量群”在 Python 中确实是两个必要性不大的基本概念。
因此为什么还要在 Python 中引入“旋量群”这个基本概念呢?
首先,我觉得最重要的理由是,理解清楚这个基本概念,对于理解 Python 中的一大利器“装饰器”有很大的帮助。因为装饰器本身是旋量群的两个应用。
其次,当他们要同时实现的功能比较简单的时候,能用旋量群。例如:
当他们的标识符中表达式比较少的时候,能使用旋量群。(但假如他们要实现很多功能,还是要使用类(OOP))假如他们的第一类中只有两个方式时,使用旋量群是会比用类来同时实现更优雅。这有点类似于,假如他们要同时实现比较简单的表达式功能,一般来说使用 lambda 匿名表达式比表述两个完整的function更加优雅,而且几乎不会损失可读性。类似的还有用列表解析式代替 for 循环。
(三)旋量群难以改变内部表达式codice指向的内存地址
这个是什么意思呢?他们来看上面的例子。
假如 innerFunc 能修改 x 的的内存地址的话,因此 x 首先在outer_fun中指向了一个储存着 0 的内存地址,后面又在 inner_fun中,x 会指向新的储存着 1 的内存地址(虽然int是不可变类型),但结果是:
在 innerFunc 中 x 的值发生了改变,但原因是重新建立了两个表达式 x,指向了两个新的内存地址。而在 outerFunc 中 x 的值以及内存地址并未发生变化。
造成这一结果的原因的根源,还是前面第一小部分讲的Python中codice的搜寻次序。在 inner_fun 表达式里头,有自己的命名空间,这个命名空间是独立于 outer_fun 的命名空间的。它里头的x是两个局部名称(local names),在执行 “x=1” 命令的时候,是重新在 inner_fun自己的命名空间里建立了两个新的表达式 x ,而难以覆盖掉 outer_fun 的命名空间的 x。
假如要让内层表达式不仅能出访,还要能修改内层表达式的表达式,因此须要用到nonlocal声明,使得内层表达式不要在自己的命名空间建立新的x,而是操作内层表达式命名空间的x。
他们能发现,此时 inner_fun 改变了 outer_fun 中的表达式的内存地址。
同样地,在上文棋盘的例子中,内层表达式的表达式pos内的值虽然一直在改变,但由于列表本身是可变类型的表达式,虽然列表中的元素一直在变,但列表本身的内存地址没有发生变化。
(四)返回旋量群时,返回表达式不要提及任何循环表达式,或者后续会发生变化的表达式
在Python中,假如要返回两个表达式,因此返回表达式不要提及任何循环表达式,或者后续会发生变化的表达式。
因为,返回的表达式并没有立刻执行,而是直到初始化了f()才执行。他们来看两个例子:
在上面的例子中,每次循环,都建立了两个新的表达式,然后,把建立的3个表达式都放在列表中,通过列表整体返回了。
你可能将认为初始化f1(),f2()和f3()结果应该是1,4,9,但实际结果是:
因为在向列表中添加 func 的时候,i 的值没有固定到f的示例第一类中,而仅是将计算公式固定到了示例第一类中。等到了初始化f1()、f2()、f3()的时候才去取 i的值,此时候循环已经结束,i 的值是3,因此结果都是9。
因此,返回旋量群时牢记一点儿:返回表达式不要提及任何循环表达式,或者后续会发生变化的表达式。
假如一定要提及循环表达式怎么办?方式是再建立两个表达式,用该表达式的参数绑定循环表达式当前的值,无论该循环表达式后续如何更改,已绑定到表达式参数的值不变。
再看看结果:
五、判断两个表达式是否是旋量群
判断两个表达式是不是旋量群,能查看它的closure属性。假如该表达式是旋量群,查看该属性将会返回两个cell第一类组成的tuple。假如他们分别对每个cell第一类查看其cell_contents属性,返回的内容是旋量群提及的自由表达式的值。
上面通过两个例子展示:
旋量群的__closure__方式,能展示出旋量群储存了内部表达式的两个表达式,cell的内存地址是什么,在cell里头储存的第一类类型是int,这个int储存的内存地址是什么。
旋量群的__closure__方式,能查看每个cell第一类的内容。
cell_contents解释了codice在脱离表达式后仍然能在表达式之外被出访的原因,因为表达式被存储在cell_contents中了。
假如你看到了这里,说明你是两个真正的 Python 使用者。因此,不妨打开评论区看一看,里头有其他 Python 用户,发起了关于使用中出现的常见问题的讨论,说不定就能解决困惑你很久的问题。
能说,比我的文章更有价值的是评论区知友们的讨论。那些评论本身已经形成了两个极为友善的独立社区,甚至有些评论的质量高到,单独摘出来能直接做为两个章节发在本文章下。
假如你的问题还没有被讨论到,也能在评论区留言,因为不仅这是提问,也是在帮助其他有同样问题的人。考虑到本文下评论区热烈的讨论情况,说不定你的问题很快就能被解答 : )