掌握Python的“时光倒流”魔法,让代码优雅10倍
一、一个让初学者目瞪口呆的操作
先看一段代码:
s = "Python编程"
print(s[::-1])
输出:
程编nohtyP
就一行?没有循环?没有反转函数?直接 [::-1] 就把字符串倒过来了!
这还不是最神奇的。再看:
lst = [1, 2, 3, 4, 5]
print(lst[::-1]) # [5, 4, 3, 2, 1]
print(lst[::2]) # [1, 3, 5] ← 隔一个取一个
print(lst[1:4]) # [2, 3, 4] ← 截取子列表
Python 的切片语法,就像一个万能遥控器,让你随心所欲地“切割”序列。
二、先从“反向索引”说起
在大多数编程语言里,获取最后一个元素需要先知道长度:
arr = [10, 20, 30, 40, 50]
last = arr[len(arr) - 1] # 太麻烦了
Python 开挂了:负数索引从右向左数
arr = [10, 20, 30, 40, 50]
print(arr[-1]) # 50 —— 最后一个
print(arr[-2]) # 40 —— 倒数第二个
print(arr[-5]) # 10 —— 第一个
示意图:
索引: -5 -4 -3 -2 -1
┌────┬────┬────┬────┬────┐
│ 10 │ 20 │ 30 │ 40 │ 50 │
└────┴────┴────┴────┴────┘
索引: 0 1 2 3 4
💡 初学者笔记:负索引从 -1 开始(最后一个),依次递减。
三、切片完整语法:[起始:结束:步长]
切片不是魔法,它有一套清晰规则:
sequence[起始:结束:步长]
3.1 基础示例
s = "0123456789"
print(s[2:5]) # "234" (索引2,3,4)
print(s[:5]) # "01234"(从开头到索引5)
print(s[5:]) # "56789"(从索引5到末尾)
print(s[:]) # "0123456789"(整个)
print(s[1:8:2]) # "1357" (步长为2)
3.2 负步长:实现反转的奥秘
当 步长为负数 时,切片会从右向左移动:
s = "12345"
print(s[::-1]) # "54321"
拆解执行过程:
等价写法(但不推荐):
s[-1::-1] # 从最后一个走到开头
# 或
s[::-1] # 最简洁
3.3 负步长的更多玩法
s = "abcdef"
print(s[4:1:-1]) # "edc" —— 从索引4走到索引2(因为结束索引1不包含)
print(s[5::-2]) # "fdb" —— 从索引5开始,步长-2
四、实战应用:反转的N种用法
4.1 判断回文字符串(经典面试题)
defis_palindrome(s):
# 去掉空格并统一小写
s = s.replace(" ", "").lower()
return s == s[::-1]
print(is_palindrome("上海自来水来自海上")) # True
print(is_palindrome("A man a plan a canal panama")) # True
一行代码版本:
is_pal = lambda s: (t := s.replace(" ", "").lower()) == t[::-1]
4.2 反转列表并保留原列表
original = [1, 2, 3, 4]
reversed_copy = original[::-1] # 新列表 [4,3,2,1]
print(original) # 原列表不变 [1,2,3,4]
# 对比:.reverse() 会原地修改
original.reverse()
print(original) # [4,3,2,1] (原列表被改了)
4.3 批量提取对角线元素
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
# 主对角线
diag1 = [matrix[i][i] for i in range(3)] # [1,5,9]
# 副对角线(反转)
diag2 = [matrix[i][2-i] for i in range(3)] # [3,5,7]
# 更骚的写法:先反转行再取主对角线
diag2 = [row[::-1][i] for i, row in enumerate(matrix)] # [3,5,7]
4.4 字符串轮转判断
defis_rotation(s1, s2):
"""判断s2是否是s1的旋转(如"abcde"旋转后是"cdeab")"""
return len(s1) == len(s2) and s2 in s1 + s1
print(is_rotation("abcde", "cdeab")) # True
# 原理:旋转后的字符串一定是原字符串拼接后的子串
五、进阶技巧:切片赋值
切片不仅能取值,还能赋值(可迭代对象):
nums = [1, 2, 3, 4, 5]
# 替换一段
nums[1:3] = [20, 30] # [1,20,30,4,5]
# 删除一段
nums[2:4] = [] # [1,20,5]
# 步长赋值(数量必须匹配)
nums = [1,2,3,4,5]
nums[::2] = [10,30,50] # [10,2,30,4,50]
# 反转并赋值(骚操作)
nums = [1,2,3,4,5]
nums[:] = nums[::-1] # 原地反转
print(nums) # [5,4,3,2,1]
六、常见陷阱与注意事项
❌ 陷阱1:字符串反转的误解
s = "hello"
s[::-1] # 正确返回新字符串
s.reverse() # ❌ 报错!字符串没有reverse方法
❌ 陷阱2:超大步长的性能问题
# 虽然可以,但不推荐在大数据上使用
big_list = list(range(10**6))
reversed_list = big_list[::-1] # 复制整个列表,内存翻倍
# 如果需要迭代,用 reversed() 更省内存
for item in reversed(big_list): # 惰性迭代,不复制
pass
❌ 陷阱3:对字典使用切片
d = {"a":1, "b":2}
d[::-1] # ❌ TypeError: 'dict' object is not subscriptable
切片只适用于序列类型:字符串、列表、元组、range 等。
七、趣味挑战(答案在文末)
挑战1:用一行代码实现“每隔一个字符反转”
输入:"abcdefgh"
输出:"hgfedcba"(这太简单了)
进阶要求:输出 "h f d b"(索引7,5,3,1) 和 "g e c a"(索引6,4,2,0)
挑战2:不使用 [::-1],手动实现一个切片反转函数
defmy_reverse(seq):
# 不准用 [::-1] 或 reversed()
# 要求:支持字符串、列表、元组
pass
八、总结
| | |
|---|
| s[::-1] | |
| s[::2] | |
| s[::-2] | |
| s[-3:] | |
| s[1:-1] | |
| lst[:] | |
一句话记住切片:
“冒号分隔,起始结束,步长可负,不写全包。”
挑战答案
挑战1:
s = "abcdefgh"
result1 = s[7::-2] # "hfdb"
result2 = s[6::-2] # "geca"
print(result1, result2)
挑战2(参考实现):
defmy_reverse(seq):
# 元组/列表/字符串通用
if isinstance(seq, (str, list, tuple)):
# 用负步长的range构造索引
indices = range(len(seq)-1, -1, -1)
result = [seq[i] for i in indices]
# 还原类型
if isinstance(seq, str):
return''.join(result)
elif isinstance(seq, tuple):
return tuple(result)
return result
raise TypeError("只支持序列类型")
思考题(欢迎评论区留言):
matrix = [[1,2,3],[4,5,6],[7,8,9]]
# 如何用切片一步得到 [[9,8,7],[6,5,4],[3,2,1]] ?