Python基础变量与赋值的底层原理是什么一、开篇变量不是盒子如果你学过其他语言你可能被教过变量就像一个盒子你把值放进去。这个比喻对于初学者理解基本概念有帮助但对于Python来说这个比喻是错误的。⌨️ 在Python中变量不是一个装东西的盒子而是一个贴了标签的箭头——标签贴在对象上指向那个对象。理解这个区别是真正理解Python的第一步。当我写x 5时Python做的事情是在内存中创建一个整数对象5创建一个名字x让x指向那个整数对象变量x本身不存储值它存储的是对对象的引用。二、Python变量模型的底层原理2.1 一切皆对象在Python中一切皆对象。数字是对象字符串是对象函数是对象类也是对象。每个对象有三个基本属性id身份标识对象在内存中的唯一编号type类型对象的类型value值对象的值x42# 用三个内置函数查看对象的三个基本属性print(fid:{id(x)})# 内存地址的整数表示print(ftype:{type(x)})# class intprint(fvalue:{x})# 42id()返回的数值在不同的运行环境下会不同但在一次运行中每个对象的id是唯一的。你可以把id理解为对象的身份证号码。2.2 赋值 贴标签x100# 创建整数对象100贴上标签xyx# 把标签y也贴到同一个对象上z100# Python发现已经有一个100的对象了把z也指向它print(fx的id:{id(x)})print(fy的id:{id(y)})print(fz的id:{id(z)})# 三个id完全相同x、y、z都指向同一个对象用图解来理解x 100 → x ──→ [100对象] y x → y ──→ [100对象] ← x也指向这里 z 100 → z ──→ [100对象] ← x和y也指向这里三个变量指向的是同一个整数对象。赋值y x并没有创建一个新的整数100而是让y也指向x指向的那个100。2.3 重新赋值 换标签x100print(fx的id:{id(x)})# 某个id比如 140735...x200# 重新赋值print(fx的id:{id(x)})# id变了指向了一个新的200对象print(f类型:{type(x)})# 还是int 关键理解x 200不是在原来的盒子里把100换成200而是让标签x从100对象上撕下来贴到一个新的200对象上。原来的100对象并没有被修改整数在Python中是不可变的。如果还有其他变量指向它它继续存在。如果没有它就会被Python的垃圾回收机制清理掉。2.4 深入理解可变对象和不可变对象的区别这是变量模型中最重要的概念。Python的对象分为两类类别类型举例特点不可变int, float, str, tuple, frozenset, bool对象创建后其值不能被改变可变list, dict, set, bytearray, 自定义对象对象创建后其值可以被改变这个区别在多个变量指向同一个对象时尤其重要# 不可变对象inta10ba# b也指向10b20# b指向了新对象20a不受影响print(a)# 10a还是指向10print(b)# 20# 可变对象lista[1,2,3]ba# b也指向同一个列表b.append(4)# 通过b修改了那个列表print(a)# [1, 2, 3, 4] ← a也变了print(b)# [1, 2, 3, 4]⚠️ 第二个例子非常关键a [1, 2, 3]然后b a此时a和b指向同一个列表对象。当你通过b.append(4)修改这个列表时a看到的列表也被修改了——因为它们指向同一个对象。a [1, 2, 3] → a ──→ [[1, 2, 3]] b a → b ──→ 同一个[[1, 2, 3]] b.append(4) → 列表变成[[1, 2, 3, 4]]a和b都指向它三、命名规则详解3.1 硬性规则必须遵守Python的变量命名有三条硬性规则规则一只能包含字母、数字、下划线不能有空格和特殊字符# ✅ 合法name小明user_namexiaominguser1用户1_private私有变量# ❌ 不合法user-name用户# 不能用连字符1user用户# 不能以数字开头user name用户# 不能有空格username用户# 不能有特殊字符规则二不能以数字开头# ✅ 合法user1正确_1st_user正确# ❌ 不合法1st_user错误# SyntaxError规则三不能使用Python关键字Python有35个保留关键字不能用作变量名# 查看所有关键字importkeywordprint(keyword.kwlist)# [False, None, True, and, as, assert, async,# await, break, class, continue, def, del, elif,# else, except, finally, for, from, global, if,# import, in, is, lambda, nonlocal, not, or,# pass, raise, return, try, while, with, yield]# ❌ 这些都会报SyntaxErrorif10classPythonfor3 一个简单的记忆方法如果你在IDE中输入一个词后它自动变色了就说明它是关键字不能用作变量名。3.2 软性规范强烈建议遵守规范一使用描述性的名字# ✅ 好的命名——一眼就知道变量是干什么的student_count42average_score85.5is_user_logged_inTrueuser_namexiaoming# ❌ 不好的命名——看不出含义a42x85.5bTruenxiaoming除了循环计数器i、j、k和临时变量外避免使用单个字母的变量名。规范二蛇形命名法Python社区约定使用snake_case蛇形命名法全小写字母单词之间用下划线连接。# ✅ 推荐student_name小明max_connection_count100calculate_average_scoreTrue# ❌ 其他语言风格的命名在Python中不推荐studentName小明# camelCase驼峰命名Java/JS风格StudentName小明# PascalCase帕斯卡命名C#风格MAX_CONNECTIONS100# 全大写通常用于常量不用于普通变量规范三使用正向命名# ✅ 推荐正向表达is_validTruehas_permissionFalsecontains_dataTrue# ❌ 不推荐双重否定让人费解is_not_invalidTrue# 意思是有效not_emptyTrue# 双重否定烧脑四、Python的赋值机制深入4.1 多重赋值Python支持将同一个值赋给多个变量xyz0print(x,y,z)# 0 0 0# 实际上三者指向同一个对象print(id(x),id(y),id(z))# 三者的id相同# 对可变对象要注意ab[]a.append(1)print(b)# [1] ← b也变了4.2 序列解包赋值这是Python最优雅的特性之一# 基础用法x,y,z1,2,3print(x,y,z)# 1 2 3# 交换两个变量的值不需要临时变量a,b10,20a,bb,aprint(a,b)# 20 10# 从函数返回多个值defget_user_info():return小明,25,北京name,age,cityget_user_info()print(f{name},{age}岁,{city})# 解包列表colors[红,绿,蓝]r,g,bcolorsprint(r,g,b)# 红 绿 蓝4.3 星号解包# 用 * 收集多余的元素first,*middle,last[1,2,3,4,5,6]print(first)# 1print(middle)# [2, 3, 4, 5]print(last)# 6# 常见用法head,*tail[1,2,3,4,5]print(head)# 1print(tail)# [2, 3, 4, 5]*body,foothelloprint(body)# [h, e, l, l]print(foot)# o4.4 链式比较和赋值# 比较运算符的链式写法x5print(1x10)# True相当于 1 x and x 10print(1x5)# True# 但是连续的赋值和比较不能混用# a b c d # 这行代码很难读不要这么写4.5 增量赋值x10x5# 等价于 x x 5x-3# 等价于 x x - 3x*2# 等价于 x x * 2x/4# 等价于 x x / 4x//2# 等价于 x x // 2x%3# 等价于 x x % 3x**2# 等价于 x x ** 2⚠️ 对于不可变对象x 1创建了新对象。对于可变对象list_a [4]在原地修改# 不可变x10old_idid(x)x5new_idid(x)print(old_idnew_id)# Falsex指向了新对象# 可变lst[1,2,3]old_idid(lst)lst[4,5]new_idid(lst)print(old_idnew_id)# Truelst还是同一个列表对象五、内存管理与引用计数5.1 引用计数的概念Python使用引用计数来管理内存。每个对象内部都有一个计数器记录有多少个引用变量指向它。importsys a[]# 列表对象的引用计数 1a指向它print(sys.getrefcount(a)-1)# 减去getrefcount函数参数自身的引用ba# 引用计数 2a和b都指向它ca# 引用计数 3dela# 引用计数 2delb# 引用计数 1delc# 引用计数 0 → 对象被销毁当一个对象的引用计数降到0时Python的垃圾回收器会自动回收它占用的内存。5.2 小整数缓存Python为了提高性能会缓存一些常用的小整数。在CPython官方Python实现中-5到256之间的整数在解释器启动时就预创建好了以后使用这些数值时直接复用a256b256print(aisb)# True同一个对象a257b257print(aisb)# False不同的对象⚠️ 这是一个实现细节不是语言特性。不要依赖这个行为来写代码。判断两个值是否相等永远用而不是is。5.3 字符串驻留Interning类似的Python也会缓存一些短字符串ahellobhelloprint(aisb)# True同一个对象# 但长字符串或有空格的字符串不一定ahello world!bhello world!print(aisb)# 可能是True也可能是False不确定再次强调判断值相等用判断是否是同一个对象用is。六、类型系统动态类型的利与弊6.1 动态类型 vs 静态类型Python是动态类型语言变量没有固定的类型同一个变量可以先指向字符串再指向数字再指向列表。thinghello# thing是字符串print(type(thing))# class strthing42# thing现在是整数print(type(thing))# class intthing[1,2,3]# thing现在是列表print(type(thing))# class list动态类型的优点代码更简洁不需要声明类型灵活同一个函数可以处理多种类型的数据开发速度快动态类型的缺点类型错误只能在运行时发现IDE能帮一些忙大型项目中不够安全6.2 类型推断从Python 3.5开始支持可选的类型注解# 类型注解——告诉IDE和代码阅读者变量的预期类型name:str小明# name应该是字符串age:int25# age应该是整数scores:list[int][85,92,78]# scores是整数列表# 函数类型注解defgreet(name:str,age:int)-str:参数是字符串和整数返回值是字符串returnf{name}今年{age}岁类型注解不影响运行——Python解释器不会检查类型。但它们能帮助IDE提供更好的代码补全和错误提示。七、常见误区7.1 误区一赋值会拷贝# ❌ 很多新手的误解original[1,2,3]copyoriginal# 这没有创建新列表copy.append(4)print(original)# [1, 2, 3, 4]——original被意外修改了# ✅ 正确的拷贝方式original[1,2,3]copyoriginal.copy()# 方法一浅拷贝copyoriginal[:]# 方法二切片copylist(original)# 方法三list()构造函数importcopy copycopy.deepcopy(original)# 方法四深拷贝处理嵌套结构7.2 误区二函数参数修改# 可变对象作为默认参数——经典的坑defadd_item(item,my_list[]):# ❌ 有坑my_list.append(item)returnmy_listprint(add_item(1))# [1]print(add_item(2))# [1, 2]——默认列表在多次调用间共享了# ✅ 正确的写法defadd_item(item,my_listNone):ifmy_listisNone:my_list[]my_list.append(item)returnmy_list7.3 误区三变量必须先定义在Python中变量在使用前必须被赋值。没有声明的变量不能直接使用print(undefined_variable)NameError:nameundefined_variableisnotdefined这和JavaScript等语言不同——在JS中未声明的变量是undefined值在Python中直接报NameError。八、本篇小结✅ 今天我们深入了Python变量的底层机制。核心收获变量不是盒子是标签赋值 贴标签重新赋值 换标签一切皆对象每个对象有id、type、value三个基本属性可变与不可变int/str/tuple是不变的list/dict/set是可变的多重赋值和解包a, b b, a一行交换变量值引用计数Python用引用计数垃圾回收自动管理内存命名三规则字母数字下划线、不能数字开头、不能是关键字蛇形命名student_name全小写下划线 理解变量是对对象的引用是区分会用Python和真正理解Python的分水岭。下一篇我们将系统学习Python的命名规范和命名习惯。
Python基础:变量与赋值的底层原理是什么
Python基础变量与赋值的底层原理是什么一、开篇变量不是盒子如果你学过其他语言你可能被教过变量就像一个盒子你把值放进去。这个比喻对于初学者理解基本概念有帮助但对于Python来说这个比喻是错误的。⌨️ 在Python中变量不是一个装东西的盒子而是一个贴了标签的箭头——标签贴在对象上指向那个对象。理解这个区别是真正理解Python的第一步。当我写x 5时Python做的事情是在内存中创建一个整数对象5创建一个名字x让x指向那个整数对象变量x本身不存储值它存储的是对对象的引用。二、Python变量模型的底层原理2.1 一切皆对象在Python中一切皆对象。数字是对象字符串是对象函数是对象类也是对象。每个对象有三个基本属性id身份标识对象在内存中的唯一编号type类型对象的类型value值对象的值x42# 用三个内置函数查看对象的三个基本属性print(fid:{id(x)})# 内存地址的整数表示print(ftype:{type(x)})# class intprint(fvalue:{x})# 42id()返回的数值在不同的运行环境下会不同但在一次运行中每个对象的id是唯一的。你可以把id理解为对象的身份证号码。2.2 赋值 贴标签x100# 创建整数对象100贴上标签xyx# 把标签y也贴到同一个对象上z100# Python发现已经有一个100的对象了把z也指向它print(fx的id:{id(x)})print(fy的id:{id(y)})print(fz的id:{id(z)})# 三个id完全相同x、y、z都指向同一个对象用图解来理解x 100 → x ──→ [100对象] y x → y ──→ [100对象] ← x也指向这里 z 100 → z ──→ [100对象] ← x和y也指向这里三个变量指向的是同一个整数对象。赋值y x并没有创建一个新的整数100而是让y也指向x指向的那个100。2.3 重新赋值 换标签x100print(fx的id:{id(x)})# 某个id比如 140735...x200# 重新赋值print(fx的id:{id(x)})# id变了指向了一个新的200对象print(f类型:{type(x)})# 还是int 关键理解x 200不是在原来的盒子里把100换成200而是让标签x从100对象上撕下来贴到一个新的200对象上。原来的100对象并没有被修改整数在Python中是不可变的。如果还有其他变量指向它它继续存在。如果没有它就会被Python的垃圾回收机制清理掉。2.4 深入理解可变对象和不可变对象的区别这是变量模型中最重要的概念。Python的对象分为两类类别类型举例特点不可变int, float, str, tuple, frozenset, bool对象创建后其值不能被改变可变list, dict, set, bytearray, 自定义对象对象创建后其值可以被改变这个区别在多个变量指向同一个对象时尤其重要# 不可变对象inta10ba# b也指向10b20# b指向了新对象20a不受影响print(a)# 10a还是指向10print(b)# 20# 可变对象lista[1,2,3]ba# b也指向同一个列表b.append(4)# 通过b修改了那个列表print(a)# [1, 2, 3, 4] ← a也变了print(b)# [1, 2, 3, 4]⚠️ 第二个例子非常关键a [1, 2, 3]然后b a此时a和b指向同一个列表对象。当你通过b.append(4)修改这个列表时a看到的列表也被修改了——因为它们指向同一个对象。a [1, 2, 3] → a ──→ [[1, 2, 3]] b a → b ──→ 同一个[[1, 2, 3]] b.append(4) → 列表变成[[1, 2, 3, 4]]a和b都指向它三、命名规则详解3.1 硬性规则必须遵守Python的变量命名有三条硬性规则规则一只能包含字母、数字、下划线不能有空格和特殊字符# ✅ 合法name小明user_namexiaominguser1用户1_private私有变量# ❌ 不合法user-name用户# 不能用连字符1user用户# 不能以数字开头user name用户# 不能有空格username用户# 不能有特殊字符规则二不能以数字开头# ✅ 合法user1正确_1st_user正确# ❌ 不合法1st_user错误# SyntaxError规则三不能使用Python关键字Python有35个保留关键字不能用作变量名# 查看所有关键字importkeywordprint(keyword.kwlist)# [False, None, True, and, as, assert, async,# await, break, class, continue, def, del, elif,# else, except, finally, for, from, global, if,# import, in, is, lambda, nonlocal, not, or,# pass, raise, return, try, while, with, yield]# ❌ 这些都会报SyntaxErrorif10classPythonfor3 一个简单的记忆方法如果你在IDE中输入一个词后它自动变色了就说明它是关键字不能用作变量名。3.2 软性规范强烈建议遵守规范一使用描述性的名字# ✅ 好的命名——一眼就知道变量是干什么的student_count42average_score85.5is_user_logged_inTrueuser_namexiaoming# ❌ 不好的命名——看不出含义a42x85.5bTruenxiaoming除了循环计数器i、j、k和临时变量外避免使用单个字母的变量名。规范二蛇形命名法Python社区约定使用snake_case蛇形命名法全小写字母单词之间用下划线连接。# ✅ 推荐student_name小明max_connection_count100calculate_average_scoreTrue# ❌ 其他语言风格的命名在Python中不推荐studentName小明# camelCase驼峰命名Java/JS风格StudentName小明# PascalCase帕斯卡命名C#风格MAX_CONNECTIONS100# 全大写通常用于常量不用于普通变量规范三使用正向命名# ✅ 推荐正向表达is_validTruehas_permissionFalsecontains_dataTrue# ❌ 不推荐双重否定让人费解is_not_invalidTrue# 意思是有效not_emptyTrue# 双重否定烧脑四、Python的赋值机制深入4.1 多重赋值Python支持将同一个值赋给多个变量xyz0print(x,y,z)# 0 0 0# 实际上三者指向同一个对象print(id(x),id(y),id(z))# 三者的id相同# 对可变对象要注意ab[]a.append(1)print(b)# [1] ← b也变了4.2 序列解包赋值这是Python最优雅的特性之一# 基础用法x,y,z1,2,3print(x,y,z)# 1 2 3# 交换两个变量的值不需要临时变量a,b10,20a,bb,aprint(a,b)# 20 10# 从函数返回多个值defget_user_info():return小明,25,北京name,age,cityget_user_info()print(f{name},{age}岁,{city})# 解包列表colors[红,绿,蓝]r,g,bcolorsprint(r,g,b)# 红 绿 蓝4.3 星号解包# 用 * 收集多余的元素first,*middle,last[1,2,3,4,5,6]print(first)# 1print(middle)# [2, 3, 4, 5]print(last)# 6# 常见用法head,*tail[1,2,3,4,5]print(head)# 1print(tail)# [2, 3, 4, 5]*body,foothelloprint(body)# [h, e, l, l]print(foot)# o4.4 链式比较和赋值# 比较运算符的链式写法x5print(1x10)# True相当于 1 x and x 10print(1x5)# True# 但是连续的赋值和比较不能混用# a b c d # 这行代码很难读不要这么写4.5 增量赋值x10x5# 等价于 x x 5x-3# 等价于 x x - 3x*2# 等价于 x x * 2x/4# 等价于 x x / 4x//2# 等价于 x x // 2x%3# 等价于 x x % 3x**2# 等价于 x x ** 2⚠️ 对于不可变对象x 1创建了新对象。对于可变对象list_a [4]在原地修改# 不可变x10old_idid(x)x5new_idid(x)print(old_idnew_id)# Falsex指向了新对象# 可变lst[1,2,3]old_idid(lst)lst[4,5]new_idid(lst)print(old_idnew_id)# Truelst还是同一个列表对象五、内存管理与引用计数5.1 引用计数的概念Python使用引用计数来管理内存。每个对象内部都有一个计数器记录有多少个引用变量指向它。importsys a[]# 列表对象的引用计数 1a指向它print(sys.getrefcount(a)-1)# 减去getrefcount函数参数自身的引用ba# 引用计数 2a和b都指向它ca# 引用计数 3dela# 引用计数 2delb# 引用计数 1delc# 引用计数 0 → 对象被销毁当一个对象的引用计数降到0时Python的垃圾回收器会自动回收它占用的内存。5.2 小整数缓存Python为了提高性能会缓存一些常用的小整数。在CPython官方Python实现中-5到256之间的整数在解释器启动时就预创建好了以后使用这些数值时直接复用a256b256print(aisb)# True同一个对象a257b257print(aisb)# False不同的对象⚠️ 这是一个实现细节不是语言特性。不要依赖这个行为来写代码。判断两个值是否相等永远用而不是is。5.3 字符串驻留Interning类似的Python也会缓存一些短字符串ahellobhelloprint(aisb)# True同一个对象# 但长字符串或有空格的字符串不一定ahello world!bhello world!print(aisb)# 可能是True也可能是False不确定再次强调判断值相等用判断是否是同一个对象用is。六、类型系统动态类型的利与弊6.1 动态类型 vs 静态类型Python是动态类型语言变量没有固定的类型同一个变量可以先指向字符串再指向数字再指向列表。thinghello# thing是字符串print(type(thing))# class strthing42# thing现在是整数print(type(thing))# class intthing[1,2,3]# thing现在是列表print(type(thing))# class list动态类型的优点代码更简洁不需要声明类型灵活同一个函数可以处理多种类型的数据开发速度快动态类型的缺点类型错误只能在运行时发现IDE能帮一些忙大型项目中不够安全6.2 类型推断从Python 3.5开始支持可选的类型注解# 类型注解——告诉IDE和代码阅读者变量的预期类型name:str小明# name应该是字符串age:int25# age应该是整数scores:list[int][85,92,78]# scores是整数列表# 函数类型注解defgreet(name:str,age:int)-str:参数是字符串和整数返回值是字符串returnf{name}今年{age}岁类型注解不影响运行——Python解释器不会检查类型。但它们能帮助IDE提供更好的代码补全和错误提示。七、常见误区7.1 误区一赋值会拷贝# ❌ 很多新手的误解original[1,2,3]copyoriginal# 这没有创建新列表copy.append(4)print(original)# [1, 2, 3, 4]——original被意外修改了# ✅ 正确的拷贝方式original[1,2,3]copyoriginal.copy()# 方法一浅拷贝copyoriginal[:]# 方法二切片copylist(original)# 方法三list()构造函数importcopy copycopy.deepcopy(original)# 方法四深拷贝处理嵌套结构7.2 误区二函数参数修改# 可变对象作为默认参数——经典的坑defadd_item(item,my_list[]):# ❌ 有坑my_list.append(item)returnmy_listprint(add_item(1))# [1]print(add_item(2))# [1, 2]——默认列表在多次调用间共享了# ✅ 正确的写法defadd_item(item,my_listNone):ifmy_listisNone:my_list[]my_list.append(item)returnmy_list7.3 误区三变量必须先定义在Python中变量在使用前必须被赋值。没有声明的变量不能直接使用print(undefined_variable)NameError:nameundefined_variableisnotdefined这和JavaScript等语言不同——在JS中未声明的变量是undefined值在Python中直接报NameError。八、本篇小结✅ 今天我们深入了Python变量的底层机制。核心收获变量不是盒子是标签赋值 贴标签重新赋值 换标签一切皆对象每个对象有id、type、value三个基本属性可变与不可变int/str/tuple是不变的list/dict/set是可变的多重赋值和解包a, b b, a一行交换变量值引用计数Python用引用计数垃圾回收自动管理内存命名三规则字母数字下划线、不能数字开头、不能是关键字蛇形命名student_name全小写下划线 理解变量是对对象的引用是区分会用Python和真正理解Python的分水岭。下一篇我们将系统学习Python的命名规范和命名习惯。