Question about sorted in Python 3.0rc1

J

josh logan

Hello,


I have 2 questions. Say I have this class:

class Player(object):
def __init__(self, fname, lname, score):
self.score = score
self.fname = fname
self.lname = lname
def __cmp__(self, other):
return (-cmp(self.score, other.score) or
cmp(self.lname, other.lname) or
cmp(self.fname, other.fname))
def __repr__(self):
return 'Player(fname={0.fname}, lname={0.lname},
score={0.score})'.format(self)
def __eq__(self, others):
if isinstance(other, Player):
return (self.score == other.score and
self.lname == other.lname and
self.fname == other.fname)
return False
def __ne__(self, others):
return not self.__eq__(others)



fnames = ['Julie', 'Ben', 'Jason', 'David']
lnames = ['Parks', 'Smith']
scores = [100, 95, 95, 130, 58, 74]

import itertools as it

score_iter = it.cycle(scores)

P = [Player(fn, ln, next(score_iter)) for fn in fnames for ln in
lnames]

cmp(P[0], P[1]) # returns -1

sorted(P) # throws TypeError: unorderable types Player() < Player()

The sorted function works when I define __lt__.
I must be misreading the documentation, because I read for the
documentation __cmp__ that it is called if none of the other rich
comparison functions are defined.
Is this a bug in Python 3.0rc1, or am I missing something?


Secondly, say that we suddenly need another sorting order, where we
want to sort by decreasing score and then by DECREASING last name
(instead of increasing last name, defined above). Now that the
comparison function argument is taken away from the sorted builtin,
how do we accomplish this with the "key" parameter?

Thank you
 
A

Arnaud Delobelle

Hello,

I have 2 questions. Say I have this class:

class Player(object):
def __init__(self, fname, lname, score):
self.score = score
self.fname = fname
self.lname = lname
def __cmp__(self, other):
return (-cmp(self.score, other.score) or
cmp(self.lname, other.lname) or
cmp(self.fname, other.fname))
def __repr__(self):
return 'Player(fname={0.fname}, lname={0.lname},
score={0.score})'.format(self)
def __eq__(self, others):
if isinstance(other, Player):
return (self.score == other.score and
self.lname == other.lname and
self.fname == other.fname)
return False
def __ne__(self, others):
return not self.__eq__(others)

fnames = ['Julie', 'Ben', 'Jason', 'David']
lnames = ['Parks', 'Smith']
scores = [100, 95, 95, 130, 58, 74]

import itertools as it

score_iter = it.cycle(scores)

P = [Player(fn, ln, next(score_iter)) for fn in fnames for ln in
lnames]

cmp(P[0], P[1]) # returns -1

sorted(P) # throws TypeError: unorderable types Player() < Player()

The sorted function works when I define __lt__.
I must be misreading the documentation, because I read for the
documentation __cmp__ that it is called if none of the other rich
comparison functions are defined.
Is this a bug in Python 3.0rc1, or am I missing something?

Secondly, say that we suddenly need another sorting order, where we
want to sort by decreasing score and then by DECREASING last name
(instead of increasing last name, defined above). Now that the
comparison function argument is taken away from the sorted builtin,
how do we accomplish this with the "key" parameter?

Thank you

I don't know about __cmp__ but for the second part of the question you
can probably do:

sorted(P, key=lambda p: (p.score, p.lname), reverse=True)

HTH
 
J

josh logan

I have 2 questions. Say I have this class:
class Player(object):
    def __init__(self, fname, lname, score):
        self.score = score
        self.fname = fname
        self.lname = lname
    def __cmp__(self, other):
        return (-cmp(self.score, other.score) or
                cmp(self.lname, other.lname) or
                cmp(self.fname, other.fname))
    def __repr__(self):
        return 'Player(fname={0.fname}, lname={0.lname},
