变量是编程的地基,极其重要,内容多、细节多,一定要花足够时间反复理解、多写代码练熟。

1. 变量定义与基本赋值


  1. 变量的本质
    • 定义:变量是存储数据的容器,其值可以动态改变,但通常不改变数据类型。Python 中变量无需显式声明,直接赋值即可创建。
  • 示例:
1
2
3
age = 25          # 整数类型
name = "Alice" # 字符串类型
is_valid = True # 布尔类型
  1. 赋值语法
    • 基本语法:变量名 = 表达式
      • 右侧表达式会被计算后赋值给左侧变量。
      • 变量名必须符合标识符规则(字母、下划线开头,区分大小写,非关键字)。
  • 示例:
1
2
x = 10 + 5        # x = 15
y = x * 2 # y = 30

2. 多变量赋值


  1. 同时赋相同值
    • 语法:var1 = var2 = ... = value
      • 右侧表达式先计算,再依次赋值给所有变量。
  • 示例:
1
a = b = c = 10    # a、b、c均为10 
  1. 同时赋不同值
    • 语法:var1, var2, ... = val1, val2, ...
      • 右侧值的数量需与左侧变量一致,顺序对应。
  • 示例:
1
x, y, z = 1, 2.5, "Hello"  # x=1, y=2.5, z="Hello" 
  1. 变量交换
    • 技巧:通过多重赋值实现,无需临时变量。
  • 示例:
1
2
a, b = 10, 20
a, b = b, a # a=20, b=10

3. 增强赋值语句


  • 语法与特性
    • 增强赋值符:+=-=*=/==%=>>=<<=&=^=|=
    • 等价关系:x += 1 等价于 x = x + 1,但x仅被求值一次,且可能原地修改对象。
  • 示例:
1
2
count = 5
count += 3 # count = 8(等价于 count = count + 3)
  • 与普通赋值的区别
    • 求值顺序:增强赋值先计算左侧变量(如a[i] += 1先查找a[i]),而普通赋值先计算右侧。
    • 原地操作:对可变对象(如列表)直接修改,而非创建新对象。

4. 变量作用域与类型


  1. 作用域规则
    • 局部变量:在函数内部定义,仅在函数内有效。
    • 全局变量:在函数外定义,全局有效。需用global关键字在函数内修改。
  • 示例:
1
2
3
4
5
6
7
8
global_var = 100

def func():
global global_var
global_var = 200 # 修改全局变量

func()
print(global_var) # 输出200
  1. 动态类型特性
    • 类型自动推断:变量类型由赋值决定,可随时改变。
  • 示例:
1
2
3
x = 10          # x是整数
x = "text" # x变为字符串
X = 'text' # x变为字符串

