3

I have the following code written in Python 2.6, with a Task class that has an optional due_date:

class Task(models.Model):
    due_date = models.DateField(null = True, blank = True)

    @staticmethod
    def compare_by_due_date(t1, t2):
        return due_date_cmp(t1.due_date, t2.due_date)

def due_date_cmp(t1, t2):
    if t1 is None and t2 is None:
        return 0;
    if t1 is None:
        return 1
    if t2 is None:
        return -1
    return (t1 - t2).days

The reason why I extracted the comparison function outside the class is that I wanted to be able to test it without needing to build Task instances. I use compare_by_due_date in the following code to order tasks by increasing due date, with tasks having no due date at the end of the list:

tasks = Task.objects.all()
tasks.sort(Task.compare_by_due_date)

I understood from this answer on Code Review that I should be able to use keys instead of comparison function? Can you show me how?

1
  • 2
    It's conventional to use 'is' instead of '==' with None. Commented Feb 2, 2011 at 20:12

2 Answers 2

4

You can't compare objects of all different types (such as datetime.date to None) even though 2.x does allow more comparisons between different types than 3.x, so the direct comparison of due_dates won't work.

def due_date_key(t):
  return (t.due_date is None, t.due_date)

tasks.sort(key=due_date_key)

This works by chaining the comparison through a tuple, so later items are only considered if earlier items are equal.

Sign up to request clarification or add additional context in comments.

2 Comments

I get the 'tuple' idea. Thanks! But it does not work here: None gets sorted before datetime.date, and my need is for the reverse...
@XavierNodet: This should sort them later, since False < True for the first item. My previous version tried to be too general, but if only None and datetime.date types are needed, this is simpler.
2

It looks like due_date_cmp sorts a list composed of datetime.date and None objects, placing the Nones at the end of the list.

You could do the same using a key which converts None objects to the maximum possible datetime.date object:

old way (using cmp):

import datetime as dt

def due_date_cmp(t1, t2):
    if t1 == None and t2 == None:
        return 0;
    if t1 == None:
        return 1
    if t2 == None:
        return -1
    return (t1 - t2).days


dates=[dt.date(2000,1,1),None,dt.date(1999,1,1),None,dt.date(2002,1,1)]
dates.sort(cmp=due_date_cmp)
print(dates)
# [datetime.date(1999, 1, 1), datetime.date(2000, 1, 1), datetime.date(2002, 1, 1), None, None]

new way (using key):

def due_date_key(t):
    if t is None:
        return dt.date(dt.MAXYEAR,12,31)
    else:
        return t

dates=[dt.date(2000,1,1),None,dt.date(1999,1,1),None,dt.date(2002,1,1)]
dates.sort(key=due_date_key)
print(dates)

# [datetime.date(1999, 1, 1), datetime.date(2000, 1, 1), datetime.date(2002, 1, 1), None, None]

Thus, you might use due_date_key in your code like this:

import operator
class Task(models.Model):
    @property
    def due_date_key(self):
        due_date=self.due_date
        if due_date is None:
            return dt.date(dt.MAXYEAR,12,31)
        else:
            return due_date

tasks = Task.objects.all()
tasks.sort(key=operator.attrgetter('due_date_key'))

1 Comment

+1 for the way to 'replace None with the largest possible date', and showing how to use operator.attrgetter.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.