python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python赋值运算符与复合赋值

Python赋值运算符与复合赋值操作大全

作者:星河耀银海

本文全面解析Python赋值运算符,从基本赋值到复合赋值操作,包括+=、-=等,再到元解包赋值与海象运算符:=,并探讨其底层机制与应用场景,助你写出更简洁高效的Python代码

一、开篇:=不仅仅是"等于"

在Python中,等号=可能是你写得最多的符号了。但很多初学者对它存在一个根本性的误解——把=理解为数学中的"等于"。实际上,=是一个赋值运算符,它的作用是"把右边的值存到左边的变量里",而不是"左边等于右边"。

⌨️ 来看一段代码:

x = 10          # 把10赋值给x
x = x + 1       # 数学上这不可能成立!但编程中这是合法的
print(x)        # 11 —— x的值被更新了

在数学中,x = x + 1是荒谬的(一个数不可能等于它自己加1),但在编程中,它的含义是"取出x当前的值,加1,再把结果存回x"。这是赋值运算与数学等式的本质区别。

💡 今天这篇文章,我们就来全面掌握Python中的赋值运算符——从最基本的=到各种复合赋值运算符(+=-=等),再到元组解包赋值、链式赋值等高级技巧,让你对赋值操作有通透的理解。

二、基本赋值运算符 =

2.1 赋值的本质:名称绑定

在Python中,赋值不是"把值放进变量"那么简单——而是把一个名称绑定到一个对象上

# 赋值 = 名称绑定
x = 42
# 含义:把名称x绑定到整数对象42上
# 可以用id()函数验证
x = [1, 2, 3]
y = x              # y和x绑定到同一个列表对象
print(id(x))       # 例如:140234567890
print(id(y))       # 和x相同!
print(x is y)      # True —— 确实是同一个对象
# 修改通过任何一个名字都会影响另一个
x.append(4)
print(y)           # [1, 2, 3, 4] —— y也变了!

2.2 赋值的执行顺序:先右后左

赋值语句的执行顺序是先计算右边,再绑定到左边

# Python执行 a = b + c 的步骤:
# 1. 计算右边表达式 b + c,得到一个值
# 2. 将这个值绑定到左边的名称 a 上
a = 1
b = 2
c = a + b    # 先计算 a + b = 3,再把3绑定到c
print(c)     # 3
# 交换变量值——利用了这个特性
a, b = b, a   # 先计算右边(b, a)得到元组(2, 1),再解包赋值
print(a, b)   # 2 1
# 更复杂的例子
x = 1
x = x + (x := 5)  # 先算右边的表达式
# 右边计算过程:x + (x := 5) = 1 + 5 = 6
# 但同时x被重新绑定为5(海象运算符)
print(x)      # 6 —— 最终x被绑定到右边表达式的计算结果

2.3 赋值的几种形式

# 1. 基本赋值
name = "Python"
age = 30
pi = 3.14159
# 2. 多重赋值(链式赋值)—— 多个变量绑定到同一个对象
a = b = c = 0
print(a, b, c)     # 0 0 0
# ⚠️ 注意:对于可变对象,链式赋值会导致所有变量指向同一对象
a = b = c = []
a.append(1)
print(b)           # [1] —— b和c也变了!
print(c)           # [1]
# 对于不可变对象则没有这个问题
a = b = c = 10
a = 20             # 只是让a绑定到新对象20,不影响b和c
print(b, c)        # 10 10
# 3. 元组解包赋值(平行赋值)
x, y, z = 1, 2, 3
print(x, y, z)     # 1 2 3
# 元组解包赋值的强大之处
# 一行代码交换两个变量的值
a, b = b, a
# 从函数返回多个值
def get_user():
    return "张三", 25, "zhangsan@example.com"
