3. Python基础速览:列表、字典、函数、类、面向对象编程在工程中的应用

说句实在话,很多人刚开始学Python,觉得语法简单,刷刷题就过了。但一上手工程,就发现不对劲。列表怎么越用越慢?字典的键为什么不能是列表?函数参数传值还是传引用?类继承怎么搞得代码一团乱麻?

这些坑,我几乎都踩过。今天这一章,咱们就直奔主题。不谈考试,只谈工程。看看列表、字典、函数、类这些东西,究竟怎么玩才能让代码跑得又快又稳。你想想看,如果每次写循环都能快几毫秒,优化参数时少掉几个坑,累积起来的效率是很可观的。

3.1 列表:不只是“数组”这么简单

列表是Python中最常用的数据结构之一。很多人把它当数组用,其实它的设计精妙得多。

我个人习惯,把列表分为“用途”和“性能”两个维度来看。用途上,它是动态数组,能存任意类型。性能上,插入和删除的位置决定了效率,这个必须心里有数。

核心要点:列表尾部操作(append、pop)是O(1),头部操作(insert(0, ...)、pop(0))是O(n)。工程中如果频繁在头部插入删除,改用collections.deque。

我在项目中遇到过一个问题:用列表存日志流水,每天上百万条,每次都从头部插入新数据。结果程序越来越慢,最后内存都炸了。

后来用deque,设置最大长度,自动丢弃旧数据。代码几行就解决了。

from collections import deque

log_queue = deque(maxlen=10000)  # 最多保留最近10000条
log_queue.appendleft(new_log)    # 头部插入,O(1)
log_queue.pop()                  # 尾部弹出,O(1)
提示:列表推导式(list comprehension)比for循环快,尤其当你的条件是简单的转换或过滤时。我习惯用它来做数据预处理。
# 不推荐
values = []
for x in raw_data:
    if x > 0:
        values.append(x * 2)

# 推荐
values = [x * 2 for x in raw_data if x > 0]

3.2 字典:工程优化的第一利器

字典,说白了就是哈希表。查找效率O(1),这在对接参数时简直是神器。你的参数名、特征值、配置项,全都能用字典来管理。

我曾经在调试一个参数协同优化的脚本时,发现每次从列表里找参数都要循环几十万次。改成字典后,速度提升了上百倍。

# 慢:列表查找O(n)
params_list = [{'name': 'lr', 'value': 0.01}, {'name': 'batch_size', 'value': 32}]
for param in params_list:
    if param['name'] == 'lr':
        print(param['value'])

# 快:字典查找O(1)
params_dict = {'lr': 0.01, 'batch_size': 32}
print(params_dict['lr'])

但这里有个大坑:字典的键必须是不可变类型。字符串、数字、元组可以。列表不行。为什么?因为哈希表要求键的哈希值不变,列表可变,哈希值就变了。

注意:千万不要用列表当字典的键。如果你非要存多对一关系,可以把列表转成元组。
# 错误示范
d = {}
d[[1, 2, 3]] = 'test'  # TypeError: unhashable type: 'list'

# 正确做法
d = {}
d[(1, 2, 3)] = 'test'  # 元组键是允许的

3.3 函数:把逻辑拆成小块

函数不只是“一段可重用的代码”。在工程中,函数是组织逻辑的最小单元。我建议每个函数只做一件事,并且把它做好。

举个例子,参数预处理、特征计算、结果输出,分别写不同的函数。这样调试时,你只用看一个函数,不用满屏幕找逻辑。

def preprocess_params(raw_params):
    """清洗和校验参数"""
    processed = {}
    for key, value in raw_params.items():
        if value is not None and value > 0:
            processed[key] = float(value)
    return processed

def compute_feature(input_data, config):
    """基于配置计算特征"""
    feature = input_data * config['scale'] + config['offset']
    return feature

def save_result(feature, path):
    """保存结果到文件"""
    with open(path, 'w') as f:
        f.write(str(feature))
提示:写函数时,给每个参数指定类型提示(type hints),工程大了以后你绝对会感谢这个习惯。
def normalize(data: list[float], mean: float, std: float) -> list[float]:
    """数据归一化"""
    return [(x - mean) / std for x in data]

3.4 类与面向对象编程:工程化的基石

