如何在Luigi Task中使用非参数实例属性实现组合避免类泛滥?
核心疑问解答
1. 非显著Parameter存储下载器对Luigi缓存的影响
significant=False的参数不会参与task_id的计算,只要两个DownloadMetoDataTask实例的date参数相同,无论传入的下载器实例是什么,Luigi都会判定为同一任务。只要其中一个实例执行成功,其余同date的实例都会被直接判定为已完成,不会重复执行,完全不符合多渠道下载的需求。
此外自定义对象作为Parameter传递时,无法被Luigi正常序列化,分布式执行场景下参数无法正常传递给Worker节点,会出现未知错误。
2. 下载器作为Parameter的接口要求
只要被声明为Luigi Parameter,就必须符合Parameter的接口规范:默认需要支持序列化、可被转换为字符串类型,同时要实现normalize、parse等内置方法。你的自定义下载器类没有实现这些逻辑,所以会触发类型警告,后续也可能出现参数解析异常等隐藏问题。
重写get_param_values的风险
完全不推荐重写该方法:get_param_values是Luigi参数体系的核心逻辑,负责参数校验、解析、转换等核心功能,重写会破坏Luigi原生的命令行参数解析、参数校验能力,且后续Luigi版本升级时极容易出现兼容性问题,维护成本极高。
推荐实现方案
你不需要修改Luigi的核心逻辑,也不需要新增伪参数,只需要调整自定义属性的赋值方式即可,以下两种方案都可以满足需求:
方案1:构造函数提前截胡自定义参数
调整__init__的参数顺序,把自定义的下载器参数放在最前面,在调用父类构造函数前完成赋值,避免自定义参数进入Luigi的参数校验逻辑:
class MeteoDownloaderTask(luigi.Task): date = luigi.Parameter() def __init__(self, downloader, *args, **kwargs): # 先处理自定义参数,不传入父类构造 self.downloader = downloader super().__init__(*args, **kwargs) # 实例化时下载器作为第一个位置参数传入 t = MeteoDownloaderTask(APIMetoDownloader(), date = "2021_11_15")
方案2:工厂方法封装(更安全)
通过类方法封装实例化逻辑,先正常生成Luigi任务实例,再给实例赋值自定义属性,完全不触碰Luigi的参数解析流程:
class MeteoDownloaderTask(luigi.Task): date = luigi.Parameter() @classmethod def with_downloader(cls, downloader, **luigi_params): task = cls(**luigi_params) task.downloader = downloader return task # 调用方式 t = MeteoDownloaderTask.with_downloader( downloader=APIMetoDownloader(), date="2021_11_15" )
两种方案都不需要维护额外的映射字典,也不会生成多余的子类,完全符合你用组合替代继承的设计需求。
注意:如果你的任务需要通过命令行启动,只能通过字符串参数指定下载器,此时映射字典方案是唯一可行的实现方式,少量的维护成本远低于类泛滥带来的复杂度。
内容的提问来源于stack exchange,提问作者MassyB