name, age, email = get_user()
print(name, age, email)
# 4. 列表解包赋值
colors = ["red", "green", "blue"]
r, g, b = colors
print(r, g, b)     # red green blue
# 5. 星号表达式解包 (Python 3.0+)
first, *middle, last = [1, 2, 3, 4, 5]
print(first)       # 1
print(middle)      # [2, 3, 4]
print(last)        # 5
*head, tail = [1, 2, 3, 4]
print(head)        # [1, 2, 3]
print(tail)        # 4

三、复合赋值运算符

3.1 为什么需要复合赋值运算符

复合赋值运算符让代码更简洁,而且它们常常比手写展开式更高效:

# 不用复合赋值
x = x + 5
x = x - 3
x = x * 2
# 用复合赋值
x += 5    # 等价于 x = x + 5
x -= 3    # 等价于 x = x - 3
x *= 2    # 等价于 x = x * 2

而且复合赋值运算符对可变对象的处理也不同:

# 对于列表而言
nums = [1, 2, 3]
# 方式1:用 + 再赋值——创建了新列表
nums = nums + [4, 5]
print(nums)    # [1, 2, 3, 4, 5]
# 方式2:用 += —— 原地修改(in-place)
nums = [1, 2, 3]
nums += [4, 5]
print(nums)    # [1, 2, 3, 4, 5]
# 两者的区别在内存上:
import timeit
# += 更高效,因为不创建中间列表

3.2 所有复合赋值运算符一览

Python共提供11种复合赋值运算符:

# 1. += 加法赋值
x = 10
x += 5       # x = x + 5
print(x)     # 15
# 对于列表
lst = [1, 2]
lst += [3, 4]   # extend的效果
print(lst)      # [1, 2, 3, 4]
# 对于字符串
s = "Hello"
s += " World"   # 创建了新字符串(字符串不可变)
print(s)        # Hello World
# 2. -= 减法赋值
x = 10
x -= 3       # x = x - 3
print(x)     # 7
# 3. *= 乘法赋值
x = 5
x *= 3       # x = x * 3
print(x)     # 15
# 字符串重复
s = "Hi"
s *= 3
print(s)     # HiHiHi
# 列表重复
lst = [0]
lst *= 5
print(lst)   # [0, 0, 0, 0, 0]
# ⚠️ 注意:列表的 *= 有个陷阱!
matrix = [[0] * 3] * 3   # 不要这样创建二维列表!
print(matrix)  # [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
matrix[0][0] = 1
print(matrix)  # [[1, 0, 0], [1, 0, 0], [1, 0, 0]] —— 三行都变了!
# ✅ 正确的二维列表创建方式
matrix = [[0] * 3 for _ in range(3)]
matrix[0][0] = 1
print(matrix)  # [[1, 0, 0], [0, 0, 0], [0, 0, 0]]
# 4. /= 除法赋值(结果总是浮点数)
x = 10
x /= 3       # x = x / 3
print(x)     # 3.3333333333333335
print(type(x))  # <class 'float'>
# 5. //= 整除赋值(结果向下取整)
x = 10
x //= 3      # x = x // 3
print(x)     # 3
print(type(x))  # <class 'int'>
# 负数陷阱
x = -10
x //= 3      # -10 // 3 = -4(向下取整)
print(x)     # -4
# 6. %= 取模赋值
x = 10
x %= 3       # x = x % 3
print(x)     # 1
# 7. **= 幂赋值
x = 2
x **= 10     # x = x ** 10
print(x)     # 1024
# 8. &= 按位与赋值
x = 0b1100   # 12
x &= 0b1010  # x = x & 0b1010
print(bin(x))  # 0b1000 (8)
# 9. |= 按位或赋值
x = 0b1100   # 12
x |= 0b0011  # x = x | 0b0011
print(bin(x))  # 0b1111 (15)
# 10. ^= 按位异或赋值
x = 0b1100   # 12
x ^= 0b1010  # x = x ^ 0b1010
print(bin(x))  # 0b110 (6)
# 11. >>= 右移赋值
x = 16
x >>= 2      # x = x >> 2
print(x)     # 4
# 12. <<= 左移赋值
x = 4
x <<= 2      # x = x << 2
print(x)     # 16

