跳转至

生信面试 Python 编程题精选

一句话概述:生信面试中 Python 编程题集中在序列处理、文件解析、数据分析和算法实现四个方向,掌握字符串操作、文件I/O、pandas 和 Biopython 就能应对大部分面试。


核心知识点速查表

概念白话解释
字符串操作DNA序列本质就是字符串,各种切片、翻转、替换
字典(dict)键值对存储,如
列表推导式一行代码生成列表的简洁写法
文件I/O读写FASTA/FASTQ/VCF等生信文件
pandas数据分析核心库(类似Excel但更强大)
Biopython生物信息专用Python库
正则表达式用模式匹配文本(re模块)
defaultdict带默认值的字典,统计计数超好用

一、DNA 序列操作(最高频)

题1:反向互补序列

# ========== 反向互补序列(面试必考) ==========
def reverse_complement(seq):
    """
    返回DNA序列的反向互补链
    A↔T, C↔G
    """
    complement = {                              # 互补碱基字典
        'A': 'T', 'T': 'A',
        'C': 'G', 'G': 'C',
        'N': 'N'                                # N保持不变
    }
    # 反向([::-1]) + 互补(complement.get)
    return ''.join(
        complement.get(base, 'N')               # 获取每个碱基的互补
        for base in reversed(seq.upper())       # 反向遍历,统一大写
    )

# 更简洁的写法(面试加分)
def reverse_complement_v2(seq):
    """使用 str.maketrans 的高效写法"""
    table = str.maketrans('ATCGatcg', 'TAGCtagc')  # 创建转换表
    return seq.translate(table)[::-1]               # 互补后反转

# 测试
print(reverse_complement("ATCGATCG"))   # CGATCGAT
print(reverse_complement("AATTCCGG"))   # CCGGAATT

题2:GC含量计算

# ========== GC含量计算 ==========
def gc_content(seq):
    """计算DNA序列的GC含量(百分比)"""
    seq = seq.upper()                           # 统一大写
    gc = seq.count('G') + seq.count('C')        # 统计G和C数量
    total = len(seq)                            # 序列总长度
    if total == 0:                              # 防止除以0
        return 0.0
    return round(gc / total * 100, 2)           # 百分比,保留2位小数

# 滑动窗口GC含量(进阶版)
def sliding_gc(seq, window=100, step=10):
    """用滑动窗口计算GC含量变化"""
    results = []                                # 存储结果
    for i in range(0, len(seq) - window + 1, step):  # 滑动窗口
        sub = seq[i:i+window]                   # 截取窗口序列
        gc = gc_content(sub)                    # 计算GC含量
        results.append((i, gc))                 # 保存位置和GC值
    return results

print(gc_content("ATCGATCG"))           # 50.0
print(gc_content("AAATTT"))             # 0.0
print(gc_content("GGGCCC"))             # 100.0

题3:DNA转RNA转蛋白质

# ========== 中心法则:DNA→RNA→蛋白质 ==========
def transcribe(dna):
    """DNA转录为mRNA(T→U)"""
    return dna.upper().replace('T', 'U')        # 把T替换为U

def translate(rna):
    """mRNA翻译为蛋白质序列"""
    codon_table = {                             # 密码子表
        'UUU': 'F', 'UUC': 'F', 'UUA': 'L', 'UUG': 'L',
        'CUU': 'L', 'CUC': 'L', 'CUA': 'L', 'CUG': 'L',
        'AUU': 'I', 'AUC': 'I', 'AUA': 'I', 'AUG': 'M',
        'GUU': 'V', 'GUC': 'V', 'GUA': 'V', 'GUG': 'V',
        'UCU': 'S', 'UCC': 'S', 'UCA': 'S', 'UCG': 'S',
        'CCU': 'P', 'CCC': 'P', 'CCA': 'P', 'CCG': 'P',
        'ACU': 'T', 'ACC': 'T', 'ACA': 'T', 'ACG': 'T',
        'GCU': 'A', 'GCC': 'A', 'GCA': 'A', 'GCG': 'A',
        'UAU': 'Y', 'UAC': 'Y', 'UAA': '*', 'UAG': '*',
        'CAU': 'H', 'CAC': 'H', 'CAA': 'Q', 'CAG': 'Q',
        'AAU': 'N', 'AAC': 'N', 'AAA': 'K', 'AAG': 'K',
        'GAU': 'D', 'GAC': 'D', 'GAA': 'E', 'GAG': 'E',
        'UGU': 'C', 'UGC': 'C', 'UGA': '*', 'UGG': 'W',
        'CGU': 'R', 'CGC': 'R', 'CGA': 'R', 'CGG': 'R',
        'AGU': 'S', 'AGC': 'S', 'AGA': 'R', 'AGG': 'R',
        'GGU': 'G', 'GGC': 'G', 'GGA': 'G', 'GGG': 'G',
    }
    protein = []                                # 存储氨基酸
    for i in range(0, len(rna) - 2, 3):         # 每3个碱基一组
        codon = rna[i:i+3]                      # 截取密码子
        aa = codon_table.get(codon, 'X')        # 查密码子表,未知用X
        if aa == '*':                           # 遇到终止密码子
            break
        protein.append(aa)                      # 添加氨基酸
    return ''.join(protein)                     # 拼接成蛋白质序列

