Get the object with the max attribute's value in a list of objects

22,749

max() takes a key parameter, a function that when passed one of the objects returns the value by which to compare them.

Use operator.attrgetter() to get that value:

from operator import attrgetter

max(self.allPartners, key=attrgetter('attrOne'))

This returns the matching object for which that attribute is the maximum. If you wanted to store just that maximum value itself, you have two options:

  • Take the attribute from the returned object:

    max(self.allPartners, key=attrgetter('attrOne')).attrOne
    
  • Pass just the attributes instead to max() with a generator expression:

    max(p.attrOne for p in self.allPartners)
    

If you find that you need to order the One classes in various directions by the same attribute again and again (to find the minimum, maximum, sort them, etc.) you may want to make your class orderable as well.

To do that, you'll need to implement some of the basic customization hooks Python will look for. With some extra trickery, you can get away with just the lower-than and equals operations, and by using the funtools.total_ordering class decorator:

from functools import total_ordering

@total_ordering
class One:
    # ...
    def __lt__(self, other):
        if not isinstance(other, type(self)): return NotImplemented
        return self.attrOne < other.attrOne

    def __eq__(self, other):
        if not isinstance(other, type(self)): return NotImplemented
        return self.attrOne == other.attrOne

Now your One class is orderable, entirely on the basis of attrOne; for the max() function, that means you can drop the key parameter altogether.

Share:
22,749

Related videos on Youtube

Filip
Author by

Filip

Updated on August 05, 2022

Comments

  • Filip
    Filip almost 2 years

    This is the code I written so far, and the point with the program is to read 20 people from a file and then assign them their attributes, then normalise their values from a input given by the user.

    class One:
        def __init__(self):
            self.attrOne = ()
            self.attrTwo = ()
            self.attrThree = ()
            self.attrFour = ()
            self.attrFive= ()
            self.attrSix = ()
            self.attrSeven = ()
            self.attrEight = ()
            self.attrNine = ()
    
    
    class Two:
    
        def __init__(self):
            self.allPersons = []
    
       def importFromList(self, filename): 
           file= open(filename, "rU")
           for line in file:
               partOfList = line.split()                        
               x = Partner() 
               x.attrOne = partOfList[0]
               x.attrTwo = partOfList[1]
               x.attrThree = partOfList[2]
               x.attrFour = partOfList[3]
               x.attrFive = partOfList[4]
               x.attrSix = partOfList[5]
               x.attrSeven = partOfList[6]
               x.attrEight= partOfList[7]
               x.attrNine = partOfList[8]
               self.addPerson(x)
            file.close()
    
    def addPerson(self, x):
        self.allPersons.append(x) 
    

    What I wonder is how to loop through the attributes of the persons that is placed in allPersons list and then compare them against eachother to find out the max value. This is what I tried so far, but I can't get it to work

    def getMaxValue(self): 
        o = One()
        for eachPartner in self.allPartners:
            maxValuesAttrOne = max(O.attrOne))
    

    All help will be appreciated, and I'm open for new solutions, also I imagine the importFromList method is not the most effective one, so if you got any objections I'm willing to listen and learn!

    • abarnert
      abarnert almost 11 years
      Is there a reason you have 9 separate attributes named attrOne through attrNine instead of, say, a single attribute which is a list of 9 values, or a dict mapping 9 names to values?
    • abarnert
      abarnert almost 11 years
      Also, why does class One have 9 attributes all set to an empty tuple, while Partner has 9 attributes with the same names each set to a string? That seems like a recipe for confusion…
    • Filip
      Filip almost 11 years
      The attributes are definitions of a person, for example name, age and wealth. Renamned them before posting here. Is it better to do self.name = name instead of an empty tuple? Also how do you mean by having a dict mapping 9 names to values, we didn't dig to deep on how to use dictionaries in the course I did, but would love an example! @abarnert
    • abarnert
      abarnert almost 11 years
      If you actually have a name that you want to store, certainly it's better to store self.name = name instead of storing an empty tuple and then forgetting the name! But if you don't have a name, using () as an "initializing value" for an attribute meant to hold strings is very weird. Either don't initialize it at all (so there will be no attrOne attribute until you have a real value to store there—which is perfectly fine; you're allowed to add new attributes to objects after __init__), or initialize it to '' or None.
    • abarnert
      abarnert almost 11 years
      As for the dict idea… if these are real attributes like name and age, you probably don't want to do that. When you have a bunch of attributes with names like attrOne and attrTwo, that implies that you're going to be writing code that tries to read an attribute chosen dynamically based on some index or something, and that's almost always a bad idea. It doesn't sound like you have any intention of doing anything like that. So, don't worry about that part.
    • abarnert
      abarnert almost 11 years
      One last question: In your real code, are One and Partner the same class, or one a base of the other, or something like that? Because here, they appear to be completely unrelated, and only coincidentally to have very similar attributes…
    • Filip
      Filip almost 11 years
      Yeah that's correct, must have missed it while writing the code here. In the real code the classes are Partner and Partners. Where partner store the attributes and Partners contains the code like importPartnersFromFile. The file im reading from contains 20 persons but they have the same kind of attributes, like name, age and so on. The point with the code im writing is to find a suitable partner for the user by comparing his choices to the attributes of the partners @abarnet
  • Martijn Pieters
    Martijn Pieters almost 11 years
    That's because there is an error in my answer; it should be attrgetter not itemgetter. Mea Culpa!
  • Martijn Pieters
    Martijn Pieters almost 11 years
    For the record, itemgetter is used for item access. dictionary['somekey'] or alist[1] are examples of item access, and the itemgetter callable is used for those. Attribute access on the other hand is someobject.attrOne, and you'd use attrgetter for that. Trying to use itemgetter anyway would raise a TypeError because your class doesn't define a __getitem__ method...
  • Filip
    Filip almost 11 years
    Tried the itemgetter solution, and I keep getting this error: maxValues = max(self.allPartners, key=itemgetter('age')) TypeError: 'Partner' object is not subscriptable Please note that the names of the classes/methods may differ, sorry. Age is one of the attributes I want to get the max value from. If I understood objects correctly I should now have 20 (the number of persons in the file) partners placed in the allPartners list. For example writing in console. p = Partners() print (p.allPartners[0].age) gives the age of person 1. Ps. Could only edit for 5 minutes.
  • Filip
    Filip almost 11 years
    Attrgetter stopped the error from coming! Though maxValues = max(self.allPartners, key=attrgetter('age')) prints out Tony, the name of person nr 5. While max(self.allPartners, key=attrgetter('wealth')) prints out Victor the name of person 3. Haha i feel so confused right now. :) @Martijn Pieters
  • Martijn Pieters
    Martijn Pieters almost 11 years
    That means that the value for .age is highest for Tony. If .age is a string, that might not be what you expected the highest value to be. '10' is lower than '9' because 1 comes before 9 in the ASCII standard. The age values would have to be integers to meet most peoples expectations of what would be the maximum. :-)
  • Filip
    Filip almost 11 years
    You are correct. Tony is indeed the oldest person. What it does right now is to print position [0] in Tonys line and the same for Victor and attribute ("wealth"). If I want maxValues to store the number instead, shall I change how my def __init__(): is written in class One(class Partner in the real program) as abarnert pointed out in the above comments? This is how the partner Tony looks like in the text file Tony M 59 0,20 130 0,30 1200 0 0,9 M is gender, 59 is age and so on.
  • Martijn Pieters
    Martijn Pieters almost 11 years
    Added your options to my answer. :-)
  • Filip
    Filip almost 11 years
    As a comment to your edited text. ASCII valuing 10 lower than 9 explains some of the previous problems I've had :-) @Martijn Pieters
  • abarnert
    abarnert almost 11 years
    @MartijnPieters: One quick thing; ASCII is a bit misleading here; if the OP is using Python 2.x, it's that 1 comes before 9 in his system charset, which is probably not ASCII; if he's using Python 3.x, it's that 1 comes before 9 in Unicode. Fortunately, Unicode, and every important 8-bit charset that Python can handle properly, put the digits in the same order (and even the same position) as ASCII, but I don't think it should be necessary to know that to understand your answer.