3.3 复合赋值 ≠ 简单展开

一个关键的知识点:复合赋值运算符不完全等价于先运算再赋值

# 对于可变对象的 += 操作
# 情况1:列表的 += 调用 __iadd__(原地修改)
lst = [1, 2, 3]
old_id = id(lst)
lst += [4, 5]          # 调用 __iadd__,原地修改
new_id = id(lst)
print(old_id == new_id)  # True —— 还是同一个列表对象!
print(lst)               # [1, 2, 3, 4, 5]
# 情况2:lst = lst + [4, 5] 创建了新列表
lst = [1, 2, 3]
old_id = id(lst)
lst = lst + [4, 5]     # 先创建新列表,再赋值
new_id = id(lst)
print(old_id == new_id)  # False —— 创建了新对象!
print(lst)               # [1, 2, 3, 4, 5]
# 对于元组(不可变对象)
t = (1, 2, 3)
old_id = id(t)
t += (4, 5)            # 元组没有__iadd__,等价于 t = t + (4, 5)
new_id = id(t)
print(old_id == new_id)  # False —— 创建了新元组
print(t)                 # (1, 2, 3, 4, 5)

四、元组解包赋值深度解析

4.1 基本解包

# 等号两边元素数量必须一致
a, b, c = 1, 2, 3       # ✅ 3个变量,3个值
print(a, b, c)
# a, b = 1, 2, 3        # ❌ ValueError: too many values to unpack
# a, b, c = 1, 2        # ❌ ValueError: not enough values to unpack
# 可以解包任何可迭代对象
# 列表
x, y, z = [10, 20, 30]
# 元组
p, q = (100, 200)
# 字符串
c1, c2, c3 = "ABC"
print(c1, c2, c3)    # A B C
# 字典 —— 解包的是键
d = {"name": "张三", "age": 25}
k1, k2 = d
print(k1, k2)        # name age
# 集合
a, b = {1, 2}
print(a, b)          # 1 2(顺序不保证)
# 生成器
def gen():
    yield "x"
    yield "y"
v1, v2 = gen()
print(v1, v2)        # x y

4.2 星号表达式解包

Python 3引入的星号表达式让解包更灵活:

# *变量 收集剩余元素
first, *rest = [1, 2, 3, 4, 5]
print(first)    # 1
print(rest)     # [2, 3, 4, 5] —— 剩余元素组成列表
# *放在中间
first, *middle, last = [1, 2, 3, 4, 5]
print(first)    # 1
print(middle)   # [2, 3, 4]
print(last)     # 5
# *放在开头
*head, last1, last2 = [1, 2, 3, 4, 5]
print(head)     # [1, 2, 3]
print(last1)    # 4
print(last2)    # 5
# 一个例外:* 可以收集0个元素
first, *rest = [1]
print(first)    # 1
print(rest)     # [] —— 空列表
# 但不能有两个 *
# first, *a, *b = [1, 2, 3, 4]  # SyntaxError!
# 实用案例:忽略不关心的元素
# 只获取第一个和最后一个
first, *_, last = range(100)
print(first, last)    # 0 99
# 或者忽略某些返回值
name, _, age, *_ = "张三", "男", 25, "北京", "工程师", "138xxxx"
print(name, age)      # 张三 25(只取名字和年龄,后面全忽略)

4.3 深度解包(嵌套解包)

# 嵌套元组解包
(a, b), (c, d) = [(1, 2), (3, 4)]
print(a, b, c, d)    # 1 2 3 4
# 更复杂的嵌套
name, (street, city, zipcode) = "张三", ("中山路100号", "北京", "100000")
print(name, street, city, zipcode)
# 张三 中山路100号 北京 100000
# 三层嵌套
data = ("Python", (3, (10, 2)))
lang, (major, (minor, patch)) = data
print(lang, major, minor, patch)
# Python 3 10 2
# 与 * 结合使用
(a, *b), (c, d) = [(1, 2, 3), (4, 5)]
print(a, b, c, d)    # 1 [2, 3] 4 5
# 实际应用:解析JSON数据
user_data = {
    "name": "张三",
    "scores": [85, 92, 78, 95, 88]
}
name = user_data["name"]
first, *middle, last = user_data["scores"]
print(f"{name}: 首考{first}, 末考{last}, 中间成绩{middle}")
# 张三: 首考85, 末考88, 中间成绩[92, 78, 95]

