Python变量的认识理解(修订)

Python变量的认识理解(修订)

变量(variable)是编程的基础概念,Python 的变量看似简单,深入了解却不易。

在大多数语言中,为一个值起一个名字时,把这种行为称为“给变量赋值”或“把值存储在变量中”。不过,Python与许多其它计算机语言的有所不同,它并不是把值存储在变量中,而像是把名字“贴”在值的上边(专业一点说法是将名字绑定了对象,或者说名称引用了对象——名称指向对象)。所以,有些Python程序员会说Python没有变量,只有名字,通过名字找到它代表的值。

Python中,名称 用于指代对象。 名称是通过名称绑定操作来引入的。
【见官方文档 命名与绑定 
https://docs.python.org/zh-cn/3/reference/executionmodel.html#naming-and-binding
提示:在Python的官方文档中更多地使用了“名称”(name)这个术语来。但是在实际的编码和交流中,人们也常常使用“变量”(variable)这个术语来描述Python中的名称。】

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中变量的这种情况加深认识,下面适度展开介绍。

Python中的变量、对象、引用三者之间的关系。

在Python里一切皆对象。Python中,对象具有三要素:标识(identity)、类型(type)、值(value)。

【对象、值与类型官方文档https://docs.python.org/zh-cn/3/reference/datamodel.html#objects-values-and-types摘录:

Python 程序中的所有数据(data)都是由对象或对象间关系来表示的。
每个对象(object)都有各自的标识(identity)、类型(type)和值(value)。一个对象被创建后,它的标识就绝不会改变;你可以将其理解为该对象在内存中的地址(address )。“is” 运算符可以比较两个对象的标识号是否相同;id() 函数能返回一个代表其标识的整数。
有些对象的 值 可以改变。值可以改变的对象被称为 可变的(mutable);值不可以改变的对象就被称为 不可变的(immutable)。(一个不可变容器对象如果包含对可变对象的引用,当后者的值改变时,前者的值也会改变;但是该容器仍属于不可变对象,因为它所包含的对象集是不会改变的。因此,不可变并不严格等同于值不能改变,实际含义要更微妙。) 一个对象的可变性是由其类型决定的;例如,数字、字符串和元组是不可变的,而字典和列表是可变的。 】

☆标识(identity):

用于唯一标识对象,通常对应对象在计算机内存中的地址。使用内置函数id(obj)返回对象唯一标识。

☆类型(type):

类型可以限制对象的取值范围和可执行的操作。使用内置函数type(obj)返回对象所属类型。

对象中含有标准的头部信息:类型标识符。标识对象类型,表示对象存储的数据的类型。

每一个对象都有两个标准的头部信息:

1.类型标识符,去标识对象的(数据)类型;

2.引用计数器,记录当前对象的引用的数目。

(回收机制:变量的引用计数器为0,自动清理。 ※ 较小整数型对象有缓存机制。)

☆值(value):

表示对象存储的数据的信息。使用内置函数print(obj)可以直接打印值。

Python中,变量用来指向任意的对象,是对象的引用。Python变量更像是“标签”——给一个变量赋值,把这个标签贴到一个对象上,重新赋值,是撕下标签贴到另一个对象上。

在python中,变量保存的是对象(值)的引用,id()函数可以获取变量在内存中的地址。下面给出示例:

Python变量的认识理解(修订)_第1张图片

【顺便提示:id()的值不是固定不变的——此值系统为对象分配的内存地址,在你练习时显示的不同值是正常的。】

参见下图:

Python变量的认识理解(修订)_第2张图片

下面是字符串的示例:

Python变量的认识理解(修订)_第3张图片

参见下图:

Python变量的认识理解(修订)_第4张图片

在Python中,值可以放在内存的某个位置(地址),变量用于引用它们,给变量赋一个新值,原值不会被新值覆盖,变量只是引用了新值。顺便说明,Python的垃圾回收机制会自动清理不再被用到的值,所以不用担心计算机内存中充满被“丢弃”的无效的值。

可变(mutable) 类型对象、不可变(immutable) 类型对象

list(列表)、dict(字典)、set(集合)是可变对象。