score={0.score})'.format(self)
    def __eq__(self, others):
        if isinstance(other, Player):
            return (self.score == other.score and
                    self.lname == other.lname and
                    self.fname == other.fname)
        return False
    def __ne__(self, others):
        return not self.__eq__(others)
fnames = ['Julie', 'Ben', 'Jason', 'David']
lnames = ['Parks', 'Smith']
scores = [100, 95, 95, 130, 58, 74]
import itertools as it
score_iter = it.cycle(scores)
P = [Player(fn, ln, next(score_iter)) for fn in fnames for ln in
lnames]
cmp(P[0], P[1]) # returns -1
sorted(P) # throws TypeError: unorderable types Player() < Player()
The sorted function works when I define __lt__.
I must be misreading the documentation, because I read for the
documentation __cmp__ that it is called if none of the other rich
comparison functions are defined.
Is this a bug in Python 3.0rc1, or am I missing something?
Secondly, say that we suddenly need another sorting order, where we
want to sort by decreasing score and then by DECREASING last name
(instead of increasing last name, defined above). Now that the
comparison function argument is taken away from the sorted builtin,
how do we accomplish this with the "key" parameter?
Thank you

I don't know about __cmp__ but for the second part of the question you
can probably do:

    sorted(P, key=lambda p: (p.score, p.lname), reverse=True)

HTH

Thank you for the prompt reply. I didn't think my second question
completely through.

A better example would be sorting by increasing last name and
decreasing first name. This would be easy with the sort function
comparator, but I can't see how to do the same with the key argument.
Is the only solution to decorate the Player objects in another class
that has the appropriate __cmp__ function (or whatever is needed) and
then retrieve the Player objects back?
 
P

Peter Otten

josh said:
A better example would be sorting by increasing last name and
decreasing first name. This would be easy with the sort function
comparator, but I can't see how to do the same with the key argument.
Is the only solution to decorate the Player objects in another class
that has the appropriate __cmp__ function (or whatever is needed) and
then retrieve the Player objects back?

Python's sort algorithm is guaranteed to be stable; therefore you can sort
twice:

ordered = sorted(players, key=lambda p: p.fname, reverse=True)
ordered.sort(key=lambda p: p.lname)

Peter
 
S

Sion Arrowsmith

josh logan said:
sorted(P) # throws TypeError: unorderable types Player() < Player()

The sorted function works when I define __lt__.
I must be misreading the documentation, because I read for the
documentation __cmp__ that it is called if none of the other rich
comparison functions are defined.

You're either misreading or forgetting that __eq__ and __ne__,
which you define, are rich comparison functions. __cmp__ will only
be called for a comparison when *none* of the rich comparison
functions are defined, not just the one in question.
 
A

Arnaud Delobelle

Hello,
I have 2 questions. Say I have this class:
class Player(object):
def __init__(self, fname, lname, score):
self.score = score
self.fname = fname
self.lname = lname
def __cmp__(self, other):
return (-cmp(self.score, other.score) or
cmp(self.lname, other.lname) or
cmp(self.fname, other.fname))
def __repr__(self):
return 'Player(fname={0.fname}, lname={0.lname},
score={0.score})'.format(self)
def __eq__(self, others):
if isinstance(other, Player):
return (self.score == other.score and
self.lname == other.lname and
self.fname == other.fname)
return False
def __ne__(self, others):
return not self.__eq__(others)
fnames = ['Julie', 'Ben', 'Jason', 'David']
lnames = ['Parks', 'Smith']
scores = [100, 95, 95, 130, 58, 74]
import itertools as it
score_iter = it.cycle(scores)
P = [Player(fn, ln, next(score_iter)) for fn in fnames for ln in
lnames]
cmp(P[0], P[1]) # returns -1
sorted(P) # throws TypeError: unorderable types Player() < Player()
The sorted function works when I define __lt__.
I must be misreading the documentation, because I read for the
documentation __cmp__ that it is called if none of the other rich
comparison functions are defined.
Is this a bug in Python 3.0rc1, or am I missing something?
Secondly, say that we suddenly need another sorting order, where we
want to sort by decreasing score and then by DECREASING last name
(instead of increasing last name, defined above). Now that the
comparison function argument is taken away from the sorted builtin,
how do we accomplish this with the "key" parameter?
Thank you
I don't know about __cmp__ but for the second part of the question you
can probably do:
sorted(P, key=lambda p: (p.score, p.lname), reverse=True)

Thank you for the prompt reply. I didn't think my second question
completely through.

A better example would be sorting by increasing last name and
decreasing first name. This would be easy with the sort function
comparator, but I can't see how to do the same with the key argument.
Is the only solution to decorate the Player objects in another class
that has the appropriate __cmp__ function (or whatever is needed) and
then retrieve the Player objects back?

You can use the stability of the sorting algorithm as Peter pointed
out. If decorating, you don't need to decorate the Player objects,
but you can decorate the keys instead: e.g

class RevStr(str):
def __lt__(self, other): return str.__lt__(other, self)
def __ge__(self, other): retrn str.__gt__(other, self)

then you can write:

sorted(P, key=lambda p:(p.lname, RevStr(p.fname)))
 
J

josh logan

You're either misreading or forgetting that __eq__ and __ne__,
which you define, are rich comparison functions. __cmp__ will only
be called for a comparison when *none* of the rich comparison
functions are defined, not just the one in question.

--
\S -- (e-mail address removed) --http://www.chaos.org.uk/~sion/
   "Frankly I have no feelings towards penguins one way or the other"
        -- Arthur C. Clarke
   her nu becomeþ se bera eadward ofdun hlæddre heafdes bæce bump bump bump

Hello Sion,

When I don't define the __eq__ and __ne__ comparison functions, the
same unexpected behavior occurs.
 
J

josh logan

What documentation are you referring to, exactly?  The whole __cmp__
thing was supposed to be removed from Python 3, so mention of it
sounds like a documentation bug.


By calling sort twice on the sequence:

lst.sort(key=lambda x: x.score)
lst.sort(key=lambda x: x.last_name, reverse=True)

As much as I like the key argument, I believe it was a mistake to
remove cmp, simply because it was the more general mechanism.
Emulating cmp with key is possible, but it requires creating a bunch
of objects for each sort.  (I'm aware that list.sort caches the
calculated keys, but it still has to create as many of them as there
are list items.)

It looks like __cmp__ is still in the documentation, and it seems to
work somewhat in Python 3.0rc1. Here is the link to the documnetation
http://docs.python.org/dev/3.0/reference/datamodel.html#object.__cmp__
 
T

Terry Reedy

josh logan wrote:

Here is a minimal example showing the problematic behavior.

class Int():
def __init__(self, i):
self.i = i
def __cmp__(self, other):
return cmp(self.i, other.i)

Is = [Int(i) for i in range(8)]
Is.sort() # throws TypeError: unorderable types Int() < Int()
sorted(Is) # ditto, if above not present

The 3.0b2 version of LibRef/ Built-in Types/ Comparisions says
"Instances of a class cannot be ordered with respect to other instances
of the same class, or other types of object, unless the class defines
enough of the methods __cmp__(), __lt__(), __le__(), __gt__(), and
__ge__() (in general, either __cmp__() or both __lt__() and __eq__() are
sufficient, if you want the conventional meanings of the comparison
operators).

The notes for Mutable Sequence .sort() say nothing more. So the
exception appears to be a bug, perhaps left over from when there was a
plan (since aborted) to delete cmp and __cmp__. If the 3.0c1 version of
the docs say the same, and no one says otherwise, I would file a report
on the tracker at bugs.python.org, using the minimal example above.

Terry Jan Reedy
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,756
Messages
2,569,535
Members
45,008
Latest member
obedient dusk

Latest Threads

Top