4.4 实际开发中的解包应用

# 1. 交换变量 —— Python最优雅的特性之一
a, b = 10, 20
a, b = b, a
print(a, b)    # 20 10
# 不需要临时变量!
# 其他语言: temp = a; a = b; b = temp;
# 2. 遍历字典的键值对
user = {"name": "张三", "age": 25, "city": "北京"}
for key, value in user.items():
    print(f"{key}: {value}")
# 3. enumerate 同时获取索引和值
fruits = ["苹果", "香蕉", "橘子"]
for i, fruit in enumerate(fruits):
    print(f"{i}: {fruit}")
# 4. zip 并行遍历
names = ["张三", "李四", "王五"]
scores = [85, 92, 78]
for name, score in zip(names, scores):
    print(f"{name}: {score}分")
# 5. 函数返回多个值
def min_max_avg(numbers):
    """返回列表的最小值、最大值和平均值"""
    return min(numbers), max(numbers), sum(numbers) / len(numbers)
scores = [85, 92, 78, 95, 88]
lowest, highest, average = min_max_avg(scores)
print(f"最低分: {lowest}, 最高分: {highest}, 平均分: {average:.1f}")
# 6. 分割字符串
ip = "192.168.1.100"
a, b, c, d = ip.split('.')
print(a, b, c, d)    # 192 168 1 100
# 7. 文件读取 —— 经典的头部/尾部模式
with open("example.txt", "r") as f:
    lines = f.readlines()
header, *body, footer = lines
# header: 第一行(标题)
# body: 中间所有行(内容)
# footer: 最后一行(总结)
# 8. 命令行参数解析
import sys
# python script.py input.txt output.txt --verbose
script, *files, flag = sys.argv
print(f"脚本: {script}")
print(f"文件: {files}")
print(f"标志: {flag}")

五、海象运算符 := (Python 3.8+)

5.1 什么是海象运算符

海象运算符:=(因为长得像海象的眼睛和獠牙)是Python 3.8引入的特性,它允许在表达式中进行赋值

# 传统写法:分两步
n = len("hello")
if n > 3:
    print(f"长度{n}大于3")
# 海象运算符:一步到位
if (n := len("hello")) > 3:
    print(f"长度{n}大于3")
# 注意:海象运算符的优先级很低,通常需要加括号

5.2 海象运算符的典型应用场景

# 场景1:while循环中避免重复计算
# 传统写法 —— input()写了两次
line = input("请输入: ")
while line != "quit":
    print(f"你输入了: {line}")
    line = input("请输入: ")
# 海象运算符写法 —— 优雅!
while (line := input("请输入: ")) != "quit":
    print(f"你输入了: {line}")
# 场景2:读取文件直到结束
# 传统写法
with open("data.txt") as f:
    chunk = f.read(1024)
    while chunk:
        process(chunk)
        chunk = f.read(1024)
# 海象运算符写法
with open("data.txt") as f:
    while chunk := f.read(1024):
        process(chunk)
# 场景3:正则匹配
import re
pattern = re.compile(r'\d+')
text = "订单号: 12345, 金额: 678.90"
# 传统写法
match = pattern.search(text)
if match:
    print(f"找到数字: {match.group()}")
# 海象运算符写法
if match := pattern.search(text):
    print(f"找到数字: {match.group()}")
