I want to sort list of objects based on user input where I allow to sort some columns in ASC order and others in DESC order (that fields are strings/numbers - but with numbers there is no problem).
Example class and list of objects looks like this:
from pydantic import BaseModel
class Person(BaseModel):
firstname: str
lastname: str
weight: float
people = [
Person(firstname="John", lastname="Snow", weight=100.1),
Person(firstname="Matthew", lastname="Smith", weight=98.7),
# And more...
]
So the problem is that I want to sort objects by first name ASC, last name DESC (and other fields also DESC or ASC).
I tried to do it like people.sort(lambda person: (-person.firstname, person.lastname))
But I got TypeError: bad operand type for unary -: 'str'
.
CodePudding user response:
To sort with any arbitrary ordering of the attributes, it's best to define the magic methods __lt__
, __le__
, __gt__
, __ge__
, and __eq__
g. With funtools.total_ordering
decorator, you just need to define any of the 1st four and __eq__
Since __eq__
is already defined in BaseModel
, you just need one of __lt__
, __le__
, __gt__
, __ge__
.
For example with __lt__
from pydantic import BaseModel
from functools import total_ordering
@total_ordering
class Person(BaseModel):
firstname: str
lastname: str
weight: float
def __lt__(self, other):
# first sort firstname in ascending order
less_than = (self.firstname < other.firstname)
# if firstname is equal, sort by lastname in descending order
less_than |= ((self.firstname == other.firstname) & (self.lastname > other.lastname))
# if both firstname and last are equal, sort by weight in ascending order
less_than |= ((self.firstname == other.firstname) & (self.lastname == other.lastname) & (self.weight < other.weight))
return less_than
Example running the sort:
>>> people = [
Person(firstname="John", lastname="Snow", weight=100.1),
Person(firstname="Matthew", lastname="Smith", weight=98.7),
Person(firstname="Matthew", lastname="Adam", weight=98.7),
Person(firstname="Matthew", lastname="Smith", weight=100),
]
>>> people.sort()
>>> people
[Person(firstname='John', lastname='Snow', weight=100.1),
Person(firstname='Matthew', lastname='Smith', weight=98.7),
Person(firstname='Matthew', lastname='Smith', weight=100.0),
Person(firstname='Matthew', lastname='Adam', weight=98.7)]
CodePudding user response:
from pydantic import BaseModel
class NegatedableStr(str):
def __neg__(self):
return list(map(lambda c: -ord(c), self))
class Person(BaseModel):
firstname: str
lastname: str
weight: float
def __init__(self, **kwargs):
for key, val in kwargs.items():
if isinstance(val, str):
kwargs[key] = NegatedableStr(val)
super().__init__(**kwargs)
people = [
Person(firstname="John", lastname="Snow", weight=100.1),
Person(firstname="Matthew", lastname="Smith", weight=98.7),
]
people.sort(key=lambda person: (
-person.firstname,
person.lastname,
person.weight,
))
print(*people, sep='\n')
Output:
firstname='Matthew' lastname='Smith' weight=98.7
firstname='John' lastname='Snow' weight=100.1