5. 变量命名规范


  1. 命名规则(除了遵守标识符规则,还要易懂)
    • 允许字符:字母、数字、下划线(_),首字符不能是数字
    • 小写字母 + 下划线(蛇形命名):user_nametotal_price
    • 见名知意:agea
    • 常量一般全大写:PI = 3.14
    • 大驼峰:大驼峰法把第一个单词的首字母也大写了。常用于类名,函数名,属性,命名空间。例如:StudentInfo
    • 小驼峰:除第一个单词之外,其他单词首字母大写。例如:myStudentCount
    • 禁止使用:关键字(如iffor)、特殊符号(如@$
  • 示例:
1
2
user_age = 30    # 合法
2ndPlace = "John" # 非法(以数字开头)

⚠️注意:标识符是统称,变量名只是标识符的一种。

  • 标识符 = 所有你自己起的名字
    • 包括:变量名、函数名、类名、模块名、方法名
    • 只要是程序员自定义的名字,都叫标识符。
  • 变量名 = 用来标识变量的标识符
    • 所有变量名都是标识符
    • 但标识符不一定是变量名(还可以是函数名、类名等)

6. 链式赋值与对象引用


  1. 链式赋值
    • 特性:从右向左依次赋值,修改后续变量不影响前置变量。
  • 示例:
1
2
3
4
5
6
a = b = c = 10
b += 5

print("a = ",a) # 输出:a = 10
print("b = ",b) # 输出:b = 15
print("c = ",c) # 输出:c = 10

上述代码解释:

第一步 执行顺序 结果
a=b=c=10 先把10 赋值给 c
再把 10 赋值给 b
最后把10 赋值给a
a = 10
b = 10
c = 10
这三个变量各自独立,只是一开始值相同
第二步
b += 5 b += 5 等价于 b = b + 5
原来 b = 10
所以:b = 10 + 5 = 15
b = 15 这一步只修改了 b,和 a、c 没关系
  1. 可变对象与不可变对象
    • 不可变对象(如整数、字符串):修改值会创建新对象。
    • 可变对象(如列表、字典):修改内容不会改变对象标识。
  • 示例:
1
2
3
4
5
6
a = [1, 2, 3] 
b = a
a.append(4)

print("a = ",a) # a = [1,2,3,4]
print("b = ",b) # b = [1,2,3,4]

代码解释:

a = [1, 2, 3] 列表是对象,存在内存里,a 只是指向这个列表的标签
b = a 不是把列表复制一份给 b,
而是让 b 和 a 指向同一个列表对象
a.append(4) 是在原来那个列表里加了一个元素。
因为 a、b 都指着它,所以打印谁都变成 [1,2,3,4]
⚠️注意:
  • 数字、字符串:赋值是传值,改一个不影响另一个。
  • 表、字典等对象:赋值是传引用(贴标签),改一个,两个都变。
  1. 如果你想让 b 是独立副本、互不影响,可以这样写:
1
2
3
b = a.copy() 
# 或者
b = a[:]
  • 使用 b = a.copy()
1
2
3
4
5
6
7
a = [1, 2, 3] 
b = a.copy() # 复制一份新列表

a.append(4) # 只改 a

print("a =", a) # a = [1, 2, 3, 4]
print("b =", b) # b = [1, 2, 3]
  • 使用 b = a[:]
1
2
3
4
5
6
7
a = [1, 2, 3] 
b = a[:] # 切片方式复制整个列表

a.append(4)

print("a =", a) # a = [1, 2, 3, 4]
print("b =", b) # b = [1, 2, 3]

4. 浅拷贝 VS 深拷贝 **浅拷贝:只拷贝第一层,里面嵌套的东西还是共用**(只抄表面一层) **深拷贝:所有层都彻底复制,完全独立,互不影响**(连里面的东西一起全部抄一遍) - **==浅拷贝:==** - 简单说:你有一个盒子,盒子里还装着一个小盒子。 - 浅拷贝 = 只**复制一个新的大盒子** - 但**里面的小盒子还是原来那个**,没有复制 - 所以: - 改大盒子 → 互不影响 - 改里面的小盒子 → **两个都会变** - 示例:
1
2
3
4
5
6
7
a = [[1,2], 3] 
b = a.copy() # 浅拷贝

a[0].append(99)

print(a) # [[1, 2, 99], 3]
print(b) # [[1, 2, 99], 3] 也变了!
- **==深拷贝:==** - 深拷贝 = **大盒子复制一个,里面的小盒子也复制一个全新的** - 两个盒子完完全全独立,互不干扰 - 不管你改哪一层,都不会影响另一个。 - 示例:
1
2
3
4
5
6
7
8
import copy 
a = [[1,2], 3]
b = copy.deepcopy(a)

a[0].append(99)

print(a) # [[1, 2, 99], 3]
print(b) # [[1, 2], 3] 完全没变!
- **直接赋值 b = a**:完全共用,改一个全变 - **浅拷贝 b = a.copy ()**:外层独立,内层共用 - **深拷贝 b = copy.deepcopy (a)**:里外都独立,怎么改都互不影响

⚠️ 注意:对于列表这类可变对象,a += b 和 a = a + b 效果不一样

1
2
3
4
5
6
7
8
9
10
11
12
13
# 增强赋值:原地修改原对象 
a = [1,2]
b = a # b和a指向同一个列表
a += [3]
print(a) # 输出:[1,2,3]
print(b) # 输出:[1,2,3],b也跟着变了,因为a是原地修改

# 普通赋值:新建了一个新对象
a = [1,2]
b = a
a = a + [3]
print(b) # 输出:[1,2,3]
print(a) # 输出:[1,2],b没变,因为a指向了新的列表

7. 进阶应用📚


  1. 带标注的赋值
    • 语法:var: type = value
      • 标注变量类型(用于静态检查工具,不影响运行)
  • 示例:
1
2
age: int = 25 
age = "二十五" # 不会报错!

上述代码解释:

  • 第一行age: int = 25 是什么?

    • age = 变量名
    • : int = 类型提示 / 类型标注
    • = 25 = 真正赋值

    重点来了:
    : int 只是一个 “提醒”、一个 “注释”,不是强制规定!
    它的意思是:我建议这个变量存整数,大家看代码时注意一下。

==Python 不会管你,不会检查、不会拦着你。==

  • 第二行age = "二十五"为什么不报错?
    • 因为:Python 是动态类型语言
      变量是什么类型,只看你赋什么值,不看你标注什么。
    • 你写 age: int → 只是提醒
    • 你赋值 "二十五" → Python 就把它变成字符串
    • Python 根本不会检查你是不是违背了 : int
      所以完全不报错
    • 如果还是不理解可以看看这个比喻
      你在桌子上贴了个纸条:
    • 📝 此位置只放苹果(对应 : int
    • 然后你把一个梨放上去(对应赋值字符串)
      桌子不会报警,也不会把梨扔掉
    • Python 就是这张桌子,它只负责放东西,不负责检查纸条
  1. 解包赋值
    • 语法:a, * b, c =
      • a=1b=c=5
    • *变量名 专门用来打包剩下所有值,变成列表
  • 示例:
1
2
first, *rest = 11, 22, 33, 44, 55 
print(rest) # 输出:[22, 33, 44, 55]

上述代码解释:

  • 第一行 first, *rest = 11,22,33,44,55
    • first 拿走第一个数11
    • *rest 里的 * 意思是:
      把剩下所有的值,全部打包成一个列表,放进 rest
      所以:
  • first = 11
  • rest = [22, 33, 44, 55]
  • 第二行print(rest)
    • 就把这个列表打印出来。

⚠️ 注意:解包的时候数量要匹配。普通解包要求左边变量的数量,和右边序列的元素数量完全一致,不然会报错:

1
2
3
4
5
6
# 错误!左边2个变量,右边3个元素,不匹配
# a, b = [1,2,3] # 会抛出ValueError

# 正确的:用*收集剩余的元素
a, b, *rest = [1,2,3]
print(a, b, rest) # 输出:1 2 [3]

⚠️ 注意:不要把赋值和比较搞混。不要在比较的时候用 = ,赋值的 = 和比较的 == 是两个完全不同的运算符:

1
2
3
4
5
6
# 错误!这是赋值,不是比较,会直接报错
# if a = 5:

# 正确写法
if a == 5:
print("a等于5")

练习:


第一部分

  1. 下列哪些是合法的 Python 变量名?哪些不合法?请说明原因。

    • user_age
    • 2score
    • _name
    • my-name
    • if
    • 年龄
  2. 完成变量的基本定义与赋值:

    • 定义一个变量 score,赋值为 90
    • 定义一个变量 name,赋值为字符串 小明
  3. 多变量赋值:不使用临时变量,如何用一行代码交换变量 ab 的值?写出代码。

  4. 增强赋值语句:写出下面增强赋值语句的等价普通赋值写法。

    • a += 5 等价于:___
    • b *= 2 等价于:___
    • c -= 3 等价于:___
  5. 简单回答:Python 定义变量时,需要提前声明变量的类型吗?为什么?

  6. 链式赋值:执行 a = b = c = 10 后,变量 abc 的值分别是多少?

  7. 以下代码运行会报错,请指出错误原因:

1
2
print(x)
x = 10

第二部分

  1. 思考:如果在 if 语句块中定义了一个变量,在语句块外部能不能访问到这个变量?这和你之后要学的函数里的变量有什么不同?

  2. 增强赋值的隐藏区别:对于列表对象,a += [3]a = a + [3] 这两种写法完全等价吗?为什么?

  3. 对象引用问题:执行以下代码后,变量 b 的值会变成什么?请解释原因。

    1
    2
    3
    a = b = [1, 2]
    a.append(3)
    print(b)
  4. 多变量解包进阶:执行 a, b, *c = 1, 2, 3, 4 后,变量 c 的值是什么?这个特性在后续处理序列数据时非常常用。

  5. 常见错误调试:以下代码运行会报错,请指出错误类型和原因,并尝试改正。

1
2
3
4
5
x = 10
def test():
print(x)
x = 20
test()
  1. 思考:Python 语法上支持中文变量名吗?实际开发中为什么不推荐使用?

参考答案


第一部分

  1. 合法变量名判断

    • user_age:合法
    • 2score:不合法,变量名不能以数字开头
    • _name:合法
    • my-name:不合法,不能包含减号 -
    • if:不合法,if 是 Python 关键字
    • 年龄:语法合法(Python3 支持中文标识符)
  2. 变量定义补全

1
2
score = 90
name = "小明"
  1. 交换变量
1
a, b = b, a
  1. 增强赋值等价写法

    • a += 5 等价于:a = a + 5
    • b *= 2 等价于:b = b * 2
    • c -= 3 等价于:c = c - 3
  2. 变量类型声明

    不需要提前声明类型。Python 是动态类型语言,解释器会在运行时自动根据赋值的内容推断变量的类型。

  3. 链式赋值结果

    三个变量的值都是 10

  4. 错误原因

    变量 x 还未定义就被使用,会抛出 NameError 错误。Python 代码是从上到下顺序执行的,必须先定义变量,才能使用它。


第二部分

  1. if 语句块的变量作用域

    在 Python 中,ifforwhile 这类语句块不会创建新的作用域,所以块内定义的变量,外部可以直接访问。

    但后续要学的函数会创建独立的局部作用域,函数内定义的变量,外部默认无法访问。

  2. 列表的增强赋值区别

    不完全等价。

    • a += [3]原地修改列表 a,不会创建新的对象。

    • a = a + [3] 会先创建一个新的列表,再把它赋值给变量 a,会改变原变量的引用。

      这个区别在可变对象(比如列表)中才会体现,不可变对象(比如整数)中两者效果一致。

  3. 对象引用的影响

    b 的值会变成 [1, 2, 3]

    原因:a = b = [1,2] 是链式赋值,两个变量都指向了同一个列表对象。修改 a 指向的对象内容,b 因为指向同一个对象,所以也会看到变化。

  4. 解包结果

    c 的值是 [3, 4]

    * 的变量会收集剩余的所有元素,组成一个列表,这是 Python 中处理序列解包的常用特性。

  5. 错误调试

    错误类型:UnboundLocalError

    原因:函数内部对 x 进行了赋值,Python 会将 x 识别为局部变量,导致 print(x) 时局部变量还未定义。

    改正:如果要修改全局变量,需要加 global 声明:

1
2
3
4
5
6
x = 10
def test():
global x
print(x)
x = 20
test()
  1. 中文变量名

    语法上是合法的(Python3 支持 Unicode 标识符),但实际开发不推荐。

    原因:会降低代码的兼容性、可读性,不同编码环境可能出问题,也不符合通用的开发规范。