3、运算符与表达式:算术运算符、比较运算符、逻辑运算符、赋值运算符、位运算符、运算符优先级及实际应用案例
运算符是Python语言中连接数据与操作的核心纽带,而表达式则是由运算符、操作数和函数调用按语法规则组合而成的可求值单元。在Python编程中,几乎所有逻辑判断、数值计算、状态切换和流程控制都依赖于对运算符的精确理解。本章将系统拆解六大类运算符的底层行为、使用边界与组合规则,并通过一个完整的“商品折扣计算器”案例,展示运算符在实际工程场景中的综合运用。理解运算符优先级与结合性,是避免隐蔽逻辑错误、写出可读性代码的关键前提。
3.1 算术运算符:从基础运算到高级技巧
算术运算符用于执行数学计算,Python提供了8种算术运算符,覆盖了从基本四则运算到幂运算、整除和取余的全部需求。其中+、-、*、/是最基础的运算符,而//(整除)、%(取余)、**(幂)则提供了更精细的数学控制能力。值得注意的是,Python 3中的/始终返回浮点数,即使两个整数相除也是如此,这与Python 2的行为有本质区别。
核心原理:整除运算符//执行向下取整(floor division),即结果向负无穷方向取整。例如-7 // 2的结果是-4,而不是-3。取余运算符%的结果符号与除数一致,满足恒等式a = (a // b) * b + a % b。幂运算符**支持浮点数指数,如9 ** 0.5返回3.0。
# 算术运算符综合示例
a, b = 17, 5
print(f"加法: {a + b}") # 22
print(f"减法: {a - b}") # 12
print(f"乘法: {a * b}") # 85
print(f"除法: {a / b}") # 3.4
print(f"整除: {a // b}") # 3
print(f"取余: {a % b}") # 2
print(f"幂运算: {a ** 2}") # 289
print(f"负整除: {-a // b}") # -4 (向下取整)
print(f"负取余: {-a % b}") # 3 (结果符号与除数一致)
判断方法:选择哪种算术运算符取决于业务需求。当需要精确的整数商时使用//;需要判断整除关系时检查a % b == 0;需要计算平方根时使用** 0.5或math.sqrt()。在金融计算中,应避免浮点数直接比较,可改用Decimal模块或整数分单位计算。
| 运算符 | 名称 | 示例 | 结果 | 注意事项 |
|---|---|---|---|---|
| + | 加法 | 7 + 3 | 10 | 也可用于字符串/列表拼接 |
| - | 减法 | 7 - 3 | 4 | 也可用于集合差集 |
| * | 乘法 | 7 * 3 | 21 | 字符串乘整数表示重复 |
| / | 除法 | 7 / 3 | 2.333... | 始终返回浮点数 |
| // | 整除 | 7 // 3 | 2 | 向下取整,注意负数行为 |
| % | 取余 | 7 % 3 | 1 | 结果符号与除数一致 |
| ** | 幂 | 2 ** 10 | 1024 | 支持浮点指数 |
3.2 比较运算符与链式比较
比较运算符用于判断两个值之间的关系,返回布尔值True或False。Python支持6种比较运算符:==(等于)、!=(不等于)、>(大于)、<(小于)、>=(大于等于)、<=(小于等于)。比较运算符可以应用于数值、字符串、列表、元组等所有可比较类型。字符串比较基于Unicode码点顺序,列表比较则按元素逐个比较。
核心原理:Python支持链式比较(chained comparison),即a < b < c等价于a < b and b < c,但链式写法更简洁且b只计算一次。比较运算符的另一个重要特性是,==比较的是值相等性(value equality),而is比较的是对象身份(identity)。对于不可变类型如整数、字符串,==和is通常结果一致,但对于可变对象如列表、字典,两者有本质区别。
# 链式比较与身份比较
age = 25
if 18 <= age < 60:
print("成年人且未退休") # 链式比较
# 值比较 vs 身份比较
list1 = [1, 2, 3]
list2 = [1, 2, 3]
list3 = list1
print(list1 == list2) # True (值相等)
print(list1 is list2) # False (不同对象)
print(list1 is list3) # True (同一对象)
经验提醒:在比较浮点数时,直接使用==可能因精度问题导致意外结果。例如0.1 + 0.2 == 0.3返回False。推荐使用abs(a - b) < 1e-9或math.isclose(a, b)进行近似比较。
3.3 逻辑运算符:布尔逻辑的三种操作
逻辑运算符用于组合多个布尔表达式,包括and(与)、or(或)、not(非)。Python的逻辑运算符具有短路求值(short-circuit evaluation)特性:对于and,如果左侧为False,则不再计算右侧;对于or,如果左侧为True,则不再计算右侧。这一特性不仅提高了效率,还可以用于条件表达式的简化。
判断方法:逻辑运算符的返回值不一定是布尔值。当操作数不是布尔类型时,and和or返回操作数本身。具体规则为:x and y在x为假时返回x,否则返回y;x or y在x为真时返回x,否则返回y。这一行为使得逻辑运算符可以用于实现默认值赋值等技巧。
# 短路求值与返回值特性
def check_value(val):
print(f"正在检查: {val}")
return val > 0
# 短路:左侧为False时右侧不执行
result1 = False and check_value(10) # 不打印
result2 = True or check_value(10) # 不打印
# 逻辑运算符返回操作数
name = "" or "默认用户"
print(name) # 默认用户
# 多条件判断
score = 85
attendance = 0.92
if score >= 80 and attendance >= 0.9:
print("优秀学员")
风险说明:过度依赖逻辑运算符的返回值特性可能降低代码可读性。建议在明确需要布尔值时使用bool()转换,或使用显式的条件表达式a if condition else b,避免团队成员误解代码意图。
3.4 赋值运算符与复合赋值
赋值运算符用于将值绑定到变量名。基础赋值运算符=将右侧表达式的值赋给左侧变量。复合赋值运算符将算术或位运算与赋值结合,包括+=、-=、*=、/=、//=、%=、**=、&=、|=、^=、>>=、<<=。复合赋值运算符的左侧必须是一个可赋值对象(变量、列表元素、字典键等)。
操作流程:复合赋值x += 1等价于x = x + 1,但有一个重要区别:对于可变对象,x += y可能执行原地修改(in-place operation),而x = x + y总是创建新对象。例如对于列表,list1 += [4]会修改原列表,而list1 = list1 + [4]会创建新列表。
# 复合赋值与原地修改
list_a = [1, 2, 3]
list_b = [1, 2, 3]
print(id(list_a)) # 原始地址
list_a += [4] # 原地修改
list_b = list_b + [4] # 创建新对象
print(id(list_a)) # 地址不变
print(id(list_b)) # 地址改变
# 多重赋值与解包
x, y, z = 1, 2, 3
a = b = c = 0 # 链式赋值
3.5 位运算符:二进制层面的操作
位运算符直接操作整数的二进制位,包括按位与&、按位或|、按位异或^、按位取反~、左移<<、右移>>。位运算符在底层系统编程、权限管理、状态标志位处理、图像处理和加密算法中应用广泛。Python的整数是任意精度的,因此位运算可以处理任意大小的整数。
核心原理:按位与&的规则是两位都为1时结果为1;按位或|是两位至少一个为1时结果为1;按位异或^是两位不同时结果为1;按位取反~是对每一位取反,在Python中~x等价于-x - 1。左移x << n等价于x * 2**n,右移x >> n等价于x // 2**n(向下取整)。
# 位运算实战:权限管理
READ = 0b100 # 4
WRITE = 0b010 # 2
EXECUTE = 0b001 # 1
# 赋予读写权限
permission = READ | WRITE # 0b110 = 6
print(f"权限值: {permission}")
# 检查是否有写权限
has_write = permission & WRITE
print(f"有写权限: {bool(has_write)}")
# 移除写权限
permission = permission & ~WRITE
print(f"移除写权限后: {permission}") # 0b100 = 4
# 位运算实现快速乘除
print(10 << 2) # 40 (10 * 4)
print(40 >> 2) # 10 (40 / 4)
3.6 运算符优先级与结合性
运算符优先级决定了表达式中运算的执行顺序。Python的优先级从高到低大致为:括号() > 幂运算** > 一元运算符+x, -x, ~x > 算术运算符* / // % > 加法运算符+ - > 移位运算符<< >> > 按位与& > 按位异或^ > 按位或| > 比较运算符 > 逻辑运算符not > and > or > 赋值运算符。结合性决定了同优先级运算符的执行顺序,大多数运算符是左结合的,但幂运算符**是右结合的。
判断方法:当表达式复杂时,不要依赖记忆优先级,应使用括号明确运算顺序。这不仅提高可读性,也避免因优先级误判导致的逻辑错误。例如2 ** 3 ** 2的结果是512(右结合,先算3**2=9,再算2**9=512),而不是64。
# 优先级示例
result1 = 2 + 3 * 4 # 14 (乘法优先)
result2 = (2 + 3) * 4 # 20 (括号改变优先级)
result3 = 2 ** 3 ** 2 # 512 (右结合)
result4 = -3 ** 2 # -9 (幂运算优先于一元负号)
result5 = 5 & 3 | 2 # 2 (按位与优先于按位或)
# 复杂表达式建议使用括号
total = (price * quantity) - (discount if quantity > 10 else 0) + tax
3.7 实战案例:商品折扣计算器
下面通过一个完整的商品折扣计算器案例,综合运用本章学习的各类运算符。该案例模拟电商平台的折扣规则:基础折扣、会员额外折扣、满减优惠和限时促销叠加。
案例背景与约束:某电商平台需要实现一个折扣计算模块,输入为商品原价、购买数量、是否会员、是否参与限时促销。折扣规则为:购买数量超过10件享受9折;会员额外享受95折;满500元减50元;限时促销商品额外减20元。所有折扣叠加时,先计算数量折扣和会员折扣(乘法叠加),再应用满减和促销优惠(减法叠加)。
def calculate_discount(price, quantity, is_member, is_promo):
"""计算最终价格"""
# 基础总价
subtotal = price * quantity
# 数量折扣 (超过10件9折)
if quantity > 10:
subtotal *= 0.9
# 会员折扣 (额外95折)
if is_member:
subtotal *= 0.95
# 满减优惠 (满500减50)
if subtotal >= 500:
subtotal -= 50
# 限时促销 (额外减20)
if is_promo:
subtotal -= 20
# 确保价格不为负
final_price = max(subtotal, 0)
return round(final_price, 2)
# 测试用例
test_cases = [
{"price": 100, "quantity": 5, "is_member": False, "is_promo": False},
{"price": 100, "quantity": 12, "is_member": True, "is_promo": True},
{"price": 50, "quantity": 20, "is_member": False, "is_promo": True},
]
for i, case in enumerate(test_cases, 1):
result = calculate_discount(**case)
print(f"用例{i}: 原价{case['price']}元 x {case['quantity']}件 = {result}元")
分析判断过程:该案例中,quantity > 10和is_member使用了比较运算符和逻辑运算符进行条件判断;subtotal *= 0.9使用了复合赋值运算符;max(subtotal, 0)使用了内置函数确保结果非负。整个计算流程体现了运算符的优先级:先乘除后加减,括号内优先。
| 测试用例 | 原始总价 | 数量折扣 | 会员折扣 | 满减 | 促销 | 最终价格 |
|---|---|---|---|---|---|---|
| 用例1 | 500 | 无 | 无 | -50 | 无 | 450.00 |
| 用例2 | 1200 | -120 | -54 | -50 | -20 | 956.00 |
| 用例3 | 1000 | -100 | 无 | -50 | -20 | 830.00 |
风险与代价:该实现存在几个潜在问题。首先,浮点数乘法可能产生精度误差,例如0.1 * 3的结果是0.30000000000000004。其次,折扣叠加顺序可能影响最终结果,例如先减后乘与先乘后减结果不同。最后,max(subtotal, 0)虽然防止了负价格,但可能掩盖了业务逻辑错误。在实际工程中,应使用Decimal类型处理金额,并明确折扣计算顺序。
复盘经验:运算符的选择直接影响代码的简洁性和正确性。在业务逻辑中,优先使用and/or的短路特性简化条件判断;使用复合赋值运算符减少重复代码;使用位运算处理权限和状态标志。但始终牢记:可读性优先于技巧性,当运算符组合复杂时,使用括号和辅助变量明确意图。
3.8 常见误区与总结
在运算符使用中,初学者常犯以下错误:混淆==和=;在浮点数比较中直接使用==;忽略整除和取余在负数时的行为;过度依赖逻辑运算符的返回值特性;忘记幂运算符的右结合性;在链式比较中错误理解求值顺序。避免这些误区的关键在于:编写代码时明确数据类型和运算规则,必要时使用type()检查类型,使用round()或Decimal处理精度问题。
总结:本章系统讲解了Python六大类运算符的语法、语义和应用场景。算术运算符提供了完整的数学计算能力;比较运算符支持链式比较和多种数据类型;逻辑运算符具有短路求值和返回值特性;赋值运算符支持复合赋值和原地修改;位运算符在底层操作中发挥重要作用;运算符优先级和结合性决定了表达式的求值顺序。通过商品折扣计算器案例,我们看到了运算符在真实业务中的综合运用。掌握这些知识,将为后续的流程控制、函数定义和面向对象编程打下坚实基础。