Python深入:01内存管理
在Python中,一切都是指针。
一:对象三特性
所有的Python对象都有三个特性:身份,类型和值。
身份:每一个对象都有一个唯一的身份标识,任何对象的身份标识可以使用内建函数id()来得到。它可以被认为是该对象的内存地址。
类型:对象的类型决定了该对象可以保存什么类型的值,可以进行什么样的操作,以及遵循什么样的规则。可以用内建函数type()查看Python对象的类型。
值:对象表示的数据项。
上面三个特性在对象创建的时候就被赋值,除了值之外,其它两个特性都是只读的。如果对象支持更新操作,那么它的值就可以改变,否则它的值也是只读的。对象的值是否可以更改被称为对象的可改变性(mutability)。
二:可变对象和不可变对象
一种对对象进行分类的方式是:可变对象和不可变对象。
可变对象允许他们的值被更新,而不可变对象则不允许他们的值被更改。下表列出了支持更新和不支持更新的类型:
数值和字符串对象是不可改变的。尽管通过给数字对象(重新)赋值,可以“更新”一个数值对象。之所以给更新这两个字加上引号,是因为实际上并没有更新该对象的原始数值。
因为数值对象是不可改变对象。Python的对象模型与常规对象模型有些不同。你所认为的更新实际上是生成了一个新的数值对象,并得到它的引用。
在Python中,变量像一个指针指向装变量值的盒子。
对不可改变类型来说,无法改变盒子的内容,但可以将指针指向一个新盒子。每次将另外的数字赋给变量的时候,实际上创建了一个新的对象并把它赋给变量.(不仅仅是数字,对于所有的不可变类型,都是这么回事)。见下例:
x = 'Pythonnumbers and strings'
x = 'are immutable?!? What gives?'
i = 0
i = i + 1
上面的例子中,事实上是一个新对象被创建,然后它取代了旧对象。就是这样。
新创建的对象被关联到原来的变量名,旧对象被丢弃,垃圾回收器会在适当的时机回收这些对象。可以通过内建函数id()来确认对象的身份在两次赋值前后发生了变化。下面在上面的例子里加上id()调用,就会清楚的看到对象实际上已经被替换了:
>>> x = 'Python numbers andstrings'
>>> id(x)
>>> x = 'are immutable?!? Whatgives?'
>>> id(x)
>>> a=1
>>> id(a)
>>> a = a + 1
>>> id(a)
可变对象,比如列表,可以被修改而无须替换原始对象,看下面的例子:
>>> aList = ['ammonia', 83, 85,'lady']
>>> id(aList)
>>> id(aList[2])
>>> id(aList[3])
>>> aList[2] = aList[2] + 1
>>> aList[3] = 'stereo'
>>> aList
['ammonia', 83, 86, 'stereo']
>>> id(aList)
>>> id(aList[2])
>>> id(aList[3])
注意列表的值不论怎么改变,列表的 ID 始终保持不变。但是列表中的元素的ID却发生了变化。
三:变量
在Python中,无需显式变量声明语句,变量在第一次被赋值时自动声明。和其他大多数语言一样,变量只有被创建和赋值后才能被使用。
Python中不但变量名无需事先声明,而且也无需类型声明。对象的类型和内存占用都是运行时确定的。尽管代码被编译成字节码,Python仍然是一种解释型语言。在创建----也就是赋值时,解释器会根据语法和右侧的操作数来决定新对象的类型。
在对象创建后,一个该对象的引用会被赋值给左侧的变量。
四:引用
在Python语言中,赋值并不是直接将一个值赋给一个变量,对象是通过引用传递的。在赋值时,不管这个对象是新创建的,还是一个已经存在的,都是将该对象的引用(并不是值)赋值给变量。
要保持追踪内存中的对象,Python使用了引用计数这一简单技术。也就是说Python内部记录着所有使用中的对象各有多少引用。
当对象被创建时,引用计数置为1,当这个对象不再需要时,也就是这个对象的引用计数变为0时,它被垃圾回收。
1:增加引用计数
当对象被创建并(将其引用)赋值给变量时,该对象的引用计数就被设置为1。
当同一个对象(的引用)又被赋值给其它变量时,或作为参数传递给函数,方法或类实例时,
或者被赋值为一个窗口对象的成员时,该对象的一个新的引用就被创建,该对象的引用计数自动加1。
以下声明:
x = 3.14
y = x
语句 x = 3.14 创建了一个浮点数对象并将其引用赋值给x。 x是第一个引用,因此,该对象的引用计数被设置为1。语句
y=x 创建了一个指向同一对象的别名y。见下图:
事实上并没有为Y创建一个新对象,而是该对象的引用计数增加了1 次(变成了2)。这是对象引用计数增加的方式之一。
还有一些其它的方式也能增加对象的引用计数,比如该对象作为参数被函数调用或这个对象被加入到某个容器对象当中时,比如下面的例子:
>>> x=3.14
>>> y=x
>>> id(x)
>>> id(y)
>>> def printid(a):
... print id(a)
...
>>> printid(x)
>>> atuple=(1, x, 2)
>>> id(atuple[1])
>>> alist=[1,x,2]
>>> id(alist[1])
>>> alist = [1,2,3]
>>> blist = alist
>>> alist.append(4)
>>> blist
[1, 2, 3, 4]
2:del 语句
该语句会删除对象的一个引用,例如,在上例中执行del
y 会产生两个结果:从现在的名字空间中删除 y; x的引用计数减一
引申一步,执行 del x 会删除该对象的最后一个引用,也就是该对象的引用计数会减为0,这会导致该对象从此“无法访问”或“无法抵达”。 从此刻起,该对象就成为垃圾回收机制的回收对象。
注意任何追踪或调试程序会给一个对象增加一个额外的引用,这会推迟该对象被回收的时间。
3:减少引用计数
当对象的引用被销毁时,引用计数会减小。最明显的例子就是当引用离开其作用范围时,这种情况最经常出现在函数运行结束时,所有局部变量都被自动销毁,对象的引用计数也就随之减少。
当变量被赋值给另外一个对象时,原对象的引用计数也会自动减1:
foo = 'xyz'
bar = foo
foo = 123
当字符串对象"xyz"被创建并赋值给foo时,它的引用计数是1. 当增加了一个别名 bar时,引用计数变成了2.
不过当foo被重新赋值给整数对象123 时,xyz 对象的引用计数自动减1,又重新变成了1。
其它造成对象的引用计数减少的方式包括:使用 del 语句删除一个变量,或者当一个对象被移出一个窗口对象时,或该容器对象本身的引用计数变成了0
时。
4:垃圾回收
不再被使用的内存会被一种称为垃圾收集的机制释放。虽然解释器跟踪对象的引用计数,但垃圾收集器负责释放内存。垃圾收集器是一块独立代码,它用来寻找引用计数为0的对象。它也负责检查那些虽然引用计数大于0
但也应该被销毁的对象。
5:赋值
对象可以被赋值到另一个变量(通过引用)。因为每个变量都指向同一个(共享的)数据对象,只要任何一个引用发生改变,该对象的其它引用也会随之改变。
将变量名看作对象的一个指针。来看以下三个例子:
a: foo1 = foo2 = 4.3
当从值的观点看这条语句时,它表现的只是一个多重赋值,将4.3 这个值赋给了foo1和foo2 这两个变量。
这当然是对的,不过它还有另一层含义。
事实是一个值为4.3的数字对象被创建,然后这个对象的引用被赋值给foo1 和foo2, 结果就是 foo1 和foo2 指向同一个对象。如下图:
b:
foo1 = 4.3
foo2 = foo1
这个例子非常类似上一个,一个值为4.3 的数值对象被创建,然后赋给一个变量,当执行foo2 = foo1 时,foo2 被指向foo1 所指向的同一个对象,这是因为Python通过传递引用来处理对象。foo2
就成为原始值4.3 的一个新的引用。 这样foo1 和foo2 就都指向了同一个对象。示意图也和上图一样。
c:
foo1 = 4.3
foo2 = 4.3
(foo2 = 1.3 + 3.0)
这个例子有所不同。首先一个数字对象被创建,然后赋值给foo1.
然后第二个数值对象被创建并赋值给foo2. 尽管两个对象保存的是同样大小的值,但事实上系统中保存的都是两个独立的对象,其中foo1 是第一个对象的引用,foo2 则是第二个对象的引用。如下图:
对象就象一个装着内容的盒子。当一个对象被赋值到一个变量,就象在这个盒子上贴了一个标签,表示创建了一个引用。每当这个对象有了一个新的引用,就会在盒子上新贴一张标签。当一个引用被销毁时,这个标签就会被撕掉。当所有的标签都被撕掉时,这个盒子就会被回收。
这里的标签数,实际上就是引用计数。每个对象都天生具有一个计数器,记录它自己的引用次数。这个数目表示有多少个变量指向该对象。
Python提供了is
和is not运算符来测试两个变量是否指向同一个对象。象下面这样执行一个测试:
a is b
这个表达式等价于下面的表达式
id(a) == id(b)
例子如下:
>>> a = [ 5, 'hat', -9.3]
>>> b = a
>>> a is b
True
>>> a is not b
False
>>>
>>> b = 2.5e-5
>>> b
2.5e-005
>>> a
[5, 'hat', -9.3]
>>> a is b
False
>>> a is not b
True
在上面的例子中,注意到我们使用的是浮点数而不是整数。为什么这样?
整数对象和字符串对象是不可变对象,所以Python会很高效的缓存它们。这会造成我们认为Python应该创建新对象时,它却没有创建新对象的假象。看下面的例子:
>>> a = 1
>>> id(a)
>>> b = 1
>>> id(b)
>>>
>>> c = 1.0
>>> id(c)
8651220
>>> d = 1.0
>>> id(d)
8651204
在上面的例子中,a 和 b 指向了相同的整数对象,但是c 和 d 并没有指向相同的浮点数对象。
Python仅缓存简单整数和短字符串,因为它认为在Python应用程序中这些小整数会经常被用到。当我们在写作本书的时候,Python缓存的整数范围是(-1,
100),不过这个范围是会改变的,所以请不要在你的应用程序使用这个特性。
# True
a = 1
b = 1
print(a is b)
# True
a = "good"
b = "good"
print(a is b)
# False
a = "very good morning"
b = "very good morning"
print(a is b)
# False
a = []
b = []
print(a is b)
参考:
《Python核心编程》
Python深入:01内存管理的更多相关文章
- Python变量与内存管理
Python变量与内存管理 –与C语言中的变量做对比,更好的理解Python的变量. 变量 变量在C语言中 全局变量:其存放在内存的静态变量区中. 局部变量:代码块中存放在内存的代码区当中,当被调 ...
- python变量的内存管理
python变量的内存管理 一.变量存在了哪里? 先让我们来看一段代码: height = 100 # 定义变量 # print(100) # print会自动帮你创建一个变量100,打印完之后,马上 ...
- Python 中的内存管理
Python 中一切皆对象,这些对象的内存都是在运行时动态地在堆中进行分配的,就连 Python 虚拟机使用的栈也是在堆上模拟的.既然一切皆对象,那么在 Python 程序运行过程中对象的创建和释放就 ...
- python中的内存管理
不像大多数编译型语言,变量必须在使用之前声明名字和类型,在python中,变量在第一次被赋值时自动声明.在变量创建时,python解释器会根据语法和右侧的操作数来决定新对象的类型,在对象创建后,一个该 ...
- python如何进行内存管理
作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 语言的内存管理是语言设计的一个重要方面.它是决定语言性能的重要因素.无论是C语言的 ...
- Python入门 值内存管理与所有的关键字
值内存管理 Python采用的是基于值得内存管理方式,如果为不同变量赋值为相同值,这个值在内存中只有一份,多个变量指向同一块内存地址. id(x) : 用于返回变量所指值的内存地址 x = 3 pri ...
- Python中的内存管理机制
Python是如何进行内存管理的 python引用了一个内存池(memory pool)机制,即pymalloc机制,用于管理对小块内存的申请和释放 1.介绍 python和其他高级语言一样,会进行自 ...
- python学习Day9 内存管理
复习 :文件处理 1. 操作文件的三步骤:-- 打开文件:此时该文件在硬盘的空间被操作系统持有 | 文件对象被应用程序持用 -- 操作文件:读写操作 -- 释放文件:释放操作系统对文件在硬盘间的持有 ...
- python的变量内存管理
一.变量的引用机制 当你在python中定义一个值,如x = 500时,python会在内存中开辟一个小地方用于存储数值. x = 500 #定义一个变量 print(id(x)) #打印该变量的内存 ...
随机推荐
- GIT → 11:Git 工作流与实战演练
GIT → 11:Git 工作流与实战演练
- matplotlib无法显示中文
import matplotlib as mpl mpl.rcParams['font.sans-serif'] = ['KaiTi']mpl.rcParams['font.serif'] = ['K ...
- 优化SQL之最快等价SQL
SQL优化工具Tosska SQL Tuning Expert for Oracle,帮助SQL开发人员解决SQL性能问题. 本工具主要创始人Richard To, 资深ITPUB元老,从1996年开 ...
- linux挂载点 和 文件系统$ mount$ cat /etc/fstab$ vgs$ pvs$ lvs$ df -h$ lsof +D / /* beware not to kill your box */
$ mount$ cat /etc/fstab$ vgs$ pvs$ lvs$ df -h$ lsof +D / /* beware not to kill your box */ 一共挂载了多少文件 ...
- 微信网页授权demo1
要授权首先要网页域名授权 然后就index.php代码如下 <?php require_once("./function.php"); $url = 'http://'.$_ ...
- DesktopLayer.exe专杀
这两天发现电脑卡慢. 同事电脑发现病毒,而后装上杀软后(一直在裸奔~~~),发现自己电脑也存在. DesktopLayer.exe 会有以下几个行为: 第一,会在C:\Program Files (x ...
- 数据库---JDBC的解析
一.JDBC是什么? JDBC:Java Database Connectivity(Java数据库连接池).指定了统一的访问各种关系型数据库的标准接口-----桥梁作用. 功能:[与数据库建立连接 ...
- Duplicate a whole line in Vim
yy or Y to copy the line or dd to delete (cutting) the line then p to paste the copied or deleted te ...
- CMake学习笔记二
CMake预定义变量 PROJECT_SOURCE_DIR 工程的根目录 PROJECT_BINARY_DIR 运行cmake命令的目录,通常是${PROJECT_SOURCE_DIR}/build ...
- linux C 编译时手动链接遇到的问题(未解决)
写多线程的时候,编译的时候遇到了问题,开始的时候是这样的: 编译器不认识pthread_create和pthread_join这两个函数. 搜了一下原因是没有链接相应的库,下面是我看到一个博友写的: ...