# 测试
dna = "ATGGCTAAATGA"                           # 编码 M A K (stop)
rna = transcribe(dna)                           # AUGGCUAAAU GA
print(f"RNA: {rna}")
print(f"Protein: {translate(rna)}")             # MAK

二、文件解析(必考)

题4:解析FASTA文件

# ========== 解析FASTA文件(不用Biopython) ==========
def parse_fasta(filepath):
    """
    解析FASTA文件,返回 {序列名: 序列} 字典
    FASTA格式:>header\nSEQUENCE...
    """
    sequences = {}                              # 存储结果
    current_name = None                         # 当前序列名
    current_seq = []                            # 当前序列(列表拼接比字符串快)

    with open(filepath, 'r') as f:              # 打开文件
        for line in f:                          # 逐行读取
            line = line.strip()                 # 去除首尾空白
            if not line:                        # 跳过空行
                continue
            if line.startswith('>'):            # header行
                if current_name:                # 保存上一条序列
                    sequences[current_name] = ''.join(current_seq)
                current_name = line[1:].split()[0]  # 取>后第一个词作为名字
                current_seq = []                # 重置序列
            else:
                current_seq.append(line)        # 累加序列行

    if current_name:                            # 保存最后一条序列
        sequences[current_name] = ''.join(current_seq)

    return sequences

# 使用
seqs = parse_fasta("sequences.fasta")
for name, seq in seqs.items():
    print(f"{name}: length={len(seq)}, GC={gc_content(seq)}%")

题5:解析FASTQ并统计质量

# ========== 解析FASTQ文件并统计碱基质量 ==========
def parse_fastq(filepath):
    """
    解析FASTQ文件,返回 reads 列表
    FASTQ格式:@header / 序列 / + / 质量
    """
    reads = []                                  # 存储所有reads
    with open(filepath, 'r') as f:
        while True:
            header = f.readline().strip()       # 第1行:@header
            if not header:                      # 文件末尾
                break
            seq = f.readline().strip()          # 第2行:序列
            plus = f.readline().strip()         # 第3行:+
            qual = f.readline().strip()         # 第4行:质量字符串
            reads.append({
                'name': header[1:],             # 去掉@
                'seq': seq,
                'qual': qual,
            })
    return reads

def avg_quality(qual_string):
    """
    计算平均碱基质量分数
    质量字符的ASCII码 - 33 = Phred质量值(Illumina 1.8+)
    """
    scores = [ord(c) - 33 for c in qual_string]  # ASCII转Phred分数
    return sum(scores) / len(scores)              # 平均值

# 使用
reads = parse_fastq("sample.fastq")
print(f"Total reads: {len(reads)}")
low_quality = sum(1 for r in reads if avg_quality(r['qual']) < 20)
print(f"Low quality reads (Q<20): {low_quality}")

三、pandas 数据分析(高频)

题6:处理差异表达结果

# ========== pandas处理DESeq2结果 ==========
import pandas as pd

# 读取差异表达结果
df = pd.read_csv("deseq2_results.csv")         # 读取CSV
print(df.shape)                                 # 查看维度(行数, 列数)
print(df.head())                                # 查看前5行
print(df.describe())                            # 统计摘要

# 筛选显著差异基因
# 条件:|log2FoldChange| > 1 且 padj < 0.05
sig = df[
    (abs(df['log2FoldChange']) > 1) &           # 绝对值>1
    (df['padj'] < 0.05)                         # 调整后p值<0.05
].copy()                                        # .copy() 避免SettingWithCopyWarning

print(f"显著差异基因数: {len(sig)}")

# 分类:上调 vs 下调
up = sig[sig['log2FoldChange'] > 0]             # 上调基因
down = sig[sig['log2FoldChange'] < 0]           # 下调基因
print(f"上调: {len(up)}, 下调: {len(down)}")

