python中一切皆对象,命名皆引用
先了解Python中自带的一个函数id(),这是一个很有用的函数:id()函数返回对象的内存地址,可以用来唯一标识对象。
1. Python中一切皆对象,数值、字符串、函数、列表、类、文件等都是对象,加载到内存中时会给这些对象分配一些内存资源,可以通过id()函数来表示它们的内存地址。
string = "hello python" alist = [1, 2, 3] def func(): return 0 class Dog(object): pass print(id(string)) print(id(alist)) print(id(func)) print(id(Dog)) """ output: 2998789135984 2998789677376 2998789780528 2998757580832 """
2. Python在使用变量之前无须定义它的类型,但是必须声明以及初始化该变量。同一变量名可以(在不同阶段)代表不同类型的数据。
i = 1 print(i, type(i), id(i)) i = 10000000000 print(i, type(i), id(i)) i = 1.1 print(i, type(i), id(i)) """ output: 1 <class 'int'> 140736383850144 10000000000 <class 'int'> 1871879428400 1.1 <class 'float'> 1871879428048 """
和静态类型语言(如C等)有很大不同。静态语言只要一个变量获得了一个数据类型,它就会一直是这个类型,变量名代表的是用来存放数据的内存位置。
而Python中使用的变量名只是各种数据及对象的引用,即Python中的变量名等同于C++中的左值引用名。
Type &引用名 = 左值表达式;
可以用id()获取的才是存放数据的内存位置,我们输入的1、10000000000和1.1三个数据均会保存在id()所指示的这些内存位置中,直到垃圾回收车把它
拉走(在系统确定你不再使用它的时候)。这是动态语言的典型特征,它确定一个变量的类型是在给它赋值的时候。
即一切命名是引用。
3. 一个对象与多个引用
1)间接引用的情况
a = 1 b = a // 间接引用 1 c = b // 间接引用 1 d = a // 间接引用 1 print(id(a)) print(id(b)) print(id(c)) print(id(d)) """ output: 140736383850144 140736383850144 140736383850144 140736383850144 """
由输出可知,a,b,c,d所引用的内存空间地址都是一样的。
经过测试将a = 1改为a = "hello python",a = [1, 2, 3],a = (1, 2, 3)等其它对象,输出的内存地址都是一样的。
这是间接引用的情况,我们可以得到这么一个结论:无论什么数据类型,通过间接引用(引用与引用之间的传递),都指向同一个内存地址。
2)接下来我们来看看直接引用
a = 1 b = 1 print(id(a)) print(id(b)) """ output: 140736372512416 140736372512416 """
经过测试对于string,number类型的变量,分别直接引用所指向的内存空间都是一样的。
但是当类型变成list,dict时就不同了:
a = [1, 2, 3] b = [1, 2, 3] print(id(a)) print(id(b)) """ output: 2522481549248 2522481282496 """
分别直接引用相同的 list 对象,输出的内存地址是不一样的。
经过测试知道:对于list、set、dict这样的对象,分别创建多个相同值的引用,并不是指向同一个内存地址,也就是说每定义一个list对象,
会分别在内存上开辟出不同的空间存放。
结论:这个跟Python的内存机制有关,对于语言来说,频繁地创建销毁对象,会很影响机器性能。
像number、string等数据类型在定义的时候,无论你定义多少个相同值的对象,也不管你是定义在函数体内的局部变量,还是函数外的全局变量,
在Python中都只开辟一块内存空间,即所有的引用都指向的是同一个对象。
对于像list、set、dict这样的“容器对象”,在Python中,你定义了几个对象,实际就为你创建几个,分别开辟不同的内存空间存储(不同的内存地址)。
a = "hello" print(id(a)) def func(): b = "hello" print(id(b)) func() """ output: 2930713419376 2930713419376 """
注意:直接引用中存在一个比较特殊的类型,那就是tuple,先看一个例子
a = (1,2,3) b = (1,2,3) print(id(a)) print(id(b)) """ output: // 内存地址相同 2350039937344 2350039937344 """ a = (1,2,[1,2]) b = (1,2,[1,2]) print(id(a)) print(id(b)) """ output: // 内存地址不同 2350069670528 2350040112448 """
对于tuple,我们认为当其元素含有可变数据类型时,它其实是可变的,反之为不可变的。
对直接引用,我们总结一下:对于可变数据类型(含可变tuple),会分别开辟内存,对于不可变数据类型(含不可变tuple),只会开辟一块内存。
4. 可变类型和不可变类型
1)不可变类型对象的值是固定的,不可以被更改,如果需要存储一个新的值得话,会在内存中创建一个新的对象,并修改引用。
不可变类型对象,包括bool, number,string,tuple(特殊)等。
不可变类型对象扮演着重要角色,比如在字典中key的值必须是不可变类型的,比如像列表这样的对象,是不能作为key值的。
a = 1 print(id(a)) a += 1 print(id(a)) """ output: 140736383850144 140736383850176 """
上面例子中,a 加1前后的地址发生了变化,因为+1后指向了新的对象。
2)可变类型的对象,值是可以改变的,但是内存地址不会改变。
alist = [1, 2, 3] print(id(alist)) alist[2] = 10 print(id(alist)) """ output: 1974126305088 1974126305088 """
修改alist的元素,alist对象的地址没有发生改变。
可变对象的内部每个元素的类型又可以是不可变对象和可变对象,其内存分配情况也是一样的。
tuple(元组)虽然是不可变类型对象,但是可以修改其元素值(该元素类型得为可变类型),但tuple对象地址依然是不变的
t = ('a', 'b', ['A', 'B']) print(id(t)) t[2][0] = 'X' t[2][1] = 'Y' print(id(t)) """ output: 1708397867328 1708397867328 """
可以从指针角度来理解,其内存布局大概是下面这样子的,修改后变成下面右边这张图。
表面上看,tuple 的元素确实变了,但其实变的不是 tuple 的元素,而是list 的元素。tuple 一开始指向的 list 并没有改成别的 list,所以,tuple
所谓的“不变”是说,tuple 的每个元素,指向永远不变。即指向'a',就不能改成指向'b',指向一个 list,就不能改成指向其他对象,但指向的这个 list 本身是可变的!
5. 对函数传递参数做一个分析
理解了上面的内容后,我们就知道:
1)如果是间接引用,则内存地址相同。
2)如果是直接引用,则不可变类型对象(含不可变tuple)在内存中只创建一个,可变类型对象(含可变tuple)在内存中会创建多份。
函数的参数传递当然也包括直接引用和见直接引用,直接来看几个例子。
def func(t): print(id(t)) a = [1,2,3,4,5] print(id(a)) func(a) """ func()是间接引用[1,2,3,4,5],输出地址相同 2657154588800 2657154588800 """ print(id(a)) func([1,2,3,4,5]) """ func()是直接引用[1,2,3,4,5],输出地址不同 2657154588800 2794855255488 """
是否创建了新的引用,是否创建了新的对象。