python中的值传递和引用传递(可变对象与不可变对象)也就是赋值的原理-python全部是引用传递
python中的值传递和引用传递(可变对象与不可变对象)也就是赋值的原理-python全部是引用传递
20141215 Chenxin
猜测:
1.属于同一个类生成的对象,其默认属性指向同一个引用.这样当你修改一个对象的时候,会影响到其他对象,除非你通过类中的其他方法加以修改.实际上应该都是指针指向的概念.
2.基本"变量",就是不可变"对象",是调用的值传递.则当你重新通过"="赋值的时候,python内部是创建了一个新的值(对象)给它.(这里也就是通常所说的值传递).不用关心是哪种传递,说白了,python中都是引用传递.
3.对于"可变对象",如list,dict以及默认对象,完全是引用传递的方式.是内存中一个地址的引用,像指针一样.
4.如果想拷贝一个对象到其他对象(副本形式),那么只能通过python的copy模块来实现.
5.python有个缓存的概念,缓存的整数范围为(-5,256).其他类型的不会缓存.比如x=1,y=1,x is y;x=257,y=257,x is not y; id(*)来检查内存地址.
试验:
关于函数参数传递的解释如下(3个例子):<<python基础教程 第二版>>P93
def ChangeInt(a):
a=10
b=2
ChangeInt(b)
print b # ->2
翻译为非函数的样子:
b=2
a=b #a的内存地址跟b是一样的,说明这里是内存地址拷贝,因此赋值可以理解为指针指向
a=10 #将a指向了新创建的10对象,a的内存地址发生了改变.
print b
def ChangeList(a):
a[0]=10
b=[2]
ChangeList(b)
print b # ->[10]
翻译为非函数:
b=[2]
a=b #a的内存地址跟b是一样的,说明这里是内存地址拷贝,因此赋值可以理解为指针指向
a[0]=10 #将a指向的列表的第一个元素值指向内存新创建的10对象,a的内存地址没有改变,仍然跟b的内存地址一致.这里改变的只是列表内部第一个元素的地址,并未改变a的地址(也就是a指向的列表整体的地址).
print b
def ChangeList(a):
a=[10]
b=[2]
ChangeList(b)
print b # ->[2]
翻译为非函数:
b=[2]
a=b #a的内存地址跟b是一样的,说明这里是内存地址拷贝,因此赋值可以理解为指针指向
a=[10] #重新创建了一个新的列表,a指向这个新列表,a的内存地址发生了改变.
print b
结论:
总结以上3个例子,可以理解为:
1.python中的赋值"="语句,可以理解为指针的指向.
2.由于指针指向问题,故才衍生出"作用域"的概念,而不是因为有作用域的"人为限制"造成的以上结果,是吧?!
3.注意区分可变对象与不可变对象的区别.list,dict,int,char,tuple...
4.以此也推出了值传递与引用传递的概念,可以统一理解为python中都是引用传递.
说明:
int不可变的实际情况:
list可变的实际情况:
类对象与实例对象引用传递的方式:
!/bin/env python
class class_a():
num_a='abc' #当字符串足够长的时候呢?会不会是python的缓存呢?效果是一样的,不是缓存
a=class_a
b=class_a
a.num_a='xyz' #无论字符串长短,都一样
print a.num_a,b.num_a #输出xyz xyz
print id(a.num_a),id(b.num_a) #输出相同的内存地址
在这个类中,无论怎样去改变a对象,b对象都会跟着改变.
说明该对象为可变对象.类对象只是一个初始的内存空间,之后生成实例对象会把值填充进去.???
<<完>>
知识
其实到了python中差别不大.最主要的是python是通过赋值来创建一个对象的.
所以对于python中的对象,a='1'这样不能叫修改,只能叫创建.如果你认为这样就是修改的话,就错了.
一旦使用了赋值语句,就基本上与原对象无关了.#实际上是一个指针重新的指向
而且python还有可变对象和不可变对象:
象简单类型,整数,字符串,tuple都是不可变的.
而象list, dict, object都是可变的.所以对于它们的修改一般是调用相应的方法,如对于list,你要a.append(b),对于dict你要a['b'] = '1'等.
对象本身是没有什么变化,它的属性或值发生变化.对于这种种情况,传值还是引用都是一样的.
不过,可能说引用更准确一些,比如:
def b(c):
print id(c)
a = [2]
print id(a)
b(a)
你会看到两次打印出来的id值是一样的.说明是引用传递.
也就是说函数b中的参数所指的对象就是a.
理解为传值也无所谓,因为从赋值的角度来说,在函数中的赋值会创建新的对象,不会影响到原参数.
用指针的概念去理解更合理一些吧.
其实在python中变量名与真正的对象是一种绑定关系,或引用的关系.所以理解为引用更为合适.
和其他语言不一样,传递参数的时候,python不允许程序员选择采用传值还是传引用。Python参数传递采用的肯定是“传对象引用”的方式。实际上,这种方式相当于传值和传引用的一种综合。如果函数收到的是一个可变对象(比如字典或者列表)的引用,就能修改对象的原始值--相当于通过“传引用”来传递对象。如果函数收到的是一个不可变对象(比如数字、字符或者元组)的引用,就不能直接修改原始对象--相当于通过“传值'来传递对象。
python一般内部赋值变量的话,都是传个引用变量,和C语言的传地址的概念差不多。可以用id()来查询内存地址.
如果a=b的话, a和b的地址是相同的;
如果只是想拷贝,那么就得用 b=a[:],这样a和b的内存地址就不同了;
a=[1,2];b=a[:]; #这里a和b均指向一个叫做list的内存地址,list存储的,才是具体各个值对应的内存地址.列表是双层地址;
id(a)
140337215511368
id(b)
140337215510288 #不同
a='abc';b=a[:]; #这里是字符串形式,a指向的是'abc'的内存地址,b=a[:]后(相当于b=a),b同样是指向'abc'的内存地址.字符串是单层地址;这里的写法,有点多态的概念.
id(a)
140337216460600
id(b)
140337216460600 #相同
引用是指保存的值为对象的地址。在 Python 语言中,一个变量保存的值除了基本类型保存的是值外,其它都是引用,因此对于它们的使用就需要小心一些。下面举个例子:
问题描述:已知一个列表,求生成一个新的列表,列表元素是原列表的复制
a=[1,2]
b=a
这种做法其实并未真正生成一个新的列表,b指向的仍然是a所指向的对象。这样,如果对a或b的元素进行修改,a,b的值同时发生变化。
解决的方法为:
a=[1,2]
b=a[:]
这样修改a对b没有影响,修改b对a没有影响。(列表的复制,也就是副本.不是引用传递)
但这种方法只适用于简单列表,也就是列表中的元素都是基本类型,如果列表元素还存在列表的话,这种方法就不适用了。原因就是,象a[:]这种处理,只是将列表元素的值生成一个新的列表,如果列表元素也是一个列表,如:a=[1,[2]],那么这种复制对于元素[2]的处理只是复制[2]的引用,而并未生成 [2]的一个新的列表复制。
如果解决这一问题,可以使用copy模块中的deepcopy函数。修改测试如下:
import copy
a=[1,[2]]
b=copy.deepcopy(a)
print b -->[1, [2]]
a[1].append(3)
print a -->[1, [2, 3]]
print b -->[1, [2]]
有时候知道这一点是非常重要的,因为可能你的确需要一个新的列表,并且对这个新的列表进行操作,同时不想影响原来的列表。