人类认识世界是从认识世界中的一个又一个实物开始,而后再对其用语言加以描述。例如当中国人看到苹果时,便会用中文“苹果”加以描述,而用英语的一些国家则会用“apple”加以描述。python
以上说到的中文和英文都是人类认识并描述世界的一个工具,而在计算机的世界中,为了让计算机去认知世界,从而帮助人类完成更多的任务。在计算机领域中也发展了语言这个工具,从早期的机器语言到汇编语言再到如今使用范围较广的高级语言。而咱们接下来要介绍的Python则属于高级语言这一分支。app
上面说到Python是计算机世界中用来描述外部世界的,而且也说起了世界就是一个又一个实物的堆叠,描述世界其实就是去描述那一个又一个实物,人类如此,计算机也是如此。所以计算机语言开发者们为了使用计算机语言的人更好的在计算机中去描述这些实物,便在计算机语言中引入了变量这个概念,Python也不例外。简单点说,变量就是用来描述世间万物的。函数
为了在计算机书写方便,定义一变量也有必定的规则,在这里咱们仅说说Python中变量的定义规则,首先咱们先定义两个变量:工具
name = 'chenyoude' year = 2021
上述代码中咱们便定义了两个变量,从上面定义的两个变量中,咱们能够看到,变量的组成分为三个部分:编码
上面简单讲解了Python中的变量,经过字面意思,能够看到变量实际上是一个变化的量,例如,下面这个实例:翻译
year = 2021 year = year + 1 print(year) # 输出结果:2022
刚开始咱们赋予了year一个变量值为2021,当咱们对year进行加1操做时,能够发现year值变成了2022。对于上述现象咱们不难理解,由于以前说过Python中变量是用来描述世间万物的,世间万物在现实中是能够变化的,变量固然也能够随之变化。设计
可是在某个局部范围内,变量多是不会变化的,例如在2021年这一年,都只会是2021年,没有人会说2021年是2022年。若是你有丰富的开发经验,会明白变量定义出来不是存放在那里给你看的,更多的是要拿来用的。也就是说若是在2021年中的某个程序须要使用year这个变量,但这个变量是不须要进行修改的。为了防止误操做对year这个变量进行了修改,计算机语言便设计了常量这个概念,也就是说常量相对于变量是一个不会变化的量。code
在Python中,有没有常量呢?不严格的讲,实际上是有的,只是在定义常量的时候常量名必须的全大写,例如,下面这个实例:内存
YEAR = 2021 YEAR = YEAR + 1 print(YEAR) # 输出结果:2022
上面这个常量的实例使人大吃一惊,由于使用常量YEAR后和使用变量year的结果一致,也就是说常量YEAR遭到了更改。可是,稍微解释你就明白了。作用域
在Python中,虽然也和其余不少计算机语言同样拥有常量这个概念,但更多的是约定俗成的,Python并无严格的对常量进行控制,只是规定常量名必须所有大写。缘由很简单:都是常量了,你为何还要修改?
上面讲到常量就是一个不会变化的变量,严格的讲,在Python中是没有常量这个概念的。可是,在Python中又有另一种例外,那就是常量池,为了搞清楚常量池,首先咱们得弄明白Python的几个小知识,接下来一一叙说。
上面说起到Python是计算机用来描述世间万物的一种语言,因为计算机没有人脑那么强大,计算机更多的只是认识高低压电频,再经过对高低压电频的转化进而编码成咱们看到的一个又一个字符,也就是说计算机是没法直接认识利用Python写下的字符的。(此处设计计算机组成原理,很少作介绍)
也就是说,当咱们利用Python写下一个又一个字符而且交给电脑时,须要经过编码这个过程,而这个编码的过程有时候也被称为解释。解释的原理就至关于从中文转成英文,只不过此时不是须要让英文使用者看懂中文,而是让计算机可以看懂Python。
中文转成英文的时候,可能须要一个翻译员或一个翻译软件,利用Python写下的字符转化为计算机能看懂的语言一样如此,这个转化过程也须要一个外物的帮助——Python解释器。
假设咱们使用Python解释器定义了如下一个变量:
year = 2021
当咱们经过字符定义变量时,必定会好奇这些变量被Python解释器解释后到底去了哪?若是对计算机的组成熟悉的同窗,必定会清楚计算机的核心组件为:CPU、内存、外存、输入设备、输出设备。也就是说,这些字符应该存储在这些核心组件中。在这里就不卖关子了,当咱们经过字符定义变量并对其用Python解释器进行解释时,他们会以计算机能看懂的形式进入内存当中。
上面讲的对于不少非科班出身的朋友可能很难理解,在这里将它生动化。如今假设江西师范大学至关于电脑内存,每当有一批新学生进入师大时,师大都会开辟出一个新教室给这批新同窗使用,而且会给每个教室一个独一无二的教室牌号。因为把师大看做是内存,这批新同窗就能够当作是变量值,而教室牌号就是变量名。也就是说,对于师大这个大内存,每定义一个变量year=2021
,就会在这个大内存中开辟一个小空间,小空间中放变量值2021,而后大内存会给这个小空间定义一个变量名year,此时变量名year指向变量值2021。
上面说到每当Python解释器解释一个变量时,会将这个变量存放到内存中的一个小空间中,但如何知道这个小空间的具体位置呢?此处介绍Python的一个内置函数id()
,经过这个函数能够获取某一个变量所在的内存地址,例以下面这个实例:
year = 2021 print(id(year)) # 输出4499932432
对于上述师大的例子,此处再作延伸。因为那一批学生所在班级新转来了几位同窗,须要那一批学生更换更大一点教室,也就是给他们一个新的教室。那么学校应该会这样处理,首先开辟一个新的教室,而后拿下那一批学生原有教室的教室牌号更换到这个新教室,最后会清空原有教室。
在Python中,也是如此,若是到了新的一年,咱们会从新定义一个year变量,也就是year=2022
。若是这是在同一个程序中如此作,Python会沿用上述更换教室的方法,它首先会解除year和2021的链接,开辟一个新内存存放变量值2022,让year与2022链接。此时,会发现2021这个变量值只有变量值而没有变量名,所以这个没有变量名的变量值会变成Python眼中的一个垃圾变量,从而触发Python垃圾回收机制,对这个2021所在的内存空间进行回收。
为了更好地理解Python垃圾回收机制,能够看下面这个例子:
year = 2021 print(id(year)) # 输出4499932720 print(year) # 输出2021 year = 2022 print(id(year)) # 输出4499932560 print(year) # 输出2022
经过上述例子,能够看到当新定义了一个year变量时,year会与新的变量进行一个链接。固然,此处所说的垃圾回收机制只是为了引入引用计数这个概念,并非彻底正确的解释,而且上述实例还没法证实变量值2021所在内存是否被回收,下面将经过引用计数的实例会进一步说明并从新解释垃圾回收机制。
上面讲到若是某个变量值绑定着变量名,就是一个正常的变量,若是该变量值没有绑定着门牌号,这个变量就是一个垃圾变量,对于垃圾变量,Python会触发垃圾回收机制回收这个变量所占有的内存。进而能够想到,Python中一个变量名必定只能对应一个变量值。
在这里咱们就不能沿用师大这个例子了,而得引出一个新的名词——引用计数。
为了解释引用计数,咱们首先得明白在Python中,当定义了一个变量值为2021的变量时,它能够表示年份、也能够表示山的高度…也就是说一个变量名只能对应一个变量值,可是一个变量值能够对应不一样的变量名,这种设计也是比较合理的。
如今咱们引出引用计数这个概念,当相同的变量值被赋予不一样的变量名时,变量值每增长一个变量名的赋予,则该变量值的引用计数加1。因为咱们能够经过Python内置sys模块中的getrefcount()
函数获取某一个变量的引用计数(getrefcount输出值默认从3开始),能够经过下面这个例子感觉下:
import sys # 引用计数初始值为3 print(sys.getrefcount(2021)) # 输出为3 year = 2021 print(sys.getrefcount(2021)) # 输出为4 height = 2021 print(sys.getrefcount(2021)) # 输出为5 del year print(sys.getrefcount(2021)) # 输出为4
从上述代码能够看出变量值2021的引用计数因为每一次赋予新的变量名,引用计数都会增长,而当咱们利用del关键字删除变量值2021的一个变量名year时,引用计数则会减小。
为了更加严谨的表达引用计数,此处不得再也不次深刻,引用计数字面意思能够理解为引用的次数,也就是说上面的例子其实并不严谨,更严谨的讲,只有当一个变量值每一次被直接或间接引用时,引用计数才会增长,在Python中让引用计数增长共有三种方法:
具体看下述实例:
import sys # 引用计数初始值为3 print(sys.getrefcount(2021)) # 输出为3 # 变量被建立,变量值引用计数加1 year = 2021 print(sys.getrefcount(2021)) # 输出为4 # 变量被引用,变量值引用计数加1 height = year print(sys.getrefcount(2021)) # 输出为5 # 变量做为参数传入到一个函数,变量值引用计数加2 def func(year): print(sys.getrefcount(year)) func(year) # 输出为7
Python中既然有增长引用计数的方法, 也固然会减小引用计数的方法,共有如下4种:
有了getrefcount()
方法并经过引用计数,咱们就能够解开垃圾回收机制遗留的一个问题——如何判断是否触发了垃圾回收机制。每当一个变量定义,他的getrefcount输出值为3,而若是该变量值被垃圾回收机制回收,则它的getrefcount输出值回到3,能够经过下面实例验证上述猜测:
import sys print(sys.getrefcount(2021)) # 输出为3 year = 2021 print(sys.getrefcount(2021)) # 输出为4 print(id(year)) # 输出4499932720 print(year) # 输出2021 year = 2022 print(sys.getrefcount(2021)) # 输出为3 print(id(year)) # 输出4499932560 print(year) # 输出2022
经过上述实例,能够发现因为变量值2021对应的变量名被新的变量值2022引用,它的getrefcount输出值为3,引用计数变成了0,所以能够证实Python触发了垃圾回收机制。
若是对上述验证Python触发垃圾回收机制的实例深刻挖掘,会发现当把year赋给变量值2022时,变量值的2021的引用计数为0,此时触发了Python的垃圾回收机制,那么是否能够代表只有当变量值2021的引用计数为0时才能触发垃圾回收机制呢?而不是上一次说的当变量值的变量名被新的变量值被引用了才会销毁呢?由于变量值能够对应多个变量名,下面经过下述实例验证:
import sys print(sys.getrefcount(2021)) # 输出为3 year = 2021 print(sys.getrefcount(2021)) # 输出为4 height = 2021 print(sys.getrefcount(2021)) # 输出为5 year = 2022 print(sys.getrefcount(2021)) # 输出为4 del height print(sys.getrefcount(2021)) # 输出为3
经过上述实例,能够发现因为定义一个变量后,该变量对应的变量值引用计数能够不断增长,而只要引用计数不为0,那么Python就一直还在内存中保留着这个变量值而且对其引用,只有当该变量的引用计数为0时,Python才会触发垃圾回收机制对该变量值进行回收,这才是比较正确的垃圾回收机制。固然,若是深刻,Python的回收机制还有分代回收,此处不作延展,了解上述这些就足矣了解接下来说的小整数池。
在上述各个知识的打通以后,如今能够正式引入常量池这个概念。上面讲到在Python中严格的讲是没有常量这个概念的,即便你经过约定俗成的方法定义了一个常量,但这个常量也只是一个变量,也就是说只要你对这个常量作出修改,这个常量原有对应的常量值引用计数就会变成0,因为常量等同于变量,它同样会被Python垃圾回收机制回收。
可是在Python中,存在着一些例外,这些例外就是一个小整数池,顾名思义,小整数池表示的是从-5到256范围内的整数,这些整数定义出来后就是一个常量,也就是说他们的引用计数即便为0,也不会被Python的垃圾回收机制回收,能够经过下述实例验证:
import sys first_l = [] # 定义列表l存储[-5,256]中的全部整数的引用计数 add_l = [] # 定义列表add_l存储[-5,256]中的全部整数的引用计数加1后的引用计数 del_l = [] # 定义列表del_l存储[-5,256]中的全部整数的引用计数减1后的引用计数 for i in range(-5, 256): first_l.append(sys.getrefcount(i)) add = i add_l.append(sys.getrefcount(i)) del add del_l.append(sys.getrefcount(i)) first_l.sort() add_l.sort() del_l.sort() print(f'min(first_l): {min(first_l)}') # 获取[-5,256]中全部整数的最小引用计数,输出为4 print(f'min(add_l): {min(add_l)}') # 获取[-5,256]中全部整数的最小引用计数,输出为5 print(f'min(del_l): {min(del_l)}') # 获取[-5,256]中全部整数的最小引用计数,输出为4
从上述实例能够看出,[-5,256]中的整数的getrefcount默认初始值为4,也就是说即便没有对这些整数进行初始化的建立,Python早已对他们进行了引用,即便他们的引用计数为0,他们也不会也不可能被删除,由于他们从Python解释器启动开始就已经被生成。
固然,也能够经过垃圾回收机制判断小整数池中的整数是否会被垃圾回收机制回收,可用以下实例证实(因为Pycharm等解释器会一次性编译整个文件,固使用终端编辑代码):
>>> a = 5 >>> id(a) 4529334480 >>> del a >>> b = 5 >>> id(b) 4529334480 >>> >>> a = 257 >>> id(a) 4533920752 >>> del a >>> b = 257 # 消除分代回收对结果的影响 >>> del b >>> b = 257 >>> id(b) 4531031792 >>>
从上述实例中能够看出,变量值5即便被垃圾回收机制回收后,再次建立变量值为5的变量,该变量的内存地址始终无变化,即该变量未被垃圾回收机制回收,小整数池中的其余整数同理;而变量值257却已经被垃圾回收机制回收,非小整数池中的其余变量同理。
固然,还能够经过下述方法查看这些小整数池的整数的内存地址的变化,以下:
a = 256 b = int("256") print(id(a), id(b)) # 4544968752 4544968752 a = 257 b = int("257") print(id(a), id(b)) # 4548719792 4546289360 a = -5 b = int("-5") print(id(a), id(b)) # 4544960400 4544960400 a = -6 b = int("-6") print(id(a), id(b)) # 4690036912 4546289360
对于上述实例,在Python中,因为每生成一个变量便会开辟一个新的内存空间给该变量,可是上述实例代表当变量值为-5和256时,每次开辟的内存空间地址都是同样的;而当变量值不属于[-5,256]时,每次定义变量值时,内存空间的地址都是不同的。
在Python中,变量是用来描述世间万物的,变量顾名思义是变化的一个量,而在某一个局部范围内,有些量多是不会变化的,所以语言设计者在计算机中定义了常量这个概念,可是在Python中并无规定的常量,只有约定俗称的常量,也就是变量名全大写的则是常量。可是Python中有一个另外,也就是小整数池[-5,256],在这个小整数池中的整数对于Python来讲就是一个常量,由于从引用计数的打印中能够看出它在Python解释器启动的时候就已经生成并占用了一个固定的内存空间,而且不会由于引用计数变为0以后就会被Python的垃圾回收机制回收,而这些小整数池也能够称做Python的常量池。