Python变量的认识理解(修订)
变量(variable)是编程的基础概念,Python 的变量看似简单,深入了解却不易。
在大多数语言中,为一个值起一个名字时,把这种行为称为“给变量赋值”或“把值存储在变量中”。不过,Python与许多其它计算机语言的有所不同,它并不是把值存储在变量中,而像是把名字“贴”在值的上边(专业一点说法是将名字绑定了对象,或者说名称引用了对象——名称指向对象)。所以,有些Python程序员会说Python没有变量,只有名字,通过名字找到它代表的值。
Python中的变量,与其它开发语言(如C语言)的不同:
在 Python 中,变量不需要预先声明数据类型,而是根据所赋的值自动推断数据类型,也就是说,变量的类型可以在运行时改变,这和C语言不同。
在C语言中,变量类似于一个“容器”,赋给它的值,装在容器中:
定义一个变量 int a = 1;
给变量a重新赋值 a = 2;
把变量a赋值给另外一个变量b ,int b = a;
会重新创建一个变量b(容器),将a中的内容复制粘贴至b中。
在python中,变量类似于名字标签“贴”在值上面,通过名字找到它代表的值。
定义一个变量 a = 1
给变量a重新赋值 a = 2
把变量a赋值给另外一个变量b, b = a
创建新的便利贴b,与a同时贴到值上
为了对python中变量的这种情况加深认识,下面适度展开介绍。
先说明第一点:变量的实现方式有:引用语义、值语义
值语义把变量的值直接保存在变量的存储区里,赋值实际是copy一份副本,两个变量拥有的存储空间是独立的,相互之间不影响,如c语言。
简单来说就是这样的效果:
b = a;
赋值实际是copy一份副本,b的值和a无关。图示如下:
引用语义,在变量里保存值(对象)的引用,赋值并不copy一份副本,两个变量只有一份的存储空间,相互之间可以理解为是别名,使用同一个值,如Java、Python 。
简单来说就是这样的效果:
b = a;
赋值并不copy一份副本,两个变量,只有一份的存储空间,使用同一个值。
现在说明第二点:Python中的变量、对象、引用三者之间的关系。
在Python里一切皆对象。Python中,对象具有三要素:标识(identity)、类型(type)、值(value)。
☆标识(identity):
用于唯一标识对象,通常对应对象在计算机内存中的地址。使用内置函数id(obj)返回对象唯一标识。
☆类型(type):
类型可以限制对象的取值范围和可执行的操作。使用内置函数type(obj)返回对象所属类型。
对象中含有标准的头部信息:类型标识符。标识对象类型,表示对象存储的数据的类型。
每一个对象都有两个标准的头部信息:
1.类型标识符,去标识对象的(数据)类型;
2.引用计数器,记录当前对象的引用的数目。
(回收机制:变量的引用计数器为0,自动清理。 ※ 较小整数型对象有缓存机制。)
☆值(value):
表示对象存储的数据的信息。使用内置函数print(obj)可以直接打印值。
Python中,变量用来指向任意的对象,是对象的引用。Python变量更像是“标签”——给一个变量赋值,把这个标签贴到一个对象上,重新赋值,是撕下标签贴到另一个对象上。
在python中,变量保存的是对象(值)的引用,id()函数可以获取变量在内存中的地址。下面给出示例:
【顺便提示:id()的值不是固定不变的——此值系统为对象分配的内存地址,在你练习时显示的不同值是正常的。】
参见下图:
下面是字符串的示例:
参见下图:
在Python中,值可以放在内存的某个位置(地址),变量用于引用它们,给变量赋一个新值,原值不会被新值覆盖,变量只是引用了新值。顺便说明,Python的垃圾回收机制会自动清理不再被用到的值,所以不用担心计算机内存中充满被“丢弃”的无效的值。
现在说明第三点:可变(mutable) 类型对象、不可变(immutable) 类型对象
list(列表)、dict(字典)、set(集合)是可变对象。
不可变类型对象,指具有固定值的对象。不可变对象包括数字(numbers)、字符串(strings)和元组(tuples)。这样的对象不能被改变。如果必须存储一个不同的值,则必须创建新的对象。不可变对象不允许对自身内容进行修改。如果我们对一个不可变对象进行赋值,实际上是生成一个新对象,再让变量指向这个对象。哪怕这个对象简单到只是数字 0 和 1。
由于 Python 中的变量存放的是对象引用,所以对于不可变对象而言,尽管对象本身不可变,但变量的对象引用是可变的。运用这样的机制,有时候会让人产生糊涂,似乎可变对象变化了。如下面的代码:
i = 73
i += 2
不可变的对象的特征没有变,依然是不可变对象,变的只是创建了新对象,改变了变量的对象引用。参见下图:
对于可变对象,其对象的内容是可以变化的。当对象的内容发生变化时,变量的对象引用是不会变化的。如下面的例子。
m=[5,9]
m+=[6]
参见下图:
对于可变(mutable)数据,对于采用引用语义实现的变量,假若b = a,则通过其中任何一个变量(如a)修改数据中的成分,将会在另一个(如b)中体现出来。
更专业一点的描述,Python 中的变量实际上是一个指向内存中某个对象的引用,也就是说,变量存储的是对象的引用地址,而不是一个存储数据的空间。因此,在 Python 中对变量进行赋值、传参等操作时,实际上是修改了该变量所指向的对象。
Python中的对象分为可变对象和不可变对象,其中不可变对象(如数字、字符串、元组等)一旦创建就不能被修改,而可变对象(如列表、字典等)则可以被修改。
不可变对象示例
x = 5
y = x
x += 1
print(x) # 输出 6
print(y) # 输出 5
在这个例子中,我们创建了一个整数类型的不可变对象,即数字 5。然后将其赋值给变量 x 和 y,接着对变量 x 进行修改(加 1),最后分别输出变量 x 和 y 的值。由于这里修改的是变量 x 所引用的对象,而不是原始的数字 5 对象,所以变量 y 的值没有改变。
可变对象示例:
a = [1, 2, 3] # 定义一个列表对象,并将其引用赋值给变量 a
b = a # 将 a 所引用的对象也赋值给变量 b
b.append(4) # 修改 b 所指向的对象,即也修改了 a 所指向的对象
print(a) # 输出 [1, 2, 3, 4]
print(b) # 输出 [1, 2, 3, 4]
这个例子中,变量 a 和 b 都指向同一个列表对象,即它们引用的是同一块内存地址。当我们在变量 b 所指向的列表对象中添加一个元素时,实际上也修改了变量 a 所指向的对象,因此最终输出的结果是 [1, 2, 3, 4]。
顺便说明python有预分配整数概念。在Python中,有一些整数对象会被预先分配,以便在程序执行期间频繁使用。这是因为Python中整数的使用非常普遍,因此为了提高性能,对一些小的整数对象进行了缓存。
具体来说,Python会在程序运行时预先分配-5到256范围内的整数对象,这些整数对象会被分配在内存中的固定位置,每次使用时直接引用该位置即可。这些对象被称为“小整数对象”,它们在程序中的使用非常频繁。如果值不是小整数对象,那么就不会被缓存,而会创建两个不同的对象,即使它们的值是相同的。可以通过id()函数获取对象的唯一标识符来验证。
在 Python 中,id()函数用于返回对象的唯一标识符(即内存地址),其作用是判断两个变量是否引用同一个对象。其语法如下:
下面是一个简单的例子,演示了 id() 函数的使用方法:
a = 123
b = 123
c = [1, 2, 3]
d = [1, 2, 3]
print(id(a)) # 输出类似于 140714802344592
print(id(b)) # 输出类似于 140714802344592 (与 a 相同)
print(id(c)) # 输出类似于 139764926845440 的地址
print(id(d)) # 输出类似于 139764926845952 的地址 (与 c 不同)
变量作用域:Python 中的变量作用域分为全局作用域和局部作用域。在函数内部定义的变量属于局部作用域,在函数外部定义的变量属于全局作用域。如果在函数内部要访问全局变量,则需要使用 global 关键字声明。
补充说明:对复杂的数据类型(列表、集合、字典),如果添加某一项元素,或者添加几个元素,不会改变其本身的地址,只会改变其内部元素的地址引用,但是如果对其重新赋值时,就会重新赋予地址覆盖就地址,这时地址就会发生改变。示例代码如下:
list_ = [1,2,3,4]
print(list_, id(list_))
list_.append(5)
print(list_, id(list_))
#如上代码,因为append前后的list_仍然是同一个对象,只是对象的值发了改变,所以地址不变。
#再如下面的代码
print(list_, id(list_), id(list_[1]))#打印列表、列表的地址、第二个元素的地址
list_[1] = 'aaa' #修改列表
print(list_, id(list_), id(list_[1]))#打印列表、列表的地址、第二个元素的地址
#不难发发现:列表变了、列表的地址没有变、列表内部元素变了、列表内部元素的地址变了
测试运行如下图所示: