Get the object with the max attribute's value in a list of objects
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.
Related videos on Youtube
Filip
Updated on August 05, 2022Comments
-
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 almost 11 yearsIs there a reason you have 9 separate attributes named
attrOne
throughattrNine
instead of, say, a single attribute which is a list of 9 values, or a dict mapping 9 names to values? -
abarnert almost 11 yearsAlso, why does class
One
have 9 attributes all set to an empty tuple, whilePartner
has 9 attributes with the same names each set to a string? That seems like a recipe for confusion… -
Filip almost 11 yearsThe 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 almost 11 yearsIf 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 noattrOne
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''
orNone
. -
abarnert almost 11 yearsAs for the
dict
idea… if these are real attributes likename
andage
, you probably don't want to do that. When you have a bunch of attributes with names likeattrOne
andattrTwo
, 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 almost 11 yearsOne last question: In your real code, are
One
andPartner
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 almost 11 yearsYeah 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 almost 11 yearsThat's because there is an error in my answer; it should be
attrgetter
notitemgetter
. Mea Culpa! -
Martijn Pieters almost 11 yearsFor the record,
itemgetter
is used for item access.dictionary['somekey']
oralist[1]
are examples of item access, and theitemgetter
callable is used for those. Attribute access on the other hand issomeobject.attrOne
, and you'd useattrgetter
for that. Trying to useitemgetter
anyway would raise aTypeError
because your class doesn't define a__getitem__
method... -
Filip almost 11 yearsTried 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 almost 11 yearsAttrgetter 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 almost 11 yearsThat means that the value for
.age
is highest forTony
. If.age
is a string, that might not be what you expected the highest value to be.'10'
is lower than'9'
because1
comes before9
in the ASCII standard. The age values would have to be integers to meet most peoples expectations of what would be the maximum. :-) -
Filip almost 11 yearsYou 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 almost 11 yearsAdded your options to my answer. :-)
-
Filip almost 11 yearsAs a comment to your edited text. ASCII valuing 10 lower than 9 explains some of the previous problems I've had :-) @Martijn Pieters
-
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 before9
in his system charset, which is probably not ASCII; if he's using Python 3.x, it's that1
comes before9
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.