如何高效实现Python自定义对象的多属性排序?
问题场景
在Python中对自定义对象按多个属性排序时,传统嵌套if的__lt__实现会随着属性数量增加变得冗余。比如以下场景:
class A: def __init__(self, a, b): self.a = a self.b = b def __str__(self): return f'{self.__class__.__name__}({self.a}, {self.b})' A1 = A(1, 5) A2 = A(2, 5) A3 = A(1, 6) A4 = A(4, 0) A5 = A(-3, -3) A6 = A(0, 7) l = [A1, A2, A3, A4, A5, A6]
期望排序结果为:A(-3, -3)、A(0, 7)、A(1, 5)、A(1, 6)、A(2, 5)、A(4, 0),即优先按a升序,a相同时按b升序。
传统嵌套if的实现方式在属性增多时代码会非常繁琐:
def __lt__(self, other): if self.a == other.a: if self.b == other.b: return self.c < other.c return self.b < other.b return self.a < other.a
而尝试的短路逻辑return self.a < other.a or self.b < other.b会导致错误排序,因为它的逻辑不符合"优先按a排序,再按b排序"的规则。
最优解决方案
1. 利用元组比较简化__lt__实现
Python支持元组的按序比较:会先比较第一个元素,相等时再比较第二个,以此类推。直接返回属性元组的比较结果,就能替代嵌套if,代码简洁且可扩展性强。
修改后的类实现:
class A: def __init__(self, a, b): self.a = a self.b = b def __lt__(self, other): # 按a升序,a相等则按b升序,新增属性直接扩展元组即可 return (self.a, self.b) < (other.a, other.b) def __str__(self): return f'{self.__class__.__name__}({self.a}, {self.b})' # 测试排序 l = [A1, A2, A3, A4, A5, A6] l.sort() for obj in l: print(obj)
输出结果完全符合预期:
A(-3, -3) A(0, 7) A(1, 5) A(1, 6) A(2, 5) A(4, 0)
如果后续新增c属性,只需修改为return (self.a, self.b, self.c) < (other.a, other.b, other.c)即可,无需调整嵌套逻辑。
2. 不修改类,使用sorted的key参数
如果不需要给类设置默认排序规则,或者只是临时排序,可以直接在调用sorted时指定key参数,通过lambda表达式提取排序属性元组:
# 无需修改A类的__lt__方法 sorted_l = sorted(l, key=lambda x: (x.a, x.b)) for obj in sorted_l: print(obj)
此方式同样能得到符合预期的排序结果,适合不需要全局默认排序的场景。
为什么短路逻辑会出错
return self.a < other.a or self.b < other.b的逻辑错误在于:它只要a更小或者b更小就返回True,这违背了"优先按a排序"的规则。
比如对比A4(4,0)和A1(1,5):4 > 1但0 < 5,此时表达式会返回True,错误地认为A4小于A1,导致排序结果混乱。
内容的提问来源于stack exchange,提问作者user2300369