# 排序:按padj排序,找最显著的基因
top_genes = sig.nsmallest(20, 'padj')           # padj最小的20个
print(top_genes[['gene', 'log2FoldChange', 'padj']])

# 保存结果
up.to_csv("upregulated_genes.csv", index=False)     # 保存上调基因
down.to_csv("downregulated_genes.csv", index=False)  # 保存下调基因

题7:合并多个数据表

# ========== 合并多个样本的表达量数据 ==========
import pandas as pd
import glob

# 读取多个样本的计数文件并合并
files = glob.glob("counts/*.txt")               # 获取所有计数文件
print(f"找到 {len(files)} 个样本文件")

merged = None                                    # 初始化合并结果
for f in sorted(files):
    sample = f.split('/')[-1].replace('.txt', '')  # 从文件名提取样本名
    data = pd.read_csv(f, sep='\t',
                       names=['gene', sample])    # 两列:基因名, 计数
    if merged is None:
        merged = data                             # 第一个文件
    else:
        merged = pd.merge(merged, data,
                          on='gene', how='outer')  # 按基因名合并

merged = merged.fillna(0)                        # 缺失值填0
merged = merged.set_index('gene')                # 基因名设为索引
print(f"合并结果: {merged.shape[0]} genes × {merged.shape[1]} samples")

# 过滤低表达基因(至少在一个样本中有10个以上reads)
expressed = merged[merged.max(axis=1) >= 10]     # 行最大值>=10
print(f"过滤后: {len(expressed)} genes")

四、算法题(偶尔考)

题8:找最长公共子序列(LCS)

# ========== 两条序列的最长公共子序列 ==========
def longest_common_subsequence(seq1, seq2):
    """
    动态规划求最长公共子序列
    生信应用:序列比对的基础
    """
    m, n = len(seq1), len(seq2)                 # 两条序列长度
    dp = [[0] * (n + 1) for _ in range(m + 1)]  # DP矩阵(m+1 × n+1)

    # 填充DP矩阵
    for i in range(1, m + 1):                    # 遍历seq1
        for j in range(1, n + 1):                # 遍历seq2
            if seq1[i-1] == seq2[j-1]:           # 字符匹配
                dp[i][j] = dp[i-1][j-1] + 1      # 左上角+1
            else:
                dp[i][j] = max(dp[i-1][j],       # 取上方和左方的最大值
                               dp[i][j-1])

    # 回溯找到LCS序列
    lcs = []
    i, j = m, n
    while i > 0 and j > 0:
        if seq1[i-1] == seq2[j-1]:               # 匹配
            lcs.append(seq1[i-1])
            i -= 1
            j -= 1
        elif dp[i-1][j] > dp[i][j-1]:           # 上方更大
            i -= 1
        else:                                     # 左方更大
            j -= 1

    return ''.join(reversed(lcs))                # 反转得到LCS

print(longest_common_subsequence("ATCGTAC", "ATGTAC"))  # ATGTAC

题9:K-mer频率统计

# ========== K-mer频率统计 ==========
from collections import Counter

def kmer_count(seq, k=3):
    """
    统计序列中所有k-mer的出现频率
    K-mer:长度为k的连续子序列
    应用:基因组组装、物种分类
    """
    seq = seq.upper()                           # 统一大写
    kmers = []                                   # 存储所有k-mer
    for i in range(len(seq) - k + 1):            # 滑动窗口
        kmer = seq[i:i+k]                        # 截取k-mer
        kmers.append(kmer)

    counts = Counter(kmers)                      # 统计频率
    return dict(counts.most_common())            # 按频率排序

# 测试
seq = "ATCGATCGATCG"
result = kmer_count(seq, k=3)
for kmer, count in sorted(result.items(), key=lambda x: -x[1]):
    print(f"{kmer}: {count}")                    # 打印每个k-mer的频率

五、实用脚本题

题10:批量重命名文件

# ========== 批量重命名测序文件 ==========
import os
import re

def rename_fastq_files(directory, pattern, replacement):
    """
    批量重命名FASTQ文件
    如:SRR12345_1.fastq.gz → Sample1_R1.fastq.gz
    """
    renamed = 0                                  # 计数
    for filename in os.listdir(directory):        # 遍历目录
        if not filename.endswith(('.fastq.gz', '.fq.gz')):
            continue                              # 跳过非fastq文件

        new_name = re.sub(pattern, replacement, filename)  # 正则替换
        if new_name != filename:                  # 名字有变化
            old_path = os.path.join(directory, filename)
            new_path = os.path.join(directory, new_name)
            os.rename(old_path, new_path)         # 重命名
            print(f"  {filename}{new_name}")
            renamed += 1

    print(f"共重命名 {renamed} 个文件")

