订阅更新

数据质量校验脚本写了500行——Python面向对象实战

Python手册 第 17 题 约 7 分钟读完

本文目录
  1. 先说结论
  2. dataclass——替代dict传递数据
  3. 继承+抽象类——可插拔的校验器
  4. 组合——构建校验流水线
  5. 面试高频追问
  6. 总结

先说结论

大数据开发者不需要精通设计模式,但需要掌握三个OOP核心:① dataclass(数据类,替代dict传递结构化数据);② 继承+抽象类(定义接口规范,让不同实现可插拔);③ 组合优于继承(用组合构建复杂逻辑而不是深层继承树)。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

dataclass——替代dict传递数据

# 用dict传递数据(不清晰,容易拼错key)
result = {'table': 'events', 'check': 'null_rate',
          'passed': True, 'value': 0.02, 'threshold': 0.05}
print(result['talbe'])  # KeyError!拼错了key
 
# 用dataclass(清晰、有类型提示、IDE自动补全)
from dataclasses import dataclass
 
@dataclass
class CheckResult:
    table: str
    check: str
    passed: bool
    value: float
    threshold: float
 
result = CheckResult(table='events', check='null_rate',
                     passed=True, value=0.02, threshold=0.05)
print(result.table)   # IDE自动补全,拼错会报AttributeError
print(result)         # 自动生成__repr__:CheckResult(table='events',...)

继承+抽象类——可插拔的校验器

from abc import ABC, abstractmethod
 
# 定义校验器接口
class BaseValidator(ABC):
    def __init__(self, table_name, threshold):
        self.table_name = table_name
        self.threshold = threshold
    
    @abstractmethod
    def validate(self, df) -> CheckResult:
        pass
 
# 具体实现:null率校验
class NullRateValidator(BaseValidator):
    def __init__(self, table_name, column, threshold=0.05):
        super().__init__(table_name, threshold)
        self.column = column
    
    def validate(self, df) -> CheckResult:
        null_rate = df[self.column].isna().mean()
        return CheckResult(
            table=self.table_name, check=f'null_rate_{self.column}',
            passed=null_rate <= self.threshold,
            value=null_rate, threshold=self.threshold)
 
# 具体实现:行数校验
class RowCountValidator(BaseValidator):
    def validate(self, df) -> CheckResult:
        count = len(df)
        return CheckResult(
            table=self.table_name, check='row_count',
            passed=count >= self.threshold,
            value=count, threshold=self.threshold)

组合——构建校验流水线

# 校验流水线:组合多个校验器
class ValidationPipeline:
    def __init__(self):
        self.validators = []
    
    def add(self, validator: BaseValidator):
        self.validators.append(validator)
        return self  # 链式调用
    
    def run(self, df) -> list:
        results = [v.validate(df) for v in self.validators]
        failed = [r for r in results if not r.passed]
        if failed:
            self.alert(failed)
        return results
    
    def alert(self, failed_results):
        msg = '\n'.join(f'{r.table}.{r.check}: {r.value:.4f} > {r.threshold}'
                        for r in failed_results)
        send_feishu_alert(f'Data Quality Alert:\n{msg}')
 
# 使用(清晰、可扩展)
pipeline = (ValidationPipeline()
    .add(NullRateValidator('events', 'user_id', threshold=0.01))
    .add(NullRateValidator('events', 'amount', threshold=0.05))
    .add(RowCountValidator('events', threshold=1000000))
    .add(NullRateValidator('users', 'email', threshold=0.1)))
 
results = pipeline.run(df)

重构效果:从500行面条代码变成200行结构化代码。新增一种校验类型只需要继承BaseValidator实现validate方法——不用改pipeline的任何代码。新增一张表只需要在pipeline里add一行。这就是OOP的核心价值——可扩展性。

面试高频追问

追问一:dataclass和namedtuple有什么区别?

dataclass是可变的(字段可以修改),namedtuple是不可变的。dataclass支持默认值、类型提示、自定义方法。namedtuple更轻量内存更小。数据需要修改时用dataclass,不需要修改时用namedtuple。Python 3.10+的dataclass(frozen=True)也可以不可变。

追问二:什么时候用继承什么时候用组合?

'is-a'关系用继承(NullRateValidator is a Validator),'has-a'关系用组合(Pipeline has Validators)。经验法则:继承层次不超过2-3层。如果继承树变深了说明设计有问题,考虑用组合替代。大数据开发中组合比继承用得多。

追问三:大数据开发需要学设计模式吗?

学3个就够了:① 工厂模式(根据配置创建不同类型的Validator/Connector);② 策略模式(不同的校验/清洗策略可插拔,就是上面的BaseValidator);③ 装饰器模式(@retry/@timer给函数加行为)。不需要学23种设计模式,大部分在大数据开发中用不到。

总结

Python OOP面试讲三个核心:① dataclass替代dict传递数据(类型安全、IDE补全、自动repr);② 继承+抽象类定义接口规范(BaseValidator+具体实现,可插拔可扩展);③ 组合构建流水线(Pipeline组合多个Validator,链式调用清晰)。能讲出500行脚本重构到200行的实际案例和可扩展性的提升,面试官觉得你写的是工程代码不是脚本。

下一篇聊Pandas性能:apply比向量化慢100倍——Pandas怎么写才快?

你们大数据脚本用面向对象吗?评论区聊聊~

关注「Wood聊大数据」| 9年大数据老兵,只讲大数据面试干货

刷题进度保存在当前浏览器