我见过不少人,觉得面向对象就是“为了写类而写类”。其实不然。在工程中,类是把数据和方法捆绑在一起的高级抽象。当你处理多个同类的对象时,类能让代码结构清晰十倍。

比如参数协同优化,每个参数都有自己的范围、类型、优化状态。用字典当然可以存,但当你需要为每个参数增加方法时,类就显出优势了。

class Parameter:
    """一个可优化的参数"""
    def __init__(self, name: str, value: float, min_val: float, max_val: float):
        self.name = name
        self.value = value
        self.min_val = min_val
        self.max_val = max_val
    
    def clamp(self):
        """将值限制在范围内"""
        self.value = max(self.min_val, min(self.max_val, self.value))
    
    def perturb(self, delta: float):
        """在一定范围内扰动"""
        self.value += delta
        self.clamp()
    
    def __repr__(self):
        return f"Param({self.name}={self.value:.4f})"

有了类,你就可以批量创建参数、统一操作,这在优化循环中特别好用。

# 创建参数集合
params = [
    Parameter('learning_rate', 0.01, 1e-5, 1.0),
    Parameter('batch_size', 32, 1, 512),
    Parameter('dropout', 0.5, 0.0, 1.0)
]

# 统一扰动
for p in params:
    p.perturb(0.1)

print(params)
避坑指南:我曾经把所有的逻辑都塞进一个类里,结果类越来越庞大,超过1000行。后来拆成多个小类,用组合代替继承,代码才变得可维护。记住,继承不要超过三层。

这里还要提一下__repr__和__str__。在调试时,打印出优雅的对象信息能节省大把时间。我习惯给每个类都加上__repr__,这样打印列表时,每个参数一目了然。

3.5 面向对象编程在工程中的应用模式

在参数协同优化这个场景里,我常用的模式是:策略模式 + 组合。每种优化算法(贝叶斯优化、随机搜索、网格搜索)都实现相同的接口,然后在主程序中切换使用。

class OptimizerStrategy:
    """优化策略基类"""
    def optimize(self, params: list[Parameter]) -> list[Parameter]:
        raise NotImplementedError

class RandomSearch(OptimizerStrategy):
    def optimize(self, params):
        for p in params:
            p.perturb(random.uniform(-0.5, 0.5))
        return params

class GridSearch(OptimizerStrategy):
    def __init__(self, steps: int = 10):
        self.steps = steps
    def optimize(self, params):
        # 网格搜索具体实现
        pass

这样,如果你想换一种优化方法,只需换一个策略类,其他代码不用动。这叫“开闭原则”——对扩展开放,对修改关闭。

嗯,我这里整理了一个对比,帮你快速理清楚不同数据结构在工程中的定位:

结构 核心特性 工程适用场景 性能注意
列表(list) 有序、可重复、可变 日志序列、按顺序处理的数据 尾部操作快,头部操作慢
字典(dict) 键值对、哈希表 参数表、配置映射、缓存 查找O(1),键必须不可变
函数(function) 可调用、可传递 数据预处理、计算单元、回调 建议单一职责、带类型提示
类(class) 数据+方法、封装、继承 参数对象、优化器、策略模式 组合优于继承,继承层数≤3

为了更直观地展示这些基础概念之间的工程关系,我画了一张结构图。它大致反映了从数据到逻辑再到模块的演化路径。

Python基础概念工程演化路径 列表 list 字典 dict 元组 tuple 基础数据结构 / 数据层 函数 function 单一职责、类型提示、解耦 类 class 封装数据+行为 抽象逻辑层 / 组织代码 工程应用:参数协同优化 策略模式 + 组合 + 字典管理参数 + 列表批量处理 设计模式与架构 / 工程应用层 从数据到逻辑,再到工程架构,层层递进。越往上,抽象程度越高,复用性越强。

说实话,很多人学完Python基础知识后,还是不知道怎么把它们串起来用。这一章我特意用工程的视角,把列表、字典、函数、类和面向对象编程串成一条线。你想想看,一个参数协同优化的项目,底层是字典管理参数名和值,中间用列表批量处理参数对象,用函数封装每一步计算逻辑,用类把策略抽象成模块。

这套思路,是我在多个项目里反复验证过的。你照着这个模式写代码,至少不会在工程上犯基础错误。至于更复杂的用法——比如装饰器、迭代器、上下文管理器,那是后面章节的事了。