# 场景4:列表推导式中复用计算
# 传统写法(需要计算两次)
data = ["  123  ", "456", "  789  "]
cleaned = [int(s.strip()) for s in data if len(s.strip()) > 2]
# 海象运算符写法(只计算一次strip())
cleaned = [int(stripped) for s in data if len(stripped := s.strip()) > 2]
print(cleaned)  # [123, 789]
# 场景5:累积计算
def running_average():
    """生成移动平均值"""
    total = 0
    count = 0
    while (value := yield) is not None:
        total += value
        count += 1
        yield total / count

5.3 海象运算符的使用限制

# ❌ 不能在简单赋值语句中使用
# x := 5    # SyntaxError!
# ✅ 必须在表达式中使用
y = (x := 5)     # 可以,y和x都是5
# ❌ 不能用在 f-string 中(某些版本)
# print(f"{(x := 5)}")   # SyntaxError
# ✅ 先赋值再使用
x = 5
print(f"{x}")
# ⚠️ 海象运算符的优先级最低,注意括号
# x := 5 > 3    # 等价于 x := (5 > 3),即 x := True
# 建议总是加括号: (x := 5) > 3

六、增强赋值运算符的底层机制

6.1iadd等方法

增强赋值运算符在底层调用特殊的dunder方法:

# x += y 的底层执行过程:
# 1. 如果x有__iadd__方法,调用 x = x.__iadd__(y)
# 2. 否则,回退到 x = x.__add__(y)
# 演示:自定义类的增强赋值
class ShoppingCart:
    def __init__(self):
        self.items = []
    def __iadd__(self, item):
        """支持 += 添加商品"""
        self.items.append(item)
        return self  # ⚠️ 必须返回self!
    def __isub__(self, item):
        """支持 -= 移除商品"""
        if item in self.items:
            self.items.remove(item)
        return self
cart = ShoppingCart()
cart += "苹果"
cart += "香蕉"
cart += "橘子"
print(cart.items)    # ['苹果', '香蕉', '橘子']
cart -= "香蕉"
print(cart.items)    # ['苹果', '橘子']
# ⚠️ 如果不返回self,赋值后的变量会变成None!
class BadList:
    def __init__(self):
        self.data = []
    def __iadd__(self, item):
        self.data.append(item)
        # 忘记 return self!
bl = BadList()
# bl += 1   # 错误!bl会变成None

6.2 可变与不可变对象的增强赋值区别

# 不可变对象(int, str, tuple等):增强赋值始终创建新对象
x = 10
old_id = id(x)
x += 5
print(id(x) == old_id)    # False —— 新对象
# 可变对象(list, dict, set等):增强赋值通常就地修改
lst = [1, 2, 3]
old_id = id(lst)
lst += [4]                # 等价于 lst.extend([4])
print(id(lst) == old_id)  # True —— 同一个对象
# 但!如果增强赋值的右边是不同类型,会创建新对象
lst = [1, 2, 3]
old_id = id(lst)
lst += (4, 5)             # (4, 5)是元组,不是列表
print(id(lst) == old_id)  # True —— 就地修改(元组可迭代,extend接受)
# 注意这个特殊情况
t = (1, 2, [3, 4])
try:
    t[2] += [5]    # 既会就地修改列表,又会抛出异常!
except TypeError as e:
    print(f"异常: {e}")
print(t)   # (1, 2, [3, 4, 5]) —— 列表还是被修改了!
# 原因:
# 1. t[2] 取值得到列表 [3, 4]
# 2. [3, 4].__iadd__([5]) 就地修改 → [3, 4, 5]
# 3. 尝试赋值回 t[2](元组不可变)→ TypeError
# 但列表已经就地修改了!

七、常见赋值场景与最佳实践

7.1 变量交换

# Python的方式 —— 最优雅
a, b = b, a
# 其他语言的方式(Python中也可以用,但没必要)
temp = a
a = b
b = temp
# 异或交换技巧(Python中不推荐,降低可读性)
a = a ^ b
b = a ^ b
a = a ^ b

7.2 一行初始化多个变量

