62

I have this list of objects which have a x and a y parameter (and some other stuff).

path.nodes = (
    <GSNode x=535.0 y=0.0 GSLINE GSSHARP>,
    <GSNode x=634.0 y=0.0 GSLINE GSSHARP>,
    <GSNode x=377.0 y=706.0 GSLINE GSSHARP>,
    <GSNode x=279.0 y=706.0 GSLINE GSSHARP>,
    <GSNode x=10.0 y=0.0 GSLINE GSSHARP>,
    <GSNode x=110.0 y=0.0 GSLINE GSSHARP>,
    <GSNode x=189.0 y=216.0 GSLINE GSSHARP>,
    <GSNode x=458.0 y=216.0 GSLINE GSSHARP>
)

I need to have the max y of this list. Though, I tried this:

print(max(path.nodes, key=y))

And I get this error:

NameError: name 'y' is not defined

I am kinda new to Python and the docs give me no clue. I think I am doing wrong with the keyword because if iterate through nodes like this:

for node in path.nodes:
    print(node.y)

I'll get the values of y. Could somebody provide me an explanation?

Francisco
  • 10,918
  • 6
  • 34
  • 45
PDXIII
  • 1,086
  • 2
  • 8
  • 17

8 Answers8

109

To get just the maximum value and not the entire object you can use a generator expression:

print(max(node.y for node in path.nodes))
Francisco
  • 10,918
  • 6
  • 34
  • 45
Mark Byers
  • 811,555
  • 193
  • 1,581
  • 1,452
38

There is an important difference for when to use the "Pythonic" style #1 versus lambda style #2:

max(node.y for node in path.nodes)  # (style #1)

versus

max(path.nodes, key=lambda item: item.y)  # (style #2)

If you look carefully you can see that style #1 returns the maximum value for the attribute y while style #2 returns the node that has maximum attribute y. These two are not the same and code usage is important in case you want to iterate over the attribute values or iterate over the objects that holds that attribute.

Example:

class node():
    def __init__(self,x):
        self.x = x
        self.y = self.x + 10

node_lst = [node(1), node(2), node(3), node(4), node(5)]
print([(e.x,e.y) for e in node_lst])

>>> [(1, 11), (2, 12), (3, 13), (4, 14), (5, 15)]

Now:

maxy = max(node.y for node in node_lst)
print(maxy)
>>> 15

max_node = max(node_lst, key=lambda node: node.y)
print(max_node.y)
>>> 15
Francisco
  • 10,918
  • 6
  • 34
  • 45
user-asterix
  • 826
  • 8
  • 12
35

There's a built-in to help with this case.

import operator

print(max(path.nodes, key=operator.attrgetter('y')))

Alternatively:

print(max(path.nodes, key=lambda item: item.y))

Edit: But Mark Byers' answer is most Pythonic.

print(max(node.y for node in path.nodes))
Francisco
  • 10,918
  • 6
  • 34
  • 45
FogleBird
  • 74,300
  • 25
  • 125
  • 131
  • 1
    In my case the most pythonic way is what I was looking for. But in the other examples is there an advantage of the attrgetter() over the lambda style? – PDXIII Oct 25 '12 at 11:58
  • 1
    The `lambda` approach is what I like the most. It connects nicely with `C#` and `JavaScript`. – Sau001 Dec 23 '20 at 15:56
  • Mark Byers' answer is not equivalent to your first two snippets. His answer returns the maximum value of the attribute y, not the node associated with this value. – pigi5 Mar 28 '22 at 22:43
  • 1
    @PDXIII: Two advantages: 1) `lambda` can do anything, so maintainers have to look at it more closely (`lambda item: -item.y` is easily overlooked; `attrgetter` does exactly one thing so maintainers who know of it can speed past it (this is a general advantage of using `operator` and `functools` stuff over `lambda`). 2) In theory, the `attrgetter` can be more efficient (especially if it's reading multiple attributes); in the CPython reference interpreter, it's implemented at the C layer, so in most cases, the bytecode interpreter will not be invoked at all per item. – ShadowRanger Apr 05 '22 at 14:37
5
from operator import attrgetter
print(max(path.nodes, key=attrgetter("y")))
Francisco
  • 10,918
  • 6
  • 34
  • 45
Alexey Kachayev
  • 6,106
  • 27
  • 24
4

It's also possible to implement the __gt__ comparison operator for an object, and than use max without a key function:

class node:
    def __init__(self, y):
        self.y = y
    def __gt__(self, other):
        return self.y > other.y

and than something like:

ls = [node(3), node(5), node(11), node(0)]
print(max(ls).y)

is supposed to output 11.

baz
  • 1,317
  • 15
  • 10
0

y isn't defined as a variable; it's an attribute of individual GSNode objects; you can't use it as a name on its own.

To access the individual attributes you can use something like key=lambda x: x.y or attrgetter() from the operator module.

Wooble
  • 87,717
  • 12
  • 108
  • 131
0

If y is a property attribute then you don't even need to import operator.attrgetter. You can use fget method instead:

my_node = max(path.nodes, key=Node.y.fget)

This will return the Node instance from where to get the max y value is just my_node.y

Georgy
  • 12,464
  • 7
  • 65
  • 73
0

they already answered you, but if you want to get the object who has the max value:

max_val_object = lambda x: max(ob.value for ob in x)
new card
  • 13
  • 3