列表推导式与常见简写 (List Comprehension & Idioms)

1. 定义

这篇拆解几个 Python/数据分析里高频出现、初看有点绕的”一行流”写法:

  • 列表推导式 (list comprehension):一行生成列表
  • 条件表达式 (三元运算符 ternary)A if 条件 else B
  • 两者组合:推导式里嵌条件表达式
  • 列表拼接 + pandas 选列df[cols + ['label']]

核心思想:把”建一个空列表 → for 循环 → append”压缩成一行,更简洁也更快。

2. 列表推导式 (List Comprehension)

# 传统写法
result = []
for x in X_train:
    result.append(x * 2)
 
# 推导式写法(等价,一行)
result = [x * 2 for x in X_train]

结构:[对每个元素做什么 for 变量 in 可迭代对象]

带过滤:

[x for x in nums if x > 0]        # 只保留 >0 的(末尾 if 是"过滤")

3. 条件表达式(三元运算符)

# 传统写法
if score >= 60:
    label = "pass"
else:
    label = "fail"
 
# 三元写法(一行)
label = "pass" if score >= 60 else "fail"

结构:值A if 条件 else 值B —— 条件成立取 A,否则取 B。

注意区分两种 if 的位置:

  • A if 条件 else Bfor 前面 → 是”选值”(三元表达式,必须有 else)
  • ... for x in xs if 条件for 后面 → 是”过滤”(筛选元素,没有 else)

4. 组合拆解:[1 if np.dot(w, x) + t >= 0 else -1 for x in X_train]

这是感知机 / 线性分类器做预测的经典写法。逐层拆开:

predictions = [1 if np.dot(w, x) + t >= 0 else -1 for x in X_train]

等价的展开写法:

predictions = []
for x in X_train:                    # ③ 遍历每个样本 x
    score = np.dot(w, x) + t         # ① w·x + t:加权求和 + 偏置
    if score >= 0:                   # ② 判断符号
        predictions.append(1)        #    ≥0 判为 +1 类
    else:
        predictions.append(-1)       #    <0 判为 -1 类

逐部分含义:

片段含义
np.dot(w, x)权重向量 w 和样本 x点积(加权求和),见 matrix-operations.md
+ t加偏置项 (bias/threshold)
>= 0看结果符号,决定分到哪一类
1 if ... else -1三元表达式:≥0 → 1,否则 → -1
for x in X_train对训练集每个样本都算一遍
[... ]把每个样本的预测收集成一个列表

一句话:对每个样本算 w·x + t,≥0 判 +1、<0 判 -1,结果收成一个列表。 这就是感知机的预测规则。

向量化版本(更快,避免 Python 循环):

scores = X_train @ w + t              # 一次算完所有样本(矩阵乘 + 广播)
predictions = np.where(scores >= 0, 1, -1)   # 逐元素三元

np.where(条件, A, B) 就是 NumPy 版的”三元表达式”,作用在整个数组上。见 broadcasting.md

5. 列表拼接 + pandas 选列:virginica = virginica[features + ['label']]

features = ["sepal_length", "sepal_width", "petal_length", "petal_width"]
virginica = virginica[features + ["label"]]

拆两步看:

第一步:features + ['label'] 是列表拼接

["sepal_length", "sepal_width", "petal_length", "petal_width"] + ["label"]
# = ["sepal_length", "sepal_width", "petal_length", "petal_width", "label"]

+ 对列表是拼接(不是逐元素相加,那是 NumPy 的行为)。这里把特征列名列表末尾加上 "label" 列名。

第二步:df[列名列表] 是”按列子集化”

virginica[["sepal_length", ..., "label"]]   # 传一个列名 list,取这几列组成新 DataFrame

关键区别:

  • df["label"]单个字符串 → 返回一列 Series
  • df[["label"]]df[cols]列表 → 返回 DataFrame(哪怕只有一列)

合起来的意思:virginica 这张表里,只保留 4 个特征列 + label 列,丢掉其他列,重新赋值回去。 数据清洗里挑选需要的列时超常用。

6. 常见坑

  • 三元表达式漏了 elsex if cond(放 for 前面)语法错误,选值必须有 else;只有当过滤(放 for 后面)时才不用 else
  • 两种 if 位置搞混[x for x in xs if cond] 是过滤;[a if cond else b for x in xs] 是每个都映射成 a 或 b,元素个数不变
  • 列表 + 当成逐元素加[1,2]+[3][1,2,3](拼接);想逐元素加要用 NumPy 数组
  • 选列用了单括号还是双括号df['a'] 出 Series,df[['a']] 出 DataFrame,下游代码期望哪种要对上
  • 选列的列名拼错/不存在df[cols] 里有不存在的列名会 KeyError
  • 推导式套太多层:三层以上的嵌套推导式可读性差,不如老老实实写 for 循环

7. 延伸阅读 / 关联概念

  • lambda — 另一种常和推导式/map/filter 搭配的简写;见 lambda.md
  • 矩阵运算 / 点积np.dot(w, x) 的数学含义;见 matrix-operations.md
  • 广播 / np.where — 把三元逻辑向量化到整个数组;见 broadcasting.md
  • pandas 选列 vs loc/ilocdf[cols] 选列,行列都要选时用 loc;见 pandas-loc-iloc.md
  • Series vs DataFrame — 单括号/双括号返回类型的区别;见 numpy-pandas-datatypes.md
  • 官方文档:List Comprehensions