# 场景1:计数器归零
success = fail = skip = 0
# 场景2:坐标初始化
x = y = z = 0
# 但可变对象不要这样用!
# list_a = list_b = []   # ❌ 两个名字指向同一个空列表
list_a = []
list_b = []               # ✅ 两个独立的空列表

7.3 条件赋值

# 常见的条件赋值场景
# 方式1:三元表达式
status = "成年" if age >= 18 else "未成年"
# 方式2:逻辑短路
name = user_input or "匿名用户"  # 如果user_input为空,用默认值
# 方式3:字典get方法
config = config_dict.get(key, default_value)
# 方式4:try-except
try:
    value = int(user_input)
except ValueError:
    value = 0

7.4 解包赋值的错误处理

def safe_unpack(iterable, n, default=None):
    """安全解包:如果元素不够,用default填充"""
    it = iter(iterable)
    result = []
    for _ in range(n):
        try:
            result.append(next(it))
        except StopIteration:
            result.append(default)
    return tuple(result)
# 使用
data = [1, 2]
a, b, c = safe_unpack(data, 3, 0)
print(a, b, c)    # 1 2 0
# 或者使用 * 配合默认值
data = [1, 2]
a, b, *rest = data
# 如果rest为空,给它一个默认
rest = rest if rest else [0]
print(a, b, rest)  # 1 2 [0]

八、实战案例

8.1 累计统计工具

class Statistics:
    """在线统计(流式处理),使用复合赋值累计"""
    def __init__(self):
        self.count = 0
        self.sum = 0.0
        self.sum_sq = 0.0  # 平方和,用于计算方差
        self.min_val = float('inf')
        self.max_val = float('-inf')
    def add(self, value):
        """添加一个值,O(1)时间更新所有统计量"""
        self.count += 1
        self.sum += value
        self.sum_sq += value ** 2
        if value < self.min_val:
            self.min_val = value
        if value > self.max_val:
            self.max_val = value
    @property
    def mean(self):
        return self.sum / self.count if self.count > 0 else 0
    @property
    def variance(self):
        if self.count < 2:
            return 0
        return (self.sum_sq - self.sum ** 2 / self.count) / (self.count - 1)
    @property
    def std_dev(self):
        return self.variance ** 0.5
# 使用
stats = Statistics()
for score in [85, 92, 78, 95, 88, 76, 90]:
    stats.add(score)
print(f"样本数: {stats.count}")
print(f"总和: {stats.sum}")
print(f"均值: {stats.mean:.2f}")
print(f"最大值: {stats.max_val}")
print(f"最小值: {stats.min_val}")
print(f"标准差: {stats.std_dev:.2f}")

8.2 配置文件解析器

def parse_config(config_text):
    """解析简单配置文件,利用解包赋值"""
    config = {}
    for line in config_text.strip().split('\n'):
        line = line.strip()
        # 跳过空行和注释
        if not line or line.startswith('#'):
            continue
        # 利用解包赋值解析 key=value
        if '=' in line:
            key, value = line.split('=', 1)
            key = key.strip()
            value = value.strip()
            # 类型自动转换
            if value.lower() == 'true':
                value = True
            elif value.lower() == 'false':
                value = False
            elif value.isdigit():
                value = int(value)
            elif value.replace('.', '', 1).isdigit():
                value = float(value)
            config[key] = value
    return config
config_text = """
# 数据库配置
host = localhost
port = 3306
debug = false
timeout = 30.5
"""
config = parse_config(config_text)
for k, v in config.items():
    print(f"{k} = {v!r} ({type(v).__name__})")

九、本章小结

✅ 本文我们全面学习了Python中的赋值运算符:

掌握赋值运算符是Python编程的基础功,用对、用巧这些特性,能让代码更简洁、更Pythonic。⌨️ 下一篇文章,我们将学习逻辑运算符andornot的详细用法!

到此这篇关于Python基础:赋值运算符与复合赋值操作全解的文章就介绍到这了,更多相关Python赋值运算符与复合赋值内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文