{ }codelper

学习 › Python

各语言特有 · 40

Python 风格的代码

推导式、带步长的切片、解包、enumerate 和 zip、in、f 字符串,以及三个陷阱。

本单元适用于 Python。
其他语言用循环或库方法做同样的事(JavaScript map、filter,Java 流,C# LINQ,C、C++、Craykon 则是循环加 push_back、添加)。

Python 有许多简写,能把好几行循环缩成一行。
下面的示例依次用到推导式、切片、解包、enumerate、zip 和 f 字符串,最后展示两个不报错却给出错误结果的陷阱。

nums = list(map(int, input().split()))words = input().split() odd_squares = [x * x for x in nums if x % 2 == 1]print(*odd_squares)print({x % 3 for x in nums})print({w: len(w) for w in words}) print(*nums[::-1])print(*nums[1:6:2])print(words[0][::-1]) first, *rest = numsa, b = first, rest[-1]a, b = b, aprint(a, b, len(rest)) for i, w in enumerate(words, start=1):    print(f"{i}. {w:<6}|")print(*(f"{w}={n}" for w, n in zip(words, nums)))print(9 in nums, "lo" in words[0]) gen = (x for x in nums)print(sum(gen), sum(gen)) grid = [[0] * 3] * 2grid[0][0] = 1print(grid)grid = [[0] * 3 for _ in range(2)]grid[0][0] = 1print(grid)
stdin
3 1 4 1 5 9 2 6
hello world codes
stdout
9 1 1 25 81
{0, 1, 2}
{'hello': 5, 'world': 5, 'codes': 5}
6 2 9 5 1 4 1 3
1 1 9
olleh
6 3 7
1. hello |
2. world |
3. codes |
hello=3 world=1 codes=4
True True
31 0
[[1, 0, 0], [1, 0, 0]]
[[1, 0, 0], [0, 0, 0]]

要点

  • 推导式(comprehension)用一行循环生成新列表。
    [x * x for x in nums if x % 2 == 1] 的意思是“挑出奇数并求平方的列表”,先建空列表再 append 的四行循环就变成了这一行。
  • 只要换一下括号,同样的形状就能生成集合 {x % 3 for x in nums} 或字典 {w: len(w) for w in words}。
  • 切片 a[开始:结束:步长] 不包括结束位置,省略的部分默认为开头、末尾和 1。
    nums[1:6:2] 取第 1、3、5 个,nums[::-1] 是倒过来的新列表,"hello"[::-1] 是 "olleh"。
  • 超出范围的切片不会报错,只返回现有的部分(nums[5:100])。
    负数下标从后往前数,所以 a[-1] 是最后一个。
  • 解包:a, b = b, a 不用临时变量就能交换,first, *rest = nums 把第一个值和其余部分的列表分开。
    需要下标用 enumerate(words, start=1),并排遍历两个列表用 zip(words, nums)(在较短的那个结束时停下)。
  • in 判断是否包含。
    列表从前往后逐个查找,比较慢;集合和字典几乎立刻就能找到。
    在字符串上,"lo" in "hello" 查找的是子串。

各语言的写法

Pythonf 字符串 f"{i}. {w:<6}|" 在花括号里写表达式,: 后面写格式(<6 左对齐占 6 格,.2f 保留两位小数)。
f"{x=}" 会连名字一起输出成 x=3,调试时很方便。
很少用 range(len(...)) 遍历下标,带步长的切片和 [::-1] 是 Python 独有的简写

常见错误

默认值用了 []

def add(x, bucket=[]):
    bucket.append(x)
    return bucket
add(1); print(add(2))

→ [1, 2] —— [] 只在定义函数时创建一次,每次调用用的都是同一个列表

写成 bucket=None,在函数里用 if bucket is None: bucket = [] 新建列表。

生成器用了两次

gen = (x for x in nums)
print(sum(gen), sum(gen))

→ 31 0 —— 圆括号 ( ) 生成的不是列表,而是用一次就空了的生成器

要用两次,就用方括号 [ ] 生成列表。

用 [[0] * 3] * 2 建二维列表

grid = [[0] * 3] * 2
grid[0][0] = 1
print(grid)

→ [[1, 0, 0], [1, 0, 0]] —— 外层的 * 2 不会复制内层列表,而是把同一个列表放了两次

用推导式逐行新建:[[0] * 3 for _ in range(2)](像 [0] * 3 这样乘数字没问题)。

各语言的差异
用乘法(* m)生成的二维列表和 =[] 默认值都会不报错地共用同一个列表,导致意想不到的地方被改掉。
需要多个列表就用推导式,默认值请用 None。

练习题