不可变类型对象,指具有固定值的对象。不可变对象包括数字(numbers)、字符串(strings)和元组(tuples)。这样的对象不能被改变。如果必须存储一个不同的值,则必须创建新的对象。不可变对象不允许对自身内容进行修改。如果我们对一个不可变对象进行赋值,实际上是生成一个新对象,再让变量指向这个对象。哪怕这个对象简单到只是数字 0 和 1。

由于 Python 中的变量存放的是对象引用,所以对于不可变对象而言,尽管对象本身不可变,但变量的对象引用是可变的。运用这样的机制,有时候会让人产生糊涂,似乎可变对象变化了。如下面的代码:

i = 73 

i += 2

不可变的对象的特征没有变,依然是不可变对象,变的只是创建了新对象,改变了变量的对象引用。参见下图:

Python变量的认识理解(修订)_第5张图片

对于可变对象,其对象的内容是可以变化的。当对象的内容发生变化时,变量的对象引用是不会变化的。如下面的例子。

m=[5,9] 

m+=[6]

参见下图:

Python变量的认识理解(修订)_第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 关键字声明。关于变量的作用域更多介绍可见https://blog.csdn.net/cnds123/article/details/108429084
 

补充说明:对复杂的数据类型(列表、集合、字典),如果添加某一项元素,或者添加几个元素,不会改变其本身的地址,只会改变其内部元素的地址引用,但是如果对其重新赋值时,就会重新赋予地址覆盖就地址,这时地址就会发生改变。示例代码如下:

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]))#打印列表、列表的地址、第二个元素的地址
#不难发发现:列表变了、列表的地址没有变、列表内部元素变了、列表内部元素的地址变了

测试运行如下图所示:

Python变量的认识理解(修订)_第7张图片

 深入学习:

内存管理 官方文档 https://docs.python.org/zh-cn/3/c-api/memory.html

python变量的引用以及在底层存储原理 https://hero78.blog.csdn.net/article/details/81559023

python内存管理(通俗介绍) https://www.cnblogs.com/TMesh/p/11731010.html

附录、C、C++中的赋值和ython中的赋值的比较:

在C、C++中的赋值示例

#include 
using namespace std;
int main() {
    int a  = 10; // C和C++支持
    int* b = &a; // C和C++支持
    int& c = a;  // C++支持
    
    cout << "a = " << a  << endl; // a = 10
    cout << "*b = " << *b << endl; // *b = 10
    cout << "c = " << c << endl; // c = 10
    
    return 0;
}

解释:这是一个简单的 C++ 程序,它演示了指针、取地址运算符和引用的一些基本概念。

首先,定义一个整型变量 a 并初始化为 10。

然后,定义一个整型指针 b,并将 a 的地址赋值给它。使用取地址运算符 & 获取 a 的地址,将其存储在指针 b 中。

接下来,定义一个整型引用 c,并将 a 赋值给它。引用是 C++ 中的一种特殊的变量,它允许我们通过一个已知的变量名访问另一个变量。这里,我们使用引用 c 来访问变量 a。

输出语句分别打印了变量 a、指针 b 指向的值(即 a 的值)以及引用 c 所指向的值(即 a 的值)。

最后,程序返回 0,表示运行成功。

在Python中的赋值示例,一切都是对象:

a = 10
b = a 
print("a =", a) # a = 10
print("b =", b) # b = 10

解释:Python中一切都是对象。在 Python 中,每个变量都是一个对象的引用,它指向存储在内存中的对象。当我们将一个值赋给变量时,实际上是将这个变量与一个对象关联起来,而不是直接将值存储在变量中。这就是 Python 中的“引用赋值”概念。

在这个例子中,当我们执行a = 10时,Python 会创建一个整数对象10,并将变量a指向这个对象。这意味着a仅仅是指向10这个对象的引用,而不是直接存储了值10。

当我们执行b = a时,实际上是将变量b也指向a所引用的对象,也就是10这个对象。这样,a和b都指向了同一个整数对象10。

最后,使用print()函数分别打印变量a和b的值。由于a和b都指向了同一个整数对象10,所以它们的值都是10。

你可能感兴趣的:(Python学习,python,开发语言)