一、python中的变量与对象
首先在理解python的可变对象和不可变对象时,要理解python的赋值操作。在python中,一切事物皆是对象,变量是对象在内存中的存储和地址的抽象
对变量的理解
在Python中,类型是属于对象的,而不是变量, 变量和对象是分离的,对象是内存中储存数据的实体,变量则是指向对象的指针。
“=”(赋值号)是将右侧对象的内存地址赋值给左侧的变量。
当我们写下面语句时
a = "abc"
Python解释器其实顺序干了两件事情:
1 在内存中创建一个字符串“abc”;
2 在内存中创建一个名为“a”的变量,并将“a”指向字符串“abc”(将“abc”的地址保存到“a”中)。
这样我们就能通过操作“a”而改变内存中的“abc”。
二、什么是可变对象/不可变对象
- 不可变对象 该对象所指向的内存中的值不能被改变当改变一个变量的时候,由于改变量所指向的内存中的值不能被改变,因此会将原来的值复制一份后再改变,具体做法是重新开辟一块空间存放新的值,并将该变量指向这个空间。原来空间中的值如果没有变量指向它,会被当做垃圾回收。数值类型(int float), 字符串str, 元组tuple是不可变的数据类型
- 可变对象 该对象所指向的内存中的值可以被改变。变量(准确的说是引用)改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的出地址,通俗点说就是原地改变。列表list, 字典dist, 集合set是可变的数据类型
举例说明
- 不可变对象的例子
>>> a = 'hello'
>>> id(a)
1921615688624
>>> a = 'world'
>>> id(a)
1921615689744
# 重新赋值之后,变量a的内存地址已经变了
# 'hello'是str类型,不可变,所以赋值操作重新创建了str对象 'world'对象,
# 然后将变量a指向了它
- 可变对象的例子
>>> lst = [1, 2, 3]
>>> id(lst)
1921615726216
>>> lst.append(4)
>>> id(lst)
1921615726216
# list重新赋值之后,变量lst的内存地址并未改变
# [1, 2, 3]是可变的,append操作只是改变了其value,变量lst指向没有变
三、python引用
python一般内部赋值变量的时候,都是传递一个引用变量,和C语言的传地址的概念差不多, 比如:
>>> a = [1,2,3] # 表示变量a保存了这个列表的地址
# python里可以用id()来查询下a在内存的地址是:1921615756616
>>> id(a)
1921615756616
>>> b = a
# 那b的内容是什么,地址又是什么呢?
# 用print 输出下b的内容也是[1,2,3]
# 然后我们查看下b的地址看下能否验证我们的结论
>>> b
[1, 2, 3]
# 果然b的地址也是:1921615756616
>>> id(b)
1921615756616
这样会带来一个问题,因为变量a,和变量b都是保存了同一个列表的地址。如果我改变a指向的列表的值的话,那b指向的列表的值也同时改变
>>> a[1] = 6
>>> a
[1, 6, 3]
>>> b
[1, 6, 3]
如果我们只想修改a列表里面的内容。而不想修改b的内容,那就要用到python的拷贝了
>>> a = [1,2,3]
>>> b = a[:]
>>> a[1] = 6
>>> a
[1, 6, 3]
>>> b
[1, 2, 3]
四、函数值传递
首先来看一个例子
def func_int(a):
a += 4
def func_list(lst):
lst[0] = 4
t = 0
func_int(t)
print t
# output: 0
t_list = [1, 2, 3]
func_list(t_list)
print t_list
# output: [4, 2, 3]
主要是因为可变对象和不可变对象的原因:对于可变对象,对象的操作不会重建对象,而对于不可变对象,每一次操作就重建新的对象。
在函数参数传递的时候,Python其实就是把参数里传入的变量对应的对象的引用依次赋值给对应的函数内部变量。参照上面的例子来说明更容易理解,func_int中的局部变量"a"其实是全部变量"t"所指向对象的另一个引用,由于整数对象是不可变的,所以当func_int对变量"a"进行修改的时候,实际上是将局部变量"a"指向到了整数对象"1"。所以很明显,func_list修改的是一个可变的对象,局部变量"a"和全局变量"t_list"指向的还是同一个对象。
五、深拷贝 & 浅拷贝
接下来的问题是:如果我们一定要复制一个可变对象的副本怎么办?简单的赋值已经证明是不可行的,所以Python提供了copy模块,专门用于复制可变对象。
copy中有两个方法:copy()和deepcopy(),前一个是浅拷贝,后一个是深拷贝。
浅拷贝仅仅复制了第一个传给它的对象,下面的不管了;而深拷贝则将所有能复制的对象都复制了。
>>> import copy
>>> a = [[1,2,3], [4,5,6]]
>>> b = a
>>> c = copy.copy(a)
>>> d = copy.deepcopy(a)
>>>
>>> a.append(15)
>>> a[1][2] = 10
>>> a
[[1, 2, 3], [4, 5, 10], 15]
>>> b
[[1, 2, 3], [4, 5, 10], 15]
>>> c
[[1, 2, 3], [4, 5, 10]]
>>> d
[[1, 2, 3], [4, 5, 6]]
六、作用域
在Python程序中创建、改变或查找变量名时,都是在一个保存变量名的地方进行中,那个地方我们称之为命名空间。作用域这个术语也称之为命名空间。
变量名引用分为三个作用域进行查找:首先是本地,然后是函数内(如果有的话),之后是全局,最后是内置。在默认情况下,变量名赋值会创建或者改变本地变量。全局声明将会给映射到模块文件内部的作用域的变量名赋值。Python 的变量名解析机制也称为 LEGB 法则,具体如下:
当在函数中使用未确定的变量名时,Python搜索4个作用域:
- 本地作用域(L)
- 上一层嵌套结构中 def 或 lambda 的本地作用域(E)
- 全局作用域(G)
- 内置作用域(B)。
按这个查找原则,在第一处找到的地方停止。如果没有找到,Python 会报错的。
引用来源
1 Python中的可变对象和不可变对象
2 Python值传递还是引用传递
3 Python中的变量、引用、拷贝和作用域
网友评论