# 示例:SRR编号改为样本名
# rename_fastq_files("./data", r"SRR\d+", "SampleX")

题11:VCF文件变异统计

# ========== VCF文件变异类型统计 ==========
from collections import defaultdict

def vcf_stats(vcf_file):
    """统计VCF文件中的变异类型"""
    stats = defaultdict(int)                     # 默认值为0的字典
    chrom_count = defaultdict(int)               # 每条染色体变异数

    with open(vcf_file, 'r') as f:
        for line in f:
            if line.startswith('#'):              # 跳过注释行
                continue
            fields = line.strip().split('\t')     # tab分割
            chrom = fields[0]                     # 染色体
            ref = fields[3]                       # 参考等位基因
            alt = fields[4]                       # 变异等位基因

            chrom_count[chrom] += 1               # 统计染色体

            # 判断变异类型
            if len(ref) == 1 and len(alt) == 1:   # SNP
                stats['SNP'] += 1
                stats[f'{ref}>{alt}'] += 1        # 具体的碱基替换类型
            elif len(ref) > len(alt):             # 缺失
                stats['DEL'] += 1
            elif len(ref) < len(alt):             # 插入
                stats['INS'] += 1
            else:                                  # 复杂变异
                stats['COMPLEX'] += 1

    print("=== 变异类型统计 ===")
    for vtype, count in sorted(stats.items(),
                               key=lambda x: -x[1]):
        print(f"  {vtype}: {count}")

    print("\n=== 染色体分布 ===")
    for chrom, count in sorted(chrom_count.items()):
        print(f"  {chrom}: {count}")

    return stats

# vcf_stats("variants.vcf")

六、常见报错与解决

问题原因解决方法
UnicodeDecodeError文件编码不是UTF-8open(f, encoding='latin-1')
MemoryError文件太大一次读入内存逐行读取或用pandas的chunksize
KeyError字典中没有这个键.get(key, default)
IndexError列表越界检查长度,加 if len(list) > n
SettingWithCopyWarningpandas切片赋值.copy().loc[]
FileNotFoundError路径错误os.path.exists() 先检查

七、面试高频问题

Q1:Python 列表和字典的时间复杂度?

列表:索引O(1),搜索O(n),末尾添加O(1),中间插入O(n)。 字典:查找O(1),插入O(1),删除O(1)(基于哈希表)。 所以需要频繁查找时用字典,需要有序遍历时用列表。

Q2:如何处理大文件(比如50GB的FASTQ)?

(1) 逐行读取而非一次性读入(for line in open(f));(2) 用生成器(yield)代替列表;(3) 用pandas的chunksize分块读取;(4) 用多进程并行处理。

Q3:Biopython 的 SeqIO 怎么用?

from Bio import SeqIO for record in SeqIO.parse("file.fasta", "fasta"): print(record.id, len(record.seq)) 支持fasta/fastq/genbank等多种格式,parse返回迭代器不占内存。

Q4:Python 2 和 Python 3 的主要区别?

(1) print变成函数 print();(2) 整数除法 3/2=1.5(Python2是1);(3) 字符串默认Unicode;(4) range返回迭代器不是列表。现在只用Python 3。


八、速查表

# === 生信 Python 面试速查 ===

# DNA操作
rev_comp = seq.translate(str.maketrans('ATCG','TAGC'))[::-1]  # 反向互补
gc = (seq.count('G')+seq.count('C'))/len(seq)*100             # GC含量

# 文件解析
with open(f) as fh:                    # 安全打开文件
    for line in fh:                    # 逐行读取(省内存)
        line = line.strip()            # 去除换行符

# pandas常用
df = pd.read_csv("f.csv")             # 读CSV
df = pd.read_csv("f.tsv", sep="\t")   # 读TSV
df[df['col'] > 0]                      # 条件筛选
df.groupby('col').mean()               # 分组统计
df.merge(df2, on='key')                # 合并表格
df.to_csv("out.csv", index=False)      # 保存

# collections 好用工具
from collections import Counter, defaultdict
Counter("ATCGATCG")                    # 字符频率统计
d = defaultdict(list)                  # 默认值为空列表的字典

# 正则表达式
import re
re.findall(r'ATG(?:[ATCG]{3})*?(?:TAA|TAG|TGA)', seq)  # 找ORF

参考资料:Biopython Tutorial 2024、Python for Biologists (Martin Jones)、Rosalind 生信编程练习平台