Python基础指南之算术运算符与优先级规则详解
作者:星河耀银海
这篇文章主要为大家详细介绍了Python中的算术运算符以及控制表达式计算顺序的优先级规则,文中的示例代码讲解详细,希望对大家有一定的帮助
一、开篇:看似简单实则暗藏玄机的算术运算符
在Python中写a + b、"a * b"这样的表达式,几乎是我们每天都在做的事。算术运算符看起来再简单不过,但你真的了解它们的全部细节吗?
⌨️ 先来测几道题:
# 题1:猜猜输出什么? print(10 / 3) # ? print(10 // 3) # ? print(10 % 3) # ? print(10 ** 3) # ? # 题2:这个表达式的结果是什么? result = 2 + 3 * 4 ** 2 - 8 / 2 # 你能心算出来吗? # 题3:为什么这个结果不是0.3? print(0.1 + 0.2) # 输出什么?
如果你对其中任何一个问题不确定答案,说明你对算术运算符的理解还不够深入。今天这篇文章,我们就来系统性地掌握Python中所有的算术运算符,以及控制表达式计算顺序的优先级规则。
二、Python的算术运算符全家福
2.1 七种基本算术运算符
# Python共有7种算术运算符 # 1. 加法 + print(10 + 3) # 13 # 2. 减法 - print(10 - 3) # 7 # 3. 乘法 * print(10 * 3) # 30 # 4. 除法 / —— 总是返回浮点数 print(10 / 3) # 3.3333333333333335(注意:不是3!) # 5. 整除 // —— 向下取整 print(10 // 3) # 3 # 6. 取模(取余) % print(10 % 3) # 1 # 7. 幂运算 ** print(10 ** 3) # 1000
2.2 除法 / —— 总是返回浮点数
这是Python 3的一个重要变化(Python 2中/是整除):
# Python 3:/ 总是返回浮点数
print(10 / 5) # 2.0——不是2!
print(10 / 3) # 3.3333333333333335
print(1 / 3) # 0.3333333333333333
print(type(10 / 2)) # <class 'float'>
# 即使能整除,也是浮点数
result = 4 / 2
print(f'{result}, type={type(result).__name__}') # 2.0, type=float
# 如果需要整数结果,用 //
print(10 // 5) # 2(int类型)
print(type(10 // 5)) # <class 'int'>
2.3 整除 // —— 向下取整
整除运算符//的结果是向下取整(floor),不是向零取整:
# 正数——向下取整=截断小数部分 print(10 // 3) # 3 print(10 // 4) # 2 # 负数——向下取整≠向零取整! print(-10 // 3) # -4(不是-3!) # 因为-10/3 = -3.333...,向下取整得到-4 # 对比其他语言的向零取整: # C/Java中 -10/3 = -3(向零取整,截断小数) # Python中 -10//3 = -4(向下取整) # 图示: # -4 -3 -2 -1 0 1 2 3 # |---|---|---|---|---|---|---| # ↑-3.33 向下取整到-4 # 验证 print(-10 / 3) # -3.3333333333333335 print(-10 // 3) # -4 # 更多负数例子 print(-7 // 3) # -3(-7/3=-2.333...,向下取整得-3) print(-3 // 3) # -1(正好整除) print(-1 // 3) # -1(-1/3=-0.333...,向下取整得-1)
2.4 取模 % —— 和整除配套
取模运算满足恒等式:a = b * (a // b) + (a % b)
# 取模和整除的关系
# a = b * (a // b) + (a % b)
a, b = 10, 3
print(f'{a} = {b} * ({a // b}) + ({a % b})') # 10 = 3 * 3 + 1
# 对于负数,这个恒等式依然成立
a, b = -10, 3
print(f'{a} = {b} * ({a // b}) + ({a % b})') # -10 = 3 * (-4) + 2
# 注意:-10 % 3 = 2(不是1!)
# 所以取模结果的符号总是和除数相同
print(-10 % 3) # 2(除数3为正,结果为正)
print(10 % -3) # -2(除数-3为负,结果为负)
print(-10 % -3) # -1(除数-3为负,结果为负)
# 常用场景:循环索引
# 无论index多大,index % len 都在[0, len-1]范围内
items = ['a', 'b', 'c']
for i in range(10):
print(items[i % len(items)], end=' ') # a b c a b c a b c a
print()
2.5 幂运算 ** —— 右结合
幂运算符**是右结合的,意味着多个**从右向左计算:
# 幂运算——**是右结合的 # 2 ** 3 ** 2 = 2 ** (3 ** 2) = 2 ** 9 = 512 # 不是 (2 ** 3) ** 2 = 8 ** 2 = 64! print(2 ** 3 ** 2) # 512 print(2 ** (3 ** 2)) # 512——等价于上面的 print((2 ** 3) ** 2) # 64——加括号后不同 # 常用场景 print(2 ** 10) # 1024(2的10次方) print(16 ** 0.5) # 4.0(平方根) # ⚠️ 幂运算优先级高于一元负号 print(-2 ** 2) # -4(等价于 -(2**2)) print((-2) ** 2) # 4(加括号改变了计算顺序) # 这个行为经常让人困惑! # -2 ** 2 → -(2 ** 2) → -4 # 而不是 (-2) ** 2 → 4
三、算术运算符的特殊行为
3.1 字符串和序列的算术运算
# 字符串——支持 + 和 *
print('Hello' + ' ' + 'World') # Hello World(拼接)
print('Ha' * 5) # HaHaHaHaHa(重复)
# 列表——支持 + 和 *
print([1, 2] + [3, 4]) # [1, 2, 3, 4](合并)
print([0] * 5) # [0, 0, 0, 0, 0](重复)
# 元组——支持 + 和 *
print((1, 2) + (3, 4)) # (1, 2, 3, 4)
print(('Hi',) * 3) # ('Hi', 'Hi', 'Hi')
# ⚠️ 字符串不支持 - 和 *与*(两个字符串相乘)
# 'Hello' - 'lo' # TypeError
# 'Hello' * 'World' # TypeError
3.2 浮点数精度问题
# ⚠️ 浮点数运算的精度问题——二进制无法精确表示某些十进制小数
print(0.1 + 0.2) # 0.30000000000000004——不是精确的0.3!
print(0.1 + 0.2 == 0.3) # False!
# 原因:0.1和0.2在二进制中是无限循环小数
# 类似于十进制中的1/3 = 0.333333...
# 计算机用有限位数存储,所以有舍入误差
# 解决方案一:使用Decimal(精确十进制运算)
from decimal import Decimal
print(Decimal('0.1') + Decimal('0.2')) # 0.3——精确!
# 解决方案二:使用math.isclose做近似比较
import math
print(math.isclose(0.1 + 0.2, 0.3)) # True
# 解决方案三:用round控制精度
print(round(0.1 + 0.2, 10)) # 0.3
# 解决方案四:整數運算(用分代替元)
amount_cents = 10 + 20 # 0.10元 + 0.20元 = 30分
print(f'{amount_cents / 100:.2f}元') # 0.30元
3.3 大整数运算不受精度限制
# Python的整数是任意精度的——不受限于32位或64位
# 你可以计算任意大的整数
# 计算2的100次方
big = 2 ** 100
print(big) # 1267650600228229401496703205376
print(type(big)) # <class 'int'>
# 计算100的阶乘
factorial_100 = 1
for i in range(1, 101):
factorial_100 *= i
print(f'100! = {factorial_100}')
# 浮点数没有这个待遇——受限于64位IEEE 754
print(2.0 ** 1024) # OverflowError(在某些实现中返回inf)
# 💡 这就是为什么Python在科学计算和大数运算中特别受欢迎
3.4 复数运算
# Python原生支持复数运算 a = 3 + 4j b = 1 - 2j # 加减乘除 print(a + b) # (4+2j) print(a - b) # (2+6j) print(a * b) # (11-2j) print(a / b) # (-1+2j) # 取模 print(abs(a)) # 5.0(sqrt(3²+4²)) # 共轭 print(a.conjugate()) # (3-4j) # 实部和虚部 print(a.real) # 3.0 print(a.imag) # 4.0
四、复合赋值运算符
4.1 所有复合赋值运算符
复合赋值运算符将运算和赋值合并为一步:
# 完整列表 x = 10 x += 3 # x = x + 3 → 13 x -= 3 # x = x - 3 → 10 x *= 3 # x = x * 3 → 30 x /= 3 # x = x / 3 → 10.0 x //= 3 # x = x // 3 → 3.0 x %= 3 # x = x % 3 → 0.0 x **= 3 # x = x ** 3 → 0.0 # 对于字符串/列表 s = 'Hello' s += ' World' # s = s + ' World' print(s) # Hello World lst = [1, 2] lst *= 3 # lst = lst * 3 print(lst) # [1, 2, 1, 2, 1, 2]
4.2 复合赋值的微妙之处
# += 对可变对象是原地修改
lst1 = [1, 2, 3]
original_id = id(lst1)
lst1 += [4, 5] # 等价于 lst1.extend([4, 5])
print(f'lst1: {lst1}, id不变: {id(lst1) == original_id}') # True——原地修改
# 对比:lst1 = lst1 + [4, 5] 创建新对象
lst2 = [1, 2, 3]
original_id = id(lst2)
lst2 = lst2 + [4, 5] # 创建新列表
print(f'lst2: {lst2}, id不变: {id(lst2) == original_id}') # False——新对象
# ⚠️ 对于不可变类型,+= 总是创建新对象
x = 10
original_id = id(x)
x += 5 # 等价于 x = x + 5,创建了新整数
print(f'id不变: {id(x) == original_id}') # False
五、运算符优先级规则
5.1 优先级从高到低
# Python算术运算符优先级(从高到低):
# 1. () 括号
# 2. ** 幂运算
# 3. +x, -x 一元正负号
# 4. *, /, //, % 乘除模
# 5. +, - 加减
# 口诀:括号→幂→正负→乘除→加减
# 和小学数学的"先乘除后加减"一致,多了幂和正负号
# 示例:逐步演示优先级
result = 2 + 3 * 4
print(f'2 + 3 * 4 = {result}') # 14(先乘后加)
result = (2 + 3) * 4
print(f'(2 + 3) * 4 = {result}') # 20(括号优先)
result = 2 ** 3 * 4
print(f'2 ** 3 * 4 = {result}') # 32(幂优先于乘)
result = -2 ** 2
print(f'-2 ** 2 = {result}') # -4(幂优先于负号)
result = (-2) ** 2
print(f'(-2) ** 2 = {result}') # 4(括号改变了顺序)
5.2 详细优先级表
| 优先级 | 运算符 | 描述 | 结合性 |
|---|---|---|---|
| 1(最高) | () | 括号 | — |
| 2 | ** | 幂运算 | 右结合 |
| 3 | +x, -x, ~x | 一元正负号、按位取反 | 右结合 |
| 4 | *, /, //, % | 乘、除、整除、取模 | 左结合 |
| 5 | +, - | 加、减 | 左结合 |
5.3 结合性:从左到右 vs 从右到左
# 左结合——从左到右计算(大多数运算符) # 5 - 3 - 1 = (5 - 3) - 1 = 1 print(5 - 3 - 1) # 1 # 5 / 2 / 2 = (5 / 2) / 2 = 1.25 print(5 / 2 / 2) # 1.25 # 2 ** 3 ** 2 = 2 ** (3 ** 2) = 2 ** 9 = 512(右结合!) print(2 ** 3 ** 2) # 512 # 一元负号也是右结合 print(- - 5) # 5 等价于 -(-5)
5.4 复杂的优先级示例
# 结合优先级和结合性,心算出以下表达式的结果
# 示例1
result = 10 + 5 * 2 ** 3 - 30 // 4
# 解析步骤:
# 10 + 5 * 8 - 30 // 4 (先算 2**3=8)
# 10 + 40 - 7 (5*8=40, 30//4=7)
# 43 (10+40=50, 50-7=43)
print(f'10 + 5 * 2 ** 3 - 30 // 4 = {result}') # 43
# 示例2
result = 25 // 3 % 2 * 4
# 解析步骤(同级左结合):
# 8 % 2 * 4 (25//3=8)
# 0 * 4 (8%2=0)
# 0
print(f'25 // 3 % 2 * 4 = {result}') # 0
# 示例3
result = -3 ** 2 + 10
# 解析步骤:
# -(3**2) + 10 (幂优先于负号)
# -9 + 10 (3**2=9)
# 1
print(f'-3 ** 2 + 10 = {result}') # 1
# 示例4——注意浮点
result = 10 / 5 * 2
# 解析步骤(同级左结合):
# 2.0 * 2 (10/5=2.0)
# 4.0
print(f'10 / 5 * 2 = {result}') # 4.0
# 示例5:嵌套幂运算
result = 2 ** 1 ** 3 ** 2
# 解析步骤(右结合):
# 2 ** (1 ** (3 ** 2)) (从最右边开始)
# 2 ** (1 ** 9) (3**2=9)
# 2 ** 1 (1**9=1)
# 2
print(f'2 ** 1 ** 3 ** 2 = {result}') # 2
六、括号——最可靠的优先级控制工具
6.1 不确定时就加括号
# 即使你知道优先级规则,加括号也能让代码更易读 # 代码是写给人看的,不是写给编译器看的 # ❌ 代码能工作,但需要心算优先级 price = 100 * 0.8 + 5 * 2 + 10 / 2 # ✅ 加括号后一目了然 price = (100 * 0.8) + (5 * 2) + (10 / 2) # ❌ 这个更难懂 flag = a < b and c > d or e == f # ✅ 语义清晰 flag = (a < b and c > d) or (e == f) # 💡 经验法则: # 1. 涉及3个以上不同优先级的运算符时——加括号 # 2. 涉及幂运算**时——加括号(右结合容易误解) # 3. 负数参与运算时——加括号 # 4. 混合了算术和比较/逻辑运算符时——加括号
6.2 括号不影响性能
# 括号只影响编译时的解析,不影响运行时的性能
# 所以不要因为"性能"而不加括号——那是不存在的代价
import dis
def with_parens():
return (a + b) * (c + d)
def without_parens():
return a + b * c + d # 和上面逻辑不同,但说明问题
# 括号在编译时就处理了,生成的字节码完全等价于不加括号的版本
# (当括号没有改变运算顺序时)
七、实际技巧和常见模式
7.1 快速计算技巧
# 判断奇偶
n = 7
if n % 2 == 0:
print(f'{n}是偶数')
else:
print(f'{n}是奇数')
# 快速除以2的幂
print(100 >> 1) # 50(等价于100 // 2)
print(100 >> 2) # 25(等价于100 // 4)
print(100 >> 3) # 12(等价于100 // 8)
# 快速乘以2的幂
print(7 << 1) # 14(等价于7 * 2)
print(7 << 2) # 28(等价于7 * 4)
print(7 << 3) # 56(等价于7 * 8)
# 限制数值范围
def clamp(value, min_val, max_val):
"""将数值限制在[min_val, max_val]范围内"""
return max(min_val, min(value, max_val))
# 获取一个数的最后n位数字
num = 1234567
print(num % 100) # 67(最后两位)
print(num % 1000) # 567(最后三位)
# 去掉一个数的最后n位数字
print(num // 100) # 12345(去掉最后两位)
print(num // 1000) # 1234(去掉最后三位)
7.2 换算和单位转换
# 时间换算
def seconds_to_hms(total_seconds):
"""秒数转换为时:分:秒"""
hours = total_seconds // 3600
minutes = (total_seconds % 3600) // 60
seconds = total_seconds % 60
return f'{hours:02d}:{minutes:02d}:{seconds:02d}'
print(seconds_to_hms(3661)) # 01:01:01
print(seconds_to_hms(86399)) # 23:59:59
# 文件大小换算
def format_size(bytes_size):
"""字节数转换为可读的文件大小"""
units = ['B', 'KB', 'MB', 'GB', 'TB']
size = float(bytes_size)
unit_index = 0
while size >= 1024 and unit_index < len(units) - 1:
size /= 1024
unit_index += 1
return f'{size:.2f} {units[unit_index]}'
print(format_size(1024)) # 1.00 KB
print(format_size(1048576)) # 1.00 MB
print(format_size(1234567890)) # 1.15 GB
7.3 循环和分页
# 分页计算
def paginate(items, page, per_page):
"""
分页——计算总页数、当前页数据
page: 页码(从1开始)
per_page: 每页数量
"""
total = len(items)
total_pages = (total + per_page - 1) // per_page # 向上取整
start = (page - 1) * per_page
end = min(start + per_page, total)
return {
'page': page,
'per_page': per_page,
'total': total,
'total_pages': total_pages,
'items': items[start:end],
'has_next': page < total_pages,
'has_prev': page > 1,
}
# 示例
items = list(range(1, 26)) # 1到25
result = paginate(items, page=2, per_page=10)
print(f'第{result["page"]}页: {result["items"]}') # [11, 12, ..., 20]
print(f'总页数: {result["total_pages"]}') # 3
print(f'还有下一页: {result["has_next"]}') # True
7.4 数学计算函数
import math # Python内置的数学函数(不需要导入math) print(abs(-10)) # 10(绝对值) print(pow(2, 10)) # 1024(等价于2**10) print(round(3.14159, 2)) # 3.14(四舍五入) print(divmod(10, 3)) # (3, 1)——同时返回商和余数 # 等价于 (10 // 3, 10 % 3) # math模块中的常用函数 print(math.sqrt(16)) # 4.0(平方根) print(math.ceil(3.1)) # 4(向上取整) print(math.floor(3.9)) # 3(向下取整) print(math.trunc(-3.9)) # -3(向零取整——截断) print(math.factorial(5)) # 120(阶乘) print(math.gcd(12, 18)) # 6(最大公约数) print(math.lcm(12, 18)) # 36(最小公倍数,Python 3.9+) print(math.isclose(0.1+0.2, 0.3)) # True(浮点数近似相等比较)
八、综合实战:构建一个表达式计算器
class SimpleCalculator:
"""
一个遵守Python运算符优先级规则的简单计算器。
支持 +、-、*、/、//、%、** 和括号。
"""
def __init__(self):
self._operators = {
'+': (1, lambda a, b: a + b), # 优先级1(最低)
'-': (1, lambda a, b: a - b),
'*': (2, lambda a, b: a * b), # 优先级2
'/': (2, lambda a, b: a / b),
'//': (2, lambda a, b: a // b),
'%': (2, lambda a, b: a % b),
'**': (3, lambda a, b: a ** b), # 优先级3(最高)
}
def evaluate(self, expression):
"""
使用双栈法(操作数栈+运算符栈)计算表达式。
遵守Python的优先级和结合性规则。
"""
tokens = self._tokenize(expression)
return self._evaluate_tokens(tokens)
def _tokenize(self, expression):
"""将表达式字符串分解为token列表"""
tokens = []
i = 0
while i < len(expression):
c = expression[i]
# 跳过空格
if c.isspace():
i += 1
continue
# 数字(包括小数)
if c.isdigit() or c == '.':
j = i
while j < len(expression) and (expression[j].isdigit() or expression[j] == '.'):
j += 1
num_str = expression[i:j]
tokens.append(float(num_str) if '.' in num_str else int(num_str))
i = j
continue
# 括号
if c in '()':
tokens.append(c)
i += 1
continue
# 运算符
if c == '*' and i + 1 < len(expression) and expression[i + 1] == '*':
tokens.append('**')
i += 2
continue
if c == '/' and i + 1 < len(expression) and expression[i + 1] == '/':
tokens.append('//')
i += 2
continue
if c in '+-*/%':
# 处理负号(前面是运算符或开头)
if c == '-' and (i == 0 or expression[i-1] in '(+-*/%'):
pass # 暂时不处理一元负号的情况
tokens.append(c)
i += 1
continue
i += 1
return tokens
def _evaluate_tokens(self, tokens):
"""计算token列表的值(简化版,支持+、-、*、/和括号)"""
def apply_op(ops, vals):
"""应用栈顶的运算符"""
if len(ops) == 0:
return
op = ops.pop()
right = vals.pop()
left = vals.pop()
_, func = self._operators[op]
vals.append(func(left, right))
# 双栈算法
ops = [] # 运算符栈
vals = [] # 操作数栈
for token in tokens:
if isinstance(token, (int, float)):
vals.append(token)
elif token == '(':
ops.append(token)
elif token == ')':
while ops and ops[-1] != '(':
apply_op(ops, vals)
ops.pop() # 弹出 '('
elif token in self._operators:
op_priority, _ = self._operators[token]
# 处理运算符优先级
while (ops and ops[-1] != '(' and
ops[-1] in self._operators and
self._operators[ops[-1]][0] >= op_priority):
apply_op(ops, vals)
ops.append(token)
# 处理剩余运算符
while ops:
apply_op(ops, vals)
return vals[0]
# 测试计算器
calc = SimpleCalculator()
print('=== 简单计算器测试 ===')
expressions = [
'2 + 3 * 4', # 14(乘法优先)
'(2 + 3) * 4', # 20(括号优先)
'10 - 3 - 2', # 5(左结合)
'2 ** 3 + 1', # 9(幂运算优先)
]
for expr in expressions:
try:
result = calc.evaluate(expr)
python_result = eval(expr)
print(f'{expr} = {result} (Python: {python_result})')
except Exception as e:
print(f'{expr} → 错误: {e}')
九、常见陷阱总结
9.1 陷阱一:整数除法和Python 2的区别
# Python 2: 3 / 2 = 1(整数除法) # Python 3: 3 / 2 = 1.5(总是浮点数) # 如果需要整数除法行为,用 // print(3 // 2) # 1 # 如果需要兼容Python 2的除法 # from __future__ import division # 在Python 2中启用Python 3除法
9.2 陷阱二:取模的符号
# Python中,a % b的结果符号与b相同
print(10 % 3) # 1 (正)
print(-10 % 3) # 2 (正!与除数的符号一致)
print(10 % -3) # -2 (负!与除数的符号一致)
print(-10 % -3) # -1 (负!与除数的符号一致)
# 这和其他语言可能不同
# C/Java中:-10 % 3 = -1
# Python中:-10 % 3 = 2
# Python的设计遵循:a = b * (a // b) + (a % b)
# 验证恒等式
for a in [-10, -7, -3, 0, 3, 7, 10]:
for b in [3, -3]:
result = b * (a // b) + (a % b)
assert result == a, f'恒等式失败: {a}, {b}'
print('恒等式验证通过 ✓')
9.3 陷阱三:幂运算的右结合和负号优先级
# ⚠️ 最常踩的坑:-2 ** 2 print(-2 ** 2) # -4(不是4!) print((-2) ** 2) # 4 # ⚠️ 第二个坑:2 ** 3 ** 2 print(2 ** 3 ** 2) # 512(不是64!) print(2 ** (3 ** 2)) # 512 print((2 ** 3) ** 2) # 64 # 💡 建议:涉及**和负号时,永远加括号
9.4 陷阱四:浮点数比较
# ❌ 直接比较浮点数
a = 0.1 + 0.2
b = 0.3
print(a == b) # False!
# ✅ 使用近似比较
import math
print(math.isclose(a, b)) # True
print(math.isclose(a, b, rel_tol=1e-9)) # True
# ✅ 或者控制精度后比较
print(round(a, 10) == round(b, 10)) # True
# ✅ 最佳实践:使用Decimal进行精确小数计算
from decimal import Decimal
print(Decimal('0.1') + Decimal('0.2') == Decimal('0.3')) # True
十、本篇小结
Python的算术运算符看似简单,实则有不少需要注意的细节:
七种算术运算符:
| 运算符 | 名称 | 示例 | 备注 |
|---|---|---|---|
+ | 加 | 10 + 3 = 13 | 也用于字符串拼接 |
- | 减 | 10 - 3 = 7 | |
* | 乘 | 10 * 3 = 30 | 也用于序列重复 |
/ | 除 | 10 / 3 = 3.333... | 总是返回浮点数 |
// | 整除 | 10 // 3 = 3 | 向下取整 |
% | 取模 | 10 % 3 = 1 | 结果符号与除数一致 |
** | 幂 | 10 ** 3 = 1000 | 右结合 |
优先级口诀(从高到低):括号 → 幂(**) → 正负号(+x, -x) → 乘除模(* / // %) → 加减(+ -)
关键注意点:
/总是返回float,//才是整数除法(向下取整)-2 ** 2 = -4(不是4)——幂优先于负号2 ** 3 ** 2 = 512(不是64)——幂是右结合0.1 + 0.2 ≠ 0.3——浮点数精度问题- 不确定优先级时——加括号
算术运算符是Python表达式的基础。下一篇我们将进入比较运算符的世界——==、!=、<、>、<=、>=,以及Python独有的链式比较特性。
以上就是Python基础指南之算术运算符与优先级规则详解的详细内容,更多关于Python算术运算符的资料请关注脚本之家其它相关文章!
