(1)python中的引用传递
首先必须理解的是,python中一切的传递都是引用(地址),无论是赋值还是函数调用,不存在值传递。
(2)不可变对象(变量指向的内存的中的值不能够被改变)
当更改该对象时,由于所指向的内存中的值不可改变,所以会把原来的值复制到新的空间,然后变量指向这个新的地址。python中数值类型(int和float),布尔型bool,字符串str,元组tuple都是不可变对象。
a = 1print id(a) # 40133000L,整数1放在了地址为40133000L的内存中,a变量指向这个地址。a += 1 print id(a) # 40132976L,整数int不可改变,开辟新空间存放加1后的int,a指向这个新空间。
(3)可变对象(变量指向的内存的中的值能够被改变)
当更改该对象时,所指向的内存中的值直接改变,没有发生复制行为。python中列表list,字典dict,集合set都是可变对象。包括自定义的类对象也是可变对象。
a = [1,2,3]print id(a) # 44186120L。a += [4,5] # 相当于调用了a.extend([4,5])print id(a) # 44186120L,列表list可改变,直接改变指向的内存中的值,没开辟新空间。a = a + [7,8] # 直接+和+=并不等价,使用+来操作list时,得到的是新的list,不指向原空间。print id(a) # 44210632Ldef f(default_arg=[]): default_arg.append('han')f() # ['han']f() # ['han', 'han'] # 函数默认的可变参数并不会每次重新初始化,而是使用上次的作为默认值。f([]) # ['han'] # 自行传入参数即可。def f(default_arg=None): # 一个常见的做法是判断是否为空,为空则新建list,否则append if default_arg is None: default_arg = [] default_arg.append("some_string") return default_arg
(4)可变对象和不可变对象
python变量保存的是对象的引用,这个引用指向堆内存里的对象,在堆中分配的对象分为两类,一类是可变对象,一类是不可变对象。不可变对象的内容不可改变,保证了数据的不可修改(安全,防止出错),同时可以使得在多线程读取的时候不需要加锁。
() is () # 返回True,因为tuple是不可变对象(不可改变,怎么定义都一样)'' is '' # 返回True,因为str是不可变对象None is None # 返回True,None也是不可变的[] is [] # 返回False,因为是可变对象(可能改变,定义出来的两个必然要不一样){} is {} # 返回False,因为是可变对象[] == [] # 返回True,注意==和is的不同,==只比较内容,is比较地址(id)class Student: passStudent() is Student() # 返回False,自定义类型也是可变对象,两次定义的对象地址是不同的id(Student()) == id(Student()) # 返回True,这里比较神奇,是因为创建一个Student对象,id()后返回地址但是进行了对象销毁,第二次又重新创建,两次占用了同一个地址
(5)不可变对象的编译时驻留(类似java的常量池)
学java的可以回忆回忆JVM的常量池
int 的驻留:-5到256之间的整数都会进行驻留,再次定义的变量地址不变,为什么是-5到256呢,这是解释器决定的,依赖于具体实现。str的驻留:只包含字母,数字,下划线的字符串会驻留;长度为0或1的会驻留;
a = -5 b = -5 a is b # True,-5到256之间的整数,驻留(直觉上这部分数据会频繁调用,驻留可以节省资源)a = 256 b = 256 a is b # True,-5到256之间的整数,驻留a = -6 b = -6 a is b # False,非-5到256之间的整数,不驻留a = 257 b = 257 a is b # False,非-5到256之间的整数,不驻留a = 'hello_world' b = 'hello'+'_'+'world'a is b # True,只包含字母,数字,下划线的字符串会驻留a = 'hello_world!' b = 'hello_world!'a is b # False,包含了特殊字符!, 不驻留'hello_world' is '_'.join(['hello', 'world']) # False,因为驻留是编译阶段发生的,join在解释阶段才产生结果,未进行驻留a, b = 'hello_world!', 'hello_world!'a is b # True 编译器的优化,在同一行赋值字符串时,只创建一个对象,指给两个引用。(ps:不适用3.7.x以下版本,3.7.x以下中会返回False,本焓主要用Python3.8.x)
(6)关于驻留的陷阱
跟驻留没有直接关系,是在命令行运行和py文件直接运行有一些差异。先看之前的小例子。
a = 257 b = 257 a is b # False,非-5到256之间的整数,不驻留。
事实上,在命令行运行得到的才是False(我做的小实验一般都在交互式命令行上运行)如果把这三行放到py文件里,再直接运行,得到的是True,因为py文件是一次性编译的,而交互式命令行按一行为单位(严格说是命令结束时的全部,因为会有for while这种)编译或者在交互式中把这三行定义为函数,再调用函数,返回也是True。
def func(): a = 257 b = 257 return a is bfunc() # 返回True
以上程序在命令行运行和py文件直接运行有一些差异。
这是由python的代码块机制导致的,在同一代码块中相同值的赋值会指向同一个对象。函数体,类对象体,py文件,模块都可以看作一个代码块。
在交互式命令行上,一行看作一个代码块(严格说是命令结束时的全部,因为会有for while这种),所以,这里所谓“代码块的优化”,就是前面提到的,同行赋值的优化,只在一行(代码块)上优化。
到具体直接运行py文件,又有了更大范围的代码块的优化,所以连着两行相同赋值的对象,会指向同一个对象。