Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Alternative to `any` that returns the last evaluated object?

Tags:

python

I just wrote a bit of code where I wanted to do:

def foo(container)
    return any((some_obj.attr <= 0 for some_obj in container))

where foo would return the first some_obj where some_obj.attr is zero or less. The alternative, I suppose, would be

def foo(container):
    return next((some_obj for some_obj in container if some_obj.attr <= 0), False)

but that feels very hacky.

I ended up writing it out, but I don't like how deeply nested it got.

def foo(container):
    for some_obj in container:
        if some_obj.attr <= 0:
            return some_obj
    return False

To clarify: container in this case is likely no more than 6 objects (and often 2 objects), though a general case is more useful. I'm also trying to optimize for ease of reading, not for performance.

Is there some better construct than this?

like image 806
Adam Smith Avatar asked May 21 '15 03:05

Adam Smith


2 Answers

Just for fun, to extend Stefan Pochmann's answer to handle obj.attr <= 0, still without needing a lambda:

from operator import attrgetter
from functional import compose

next(filter(compose(0..__ge__, attrgetter('attr')), [3, 2, 1, -1, -2]), False)

If you don't have the functional module (which you probably don't, because the version on PyPI hasn't worked since Python 2.4 or so…) and don't want to search for a modern replacement, you can write compose yourself (and slightly better):

def compose(f, g):
    @functools.wraps(f):
    def wrapper(x):
        return f(g(x))
    return wrapper

About once/year, there's a proposal to add compose to the stdlib, and maybe even give it an infix operator. With @ being added for matrix multiplication, you can guess the latest proposal.* So, if that happens (which it probably won't), you can do this:

from operator import attrgetter

next(filter(0..__ge__ @ attrgetter('attr'), [3, 2, 1, -1, -2]), False)

Now the only thing we need is Haskell-style operator sectioning so we can get rid of the bound method, the .. hack, and the need for an attrgetter function (assuming you consider dot-attribution an operator, which it really isn't, but let's pretend…). Then:

next(filter((<= 0) @ (.attr), [3, 2, 1, -1, -2]), False)

* In fact, it was proposed, twice, during the initial PEP 465 discussion, which is why the PEP mentions, "During discussions of this PEP, a similar suggestion was made to define @ as a general purpose function composition operator, and this suffers from the same problem; functools.compose isn't even useful enough to exist."

like image 123
abarnert Avatar answered Nov 01 '22 14:11

abarnert


next(filter should do it, and here's a funny way to test <= 0:

>>> next(filter((0).__ge__, [3,2,1,-1,-2]), False)
-1

Ha, even tricker:

>>> next(filter(0..__ge__, [3,2,1,-1,-2]), False)
-1

Or, as abarnert pointed out:

>>> next(filter(0 .__ge__, [3,2,1,-1,-2]), False)
-1
like image 36
Stefan Pochmann Avatar answered Nov 01 '22 14:11

Stefan Pochmann