第2章:Python基础回顾——数据类型、控制流、函数定义、面向对象编程要点

说实话,很多教程一上来就教你怎么用 Dash 画图,结果读者连列表推导式和生成器都分不清。我个人觉得,把基础再过一遍,反而能省下后面调试的功夫。我在带团队时经常说:“基础不牢,地动山摇。” 咱们就从 Python 的四个核心模块开始:数据类型、控制流、函数、面向对象。这些在 Dash 开发里天天都会碰到。

本章核心:掌握 Python 的基础语法结构,为后续编写 Dash 回调、数据处理函数和自定义组件打下坚实的地基。

2.1 核心数据类型

Python 的数据类型其实就两大类:不可变和可变。嗯,这两个概念在 Dash 里特别重要——你想想看,回调用到的数据如果被意外修改了,排查起来非常痛苦。

类别 类型 示例 是否可变
数值 int、float、complex 42、3.14、1+2j 不可变
布尔 bool True、False 不可变
字符串 str "差异基因" 不可变
列表 list [0.01, 0.05, 0.1] 可变
元组 tuple ("基因A", "基因B") 不可变
字典 dict {"pvalue": 0.01, "logFC": 2.5} 可变
集合 set {-1, 0, 1} 可变

我记得有一次处理火山图数据,不小心用了一个可变对象当回调函数的默认参数。结果每次点击更新按钮,图表都叠加了上一次的数据,页面卡得不行。后来排查了半小时才发现是默认参数共享引用的问题。

小贴士:在 Dash 中存储 DataFrame 或列表等可变数据类型时,尽量使用 copy() 方法创建副本,避免多个回调共享同一对象引发的“幽灵 Bug”。

2.1.1 字符串操作

字符串是你在 Dash 里打交道最多的类型之一——组件 id、标签文本、文件名,样样离不开它。我最常用的是 f-string,可读性很好:

gene = "TP53"
logFC = 2.3
adj_pval = 0.001

summary = f"基因:{gene},logFC:{logFC:.2f},校正P值:{adj_pval:.2e}"
print(summary)
# 输出:基因:TP53,logFC:2.30,校正P值:1.00e-03

2.1.2 列表与字典推导式

推导式是 Python 的“语法糖”,一旦用上就回不去了。我写差异表达筛选代码时经常这么用:

# 传统写法
significant_genes = []
for gene, pval in gene_pvalue_pairs:
    if pval < 0.05:
        significant_genes.append(gene)

# 推导式写法(更 Pythonic)
significant_genes = [gene for gene, pval in gene_pvalue_pairs if pval < 0.05]
警告:千万别在推导式里嵌套超过两层。我曾经接手过一个项目,里面有个四层嵌套的字典推导式,读起来像天书。后来花了一整天重构才看懂。你想想看,三个月后你自己都未必能看懂这样的代码。

2.2 控制流

控制流说白了就是“指挥 Python 按什么顺序干活”。在 Dash 应用里,你用控制流来决定:用户点了按钮后该更新哪个图?数据缺失时弹什么提示?

2.2.1 条件判断

def classify_gene(logFC, pvalue):
    if pvalue > 0.05:
        return "不显著"
    elif logFC > 0:
        return "上调"
    else:
        return "下调"

print(classify_gene(1.5, 0.01))  # 输出:上调

我个人习惯:条件分支超过三个时,我会改用字典映射或 match-case(Python 3.10+),代码会更清爽。

2.2.2 循环遍历

遍历 DataFrame 的行、遍历文件列表、遍历用户上传的多组数据——循环无处不在。

# 遍历字典
genes = {"TP53": 0.001, "EGFR": 0.03, "KRAS": 0.5}
for gene, pval in genes.items():
    if pval < 0.05:
        print(f"{gene} 显著差异表达")
小贴士:在处理大型基因表达矩阵时,尽量用 pandas 的向量化操作代替显式循环,速度能快几十倍。这点在课程后面的数据处理章节会详细讲。

2.3 函数定义

函数就是“把你常用的操作打包起来,随时调用”。在 Dash 里,回调函数、数据处理函数都是你写的自定义函数。

2.3.1 基础函数结构

def calculate_volcano_data(df, pvalue_cutoff=0.05, logFC_cutoff=1.0):
    """
    从DataFrame中提取火山图所需数据。
    
    Args:
        df: 包含pvalue和logFC列的DataFrame
        pvalue_cutoff: 显著性阈值
        logFC_cutoff: 差异倍数阈值
    
    Returns:
        包含颜色分类的DataFrame
    """
    import numpy as np
    df['category'] = '不显著'
    up_mask = (df['pvalue'] < pvalue_cutoff) & (df['logFC'] > logFC_cutoff)
    down_mask = (df['pvalue'] < pvalue_cutoff) & (df['logFC'] < -logFC_cutoff)
    df.loc[up_mask, 'category'] = '上调'
    df.loc[down_mask, 'category'] = '下调'
    return df

2.3.2 匿名函数 lambda

在 Dash 的 DataTable 或回调里,lambda 经常用来做简单的数据变换:

# 在回调中对列表元素进行平方
squared = list(map(lambda x: x**2, [1, 2, 3, 4, 5]))
print(squared)  # [1, 4, 9, 16, 25]
警告:不要在 lambda 里写复杂逻辑!我见过有人在 lambda 里嵌套了三个条件判断和两个循环,想改都不知道从哪里下手。有这功夫,不如写个完整的 def 函数,加几行注释。

2.4 面向对象编程要点

面向对象(OOP)在 Dash 里可能不是必须的,但当你开始写自定义组件或复杂的回调管理器时,它就是必备技能了。

2.4.1 类与实例

类就像“基因的模板”,实例就是“具体的某个基因”。

class Gene:
    """基因对象,封装基本属性"""
    species = "Homo sapiens"  # 类属性

    def __init__(self, symbol, pvalue, logFC):
        self.symbol = symbol
        self.pvalue = pvalue
        self.logFC = logFC

    def is_significant(self, cutoff=0.05):
        return self.pvalue < cutoff

    def regulation_direction(self):
        if not self.is_significant():
            return "不显著"
        return "上调" if self.logFC > 0 else "下调"

# 使用
tp53 = Gene("TP53", 0.001, 2.5)
print(tp53.regulation_direction())  # 输出:上调

2.4.2 封装、继承、多态

这三个概念你基本了解一下就够了。在 Dash 开发里,你会经常遇到:

  • 封装:把你的数据处理逻辑藏进类的方法里,外部只管调用接口
  • 继承:Dash 的组件类都是继承自 Component,你可以继承它们做二次开发
  • 多态:不同的类实现同名方法,调用时根据对象类型自动选择执行哪个
# 简单的继承示例
class SignificantGene(Gene):
    """显著差异表达基因(继承自Gene)"""
    def __init__(self, symbol, pvalue, logFC, rank):
        super().__init__(symbol, pvalue, logFC)
        self.rank = rank

    def info(self):
        return f"Rank {self.rank}: {self.symbol} (logFC={self.logFC:.2f})"

top_gene = SignificantGene("TP53", 0.001, 2.5, 1)
print(top_gene.info())  # 输出:Rank 1: TP53 (logFC=2.50)
避坑指南:我曾经在写 Dash 回调时,把一个大段的业务逻辑堆在了回调函数里。结果代码越来越臃肿,根本没法测试。后来我重构了一次,把所有数据清洗、分析逻辑都封装到独立的类里。回调函数只负责调用这些类的接口,代码瞬间清爽了。记住:回调只负责“连接”,业务逻辑交给类。

2.5 本章知识体系

下面这张图帮你把本章的知识点串起来。箭头表示前后依赖关系——面向对象是函数的升级版,函数操作数据类型,控制流则贯穿始终。

Python基础回顾:知识框架图 数据类型 不可变 可变 控制流 条件 循环 函数 def lambda 面向对象 封装 · 继承 · 多态 函数操作数据类型 面向对象封装函数 箭头方向 = 依赖进化关系
小结:这一章的知识点很多,但并不要求你一下子全记住。我建议你先把 Python 环境打开,把代码示例都敲一遍。遇到不懂的,随时回来翻这张知识框架图。在后面的章节里,我们会在实际的 Dash 代码中反复用到这些基础——到时候你自然就熟了。