I have a function that sorts repeatedly based on a list of tuples of attributes and reversed booleans, like so:
def multisort(lst, sorting):
for attr, reverse in reversed(sorting):
lst.sort(key=operator.attrgetter(attr), reverse=reverse)
return lst
Example input to sorting var would be something like [('attr_1', True), ('attr_2', False)]
.
I'm upgrading code to Py3 and this no longer works because occasionally the attribute values are None (can't compare NoneType to non-NoneTypes). There's lots of solutions on stackoverflow for this type of problem that recommend swapping out attrgetter for a lambda function like lambda x: (getattr(x, attr) is None, getattr(x, attr))
.
Unfortunately, this doesn't work for me because the attributes I'm sorting on can have dots in them, e.g. 'attr_1.sub_attr_1'. operator.attrgetter supports this, but of course native getattr does not.
Any suggestions on how I can either write a wrapper around attrgetter to handle this or write a custom key function that would work here? Thanks in advance.
You can use an alternative getter that will return a key function that will generate tuples (value is not None, value)
(if you want None
to come first, which was the case with Python 2 where it is less than anything).
import operator
def none_aware_attrgetter(attr):
getter = operator.attrgetter(attr)
def key_func(item):
value = getter(item)
return (value is not None, value)
return key_func
def multisort(lst, sorting):
for attr, reverse in reversed(sorting):
lst.sort(key=none_aware_attrgetter(attr), reverse=reverse)
return lst
A sample run:
class C:
def __repr__(self):
return f'<a1:{self.a1}, a2.s: {self.a2.s}>'
a = C()
b = C()
c = C()
a.a1 = 10
b.a1 = None
c.a1 = 0
a.a2 = C()
a.a2.s = 10
b.a2 = C()
b.a2.s = None
c.a2 = C()
c.a2.s = 0
lst = [a, b, c]
print(multisort(lst, [('a1', False)]))
# [<a1:None, a2.s: None>, <a1:0, a2.s: 0>, <a1:10, a2.s: 10>]
print(multisort(lst, [('a2.s', True)]))
# [<a1:10, a2.s: 10>, <a1:0, a2.s: 0>, <a1:None, a2.s: None>]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With