重写Django Model.save()单一调用触发pylint参数错误,如何解决?
我开发并使用这个模型父类已有多年,在活跃站点上运行完全稳定。近期对无关代码做了微小调整,用最新版superlinter本地检查时,发现一段多年未改动的代码出现lint错误——但GitHub CI使用的旧版superlinter从未报错。
我的模型类重写了save()方法,会添加几个额外参数,调用super().save()前从kwargs中取出这些参数。类内其他位置的save调用带额外参数时linter都没问题,唯独在重写的from_db方法中报错:
@classmethod def from_db(cls, *args, **kwargs): # 实例化模型对象 rec = super().from_db(*args, **kwargs) # 如果未启用自动更新(即非“延迟”模式) if not cls.get_coordinator().are_lazy_updates_enabled(): return rec # 获取字段名 queryset_fields = set(args[1]) # field_names # 取维护字段与查询集字段的交集 common_fields = set(cls.get_my_update_fields()).intersection(queryset_fields) # 查找值为None的维护字段 lazy_update_fields = [fld for fld in common_fields if getattr(rec, fld) is None] if len(lazy_update_fields) > 0: # 触发延迟自动更新 rec.save(fields_to_autoupdate=lazy_update_fields, via_query=True) return rec
我尝试给rec加类型提示,但没法满足linter要求。推测是super().from_db()返回的类型没包含自定义参数,但实际我的save重载确实被调用了。
补充信息
- 旧版pylint/superlinter不会对该save调用报错,目前已临时忽略本地新版superlinter的报错来推送代码。
- 基类的
from_db方法逻辑很简单,我甚至考虑过复制这段代码到自己的方法里,不再调用super().from_db(虽然这看起来很不合理):
@classmethod def from_db(cls, db, field_names, values): if len(values) != len(cls._meta.concrete_fields): values_iter = iter(values) values = [ next(values_iter) if f.attname in field_names else DEFERRED for f in cls._meta.concrete_fields ] new = cls(*values) new._state.adding = False new._state.db = db return new
- 日志已确认save重载被正确调用:
[Tue May 28 15:13:35.396716 2024] [wsgi:error] [pid 2648907:tid 2649014] [remote 172.20.202.79:47324] Triggering lazy auto-update of fields: TracerLabel.{name} [Tue May 28 15:13:35.396755 2024] [wsgi:error] [pid 2648907:tid 2649014] [remote 172.20.202.79:47324] Auto-updated TracerLabel.name in TracerLabel.9 using TracerLabel._name from [<empty>] to [13C6].
其中Triggering lazy auto-update of fields: ...仅在此方法中打印,Auto-updated ...仅通过我的save()重载打印,足以证明该save调用确实触发了自定义save方法并传递了参数。
解决办法
1. 明确指定rec的类型为当前类
用类型标注明确告诉linterrec是当前模型类的实例,避免依赖父类方法的返回类型推断:
from typing import TypeVar # 定义与当前类绑定的TypeVar T = TypeVar('T', bound='YourModelClass') @classmethod def from_db(cls: type[T], *args, **kwargs) -> T: rec: T = super().from_db(*args, **kwargs) # 后续逻辑保持不变
如果存在循环导入问题,也可以用字符串形式标注类型:rec: 'YourModelClass' = super().from_db(*args, **kwargs)。
2. 对特定行禁用Pylint报错
若确认代码逻辑无误,可直接在报错的save调用行添加忽略注释,快速解决:
rec.save(fields_to_autoupdate=lazy_update_fields, via_query=True) # pylint: disable=unexpected-keyword-arg
这种方式适合临时适配新版linter,无需改动核心逻辑。
3. 重写from_db时直接创建当前类实例
复制基类from_db的逻辑,直接创建当前类的实例,让linter明确识别实例类型:
@classmethod def from_db(cls, db, field_names, values): if len(values) != len(cls._meta.concrete_fields): values_iter = iter(values) values = [ next(values_iter) if f.attname in field_names else DEFERRED for f in cls._meta.concrete_fields ] # 直接实例化当前类,类型明确 rec = cls(*values) rec._state.adding = False rec._state.db = db # 后续延迟更新逻辑不变 if not cls.get_coordinator().are_lazy_updates_enabled(): return rec queryset_fields = set(field_names) common_fields = set(cls.get_my_update_fields()).intersection(queryset_fields) lazy_update_fields = [fld for fld in common_fields if getattr(rec, fld) is None] if len(lazy_update_fields) > 0: rec.save(fields_to_autoupdate=lazy_update_fields, via_query=True) return rec
这种方式彻底消除类型推断歧义,从根源解决报错。
4. 调整linter配置或插件版本
新版superlinter可能引入了更严格的检查规则,可尝试:
- 降级
pylint-django插件版本,匹配CI环境的旧版配置 - 在
.pylintrc中针对当前模型类或方法禁用unexpected-keyword-arg规则,避免全局禁用影响其他代码
内容的提问来源于stack exchange,提问作者hepcat72

