引用语义
python中的变量采用引用语义,每一个变量名其实存储的是实际存放数据的内存的地址,如下图:
另外,在python中对变量名的访问(例如x=10),可以理解为通过只读的指针(地址)访问存放数据的内存空间,我们通过变量中存放的地址访问内存只能读,不能写,写的话将导致重新分配一块内存空间,存放新数据,并将变量中存放的原有的地址替换成新地址。下面我们分析一段代码:
x = 15
y = 15
s = [15, 20]
def func(): #c语言表达
x = 10 #addr_x = new int; *addr_x = 10
global y
y = 10 #*addr_y = 10
s[0] = 10 #*(*(addr_s)+0) = 10
对应的内存分布如下:
青涩难懂的解释1:
毫无疑问在python中对象也是一块内存,内存中除了包含属性、方法之外,还包含了对象得类型,我们通过引用来访问对象,比如a=A(),首先python创建一个对象A,然后声明一个变量a,再将变量a与对象A联系起来。变量a是没有类型的,它的类型取决于其关联的对象。a=A()时,a是一个A类型的引用,我们可以说a是A类型的,如果再将a赋值3,a=3,此时a就是一个整型的引用,但python并不是弱类型语言,在python中'2'+3会报错,而在php中'2'+3会得到5。可以这么理解,在python中变量类似于c中的指针,和c不同的是python中的变量可以指向任何类型,虽然这么说不太准确,但是理解起来容易点。
青涩难懂的解释2:
我们可以使用C#中的一些概念来类比python中的变量模型:
首先解释一下C#的机制:c#就相当于将c语言中的指针和非指针强行分开,程序员将不能决定使用的类型的形式,如果使用类对象,只能使用引用的形式,如果使用结构体和简单类型,只能使用值类型,不像在C++中我们完全可以在栈上定义对象,并使用非指针的形式操作对象,也完全可以在堆上分配int,并以指针的形式访问int。
C#中所有的对象都分为引用类型和值类型,它们最主要的区别是,对值类型来说,变量内存处存放的即是数据,引用类型的变量内存处存放的是真正数据对应的地址,两个值类型相互赋值,传递的是数据(和c语言一致),赋值之后它们将完全独立;两个引用类型的变量赋值(类似于C语言中指针赋值),传递的是地址,它们将指向同一块堆空间,对一个引用变量操作后,另一个引用变量所对应的数据将同步改变,那么在python中,我们可以理解为所有的变量全部都是引用类型,并且Python中也分只读的类型(例如字符串、元组、数值等)和非只读的类型(例如列表、字典等),只读的引用类型和C#中的string类型一致,非只读的和C#中的非只读变量一致。严格来说,python中没有像C#中的值类型,只有只读的引用类型,只读的引用类型表现出来的性质和值类型很像,但实现方案却不同。
string类型在C#中是只读的引用类型,string s1 = "ni hao "; string s2 = s1; s1 = s1 + "ma?"; 结果是s1 = "ni hao ma?", s2 = "ni hao"; 对s1的操作并没有影响到s2,因为,对s1进行赋值操作将导致s1执行新的内存空间"ni hao ma?",并不是对原来的内存空间"ni hao"进行修改,python中的只读类型变量也是这样,对一个变量进行赋值操作,将导致此变量指向新的内存空间,而不是修改老的内存空间。
其实C#中的string类型这一特性是非常容易实现的,只需要重载"="运算符时返回一个新的对象出去,而不是修改老的对象然后返回,然后string类型中所有的成员函数都禁止修改string类型的内容,涉及到表面上好像修改了内容的成员函数其实都是构造了一个新的string对象(包含着我们所期望的内容)。当然具体的实现方案并没有深入了解,此处只是猜测。