我有一堂课,看起来或多或少像这样:
class Something():
def __init__(self,a=None,b=None):
self.a = a
self.b = b
我希望能够将其排序在一个列表中,通常我只会实现这样的方法:
def __lt__(self,other):
return (self.a, self.b) < (other.a, other.b)
但这将在以下情况下引发错误:
sort([Something(1,None),Something(1,1)])
我想要将None
的值视为大于或跟随输出的值:
[Something(1,1),Something(1,None)]
我想到的第一件事是将__lt__
更改为:
def __lt__(self,other):
if self.a and other.a:
if self.a != other.a:
return self.a < other.a
elif self.a is None:
return True
elif other.a is None:
return False
if self.b and other.b:
if self.b != other.b:
return self.b < other.b
elif self.b is None:
return True
return False
这会给我正确的结果,但是它的丑陋和python通常有一个更简单的方法,而且我真的不想为我在对整个类进行排序时使用的每个变量都这样做(从这里省略以使问题更清晰)。
那么解决这个问题的python方式是什么?
我也尝试了以下方法,但我认为有可能做得更好:
这将:
def __lt__(self,other):
sorting_attributes = ['a', 'b']
for attribute in sorting_attributes:
self_value = getattr(self,attribute)
other_value = getattr(other,attribute)
if self_value and other_value:
if self_value != other_value:
return self_value < other_value
elif self_value is None:
return True
elif self_value is None:
return False
真正尝试内部化Pyhton禅,我知道我的代码很丑陋,所以我该如何解决?
答案 0 :(得分:1)
一种简单的方法是将None
转换为无穷大,即float('inf')
:
def __lt__(self, other):
def convert(i):
return float('inf') if i is None else i
return [convert(i) for i in (self.a, self.b)] < [convert(i) for i in (other.a, other.b)]
答案 1 :(得分:1)
后来我想到了一个完全不同的设计(由于它与众不同,因此应该单独进行评估,因此另行发布):
将所有属性映射到tuple
,其中每个tuple
的第一个元素是一个基于属性bool
的{{1}},第二个元素是属性值本身。 None
/非None
的不匹配会在表示None
的{{1}}上发生短路,从而阻止bool
,其他一切都将落到比较好类型:
None
在没有TypeError
/非def __lt__(self, other):
def _key(attr):
# Use attr is not None to make None less than everything, is None for greater
return (attr is None, attr)
return (_key(self.a), _key(self.b)) < (_key(other.a), _key(other.b))
对的情况下,可能比my other solution慢一点,但是代码要简单得多。它还具有在出现None
/ non-None
以外的不匹配类型时继续引发TypeError
的优点,而不是潜在的行为异常。 即使在通常情况下它速度稍慢,我也绝对将其称为Pythonic解决方案。
答案 2 :(得分:0)
一般情况下的解决方案(可能没有便利的“比任何值大”的解决方案,并且您不希望代码随着属性数量的增加而变得更加复杂),但仍能以最快的速度运行在没有None
值的假定常见情况下,请尽可能使用。它确实假设TypeError
意味着涉及None
,所以如果您除了None
之外还可能存在类型不匹配的情况,这会变得更加复杂,但是坦率地说,像这样的类设计对于沉思。这适用于具有两个或多个键的任何情况(因此attrgetter
返回一个tuple
),只需要更改用于构造attrgetter
的名称即可添加或删除要比较的字段。>
def __lt__(self, other, _key=operator.attrgetter('a', 'b')):
# Get the keys once for both inputs efficiently (avoids repeated lookup)
sattrs = _key(self)
oattrs = _key(other)
try:
return sattrs < oattrs # Fast path for no Nones or only paired Nones
except TypeError:
for sattr, oattr in zip(sattrs, oattrs):
# Only care if exactly one is None, because until then, must be equal, or TypeError
# wouldn't occur as we would have short-circuited
if (sattr is None) ^ (oattr is None):
# Exactly one is None, so if it's the right side, self is lesser
return oattr is None
# TypeError implied we should see a mismatch, so assert this to be sure
# we didn't have a non-None related type mismatch
assert False, "TypeError raised, but no None/non-None pair seen
该设计的一个有用功能是,在任何情况下都不会多次对任何给定属性调用丰富的比较;在快速路径上失败的尝试证明必须存在 (假设类型的不变量是兼容的或None
金)是零个或多个属性对值相等,后跟None
/非None
不匹配。由于我们关心的所有事情都是已知的相等或None
/非None
不匹配,因此我们无需再次调用可能昂贵的丰富比较,我们只需要进行便宜的身份测试即可找到{{1 }} /非None
不匹配,然后根据None
的哪一侧返回。