any() function in Python with a callback
Asked Answered
P

8

92

The Python standard library defines an any() function that

Return True if any element of the iterable is true. If the iterable is empty, return False.

It checks only if the elements evaluate to True. What I want it to be able so specify a callback to tell if an element fits the bill like:

any([1, 2, 'joe'], lambda e: isinstance(e, int) and e > 0)
Purr answered 6/1, 2010 at 11:39 Comment(1)
If you want, you can always use any(map(lambda:..., [...])) but using a generator comprehension is more idiomatic.Tubb
P
158

How about:

>>> any(isinstance(e, int) and e > 0 for e in [1,2,'joe'])
True

It also works with all() of course:

>>> all(isinstance(e, int) and e > 0 for e in [1,2,'joe'])
False
Penates answered 6/1, 2010 at 11:43 Comment(2)
Isn't the problem of this type of any function that it will first create a whole list of booleans and then checks if one 1 true? isntead of stopping after found the first valid instance?Capita
@JoelHarkes As long as you don't wrap the iterable in [] or list(), it should use a generator and work as desired.Meryl
M
22

any function returns True when any condition is True.

>>> any(isinstance(e, int) and e > 0 for e in [0 ,0, 1])
True # Returns True because 1 is greater than 0.


>>> any(isinstance(e, int) and e > 0 for e in [0 ,0, 0])
False # Returns False because not a single condition is True.

Actually,the concept of any function is brought from Lisp or you can say from the function programming approach. There is another function which is just opposite to it is all

>>> all(isinstance(e, int) and e > 0 for e in [1, 33, 22])
True # Returns True when all the condition satisfies.

>>> all(isinstance(e, int) and e > 0 for e in [1, 0, 1])
False # Returns False when a single condition fails.

These two functions are really cool when used properly.

Metaphase answered 6/1, 2010 at 11:54 Comment(0)
N
15

You should use a "generator expression" - that is, a language construct that can consume iterators and apply filter and expressions on then on a single line:

For example (i ** 2 for i in xrange(10)) is a generator for the square of the first 10 natural numbers (0 to 9)

They also allow an "if" clause to filter the itens on the "for" clause, so for your example you can use:

any (e for e in [1, 2, 'joe'] if isinstance(e, int) and e > 0)
Nettlesome answered 6/1, 2010 at 11:45 Comment(1)
Thanks for calling out the generator since I think that is what makes it the most like a lambda version (in terms of not having to process the entire list if an earlier item is false). Also, nice to know we can leave off parens of a generator if it is the only parameter. Somehow missed that..Hiltner
B
9

Slight improvement to Antoine P's answer

>>> any(type(e) is int for e in [1,2,'joe'])
True

For all()

>>> all(type(e) is int for e in [1,2,'joe'])
False
Bully answered 14/3, 2014 at 2:20 Comment(0)
H
6

While the others gave good Pythonic answers (I'd just use the accepted answer in most cases), I just wanted to point out how easy it is to make your own utility function to do this yourself if you really prefer it:

def any_lambda(iterable, function):
  return any(function(i) for i in iterable)

In [1]: any_lambda([1, 2, 'joe'], lambda e: isinstance(e, int) and e > 0
Out[1]: True
In [2]: any_lambda([-1, '2', 'joe'], lambda e: isinstance(e, int) and e > 0)
Out[2]: False

I think I'd at least define it with the function parameter first though, since that'd more closely match existing built-in functions like map() and filter():

def any_lambda(function, iterable):
  return any(function(i) for i in iterable)
Hiltner answered 8/11, 2013 at 20:43 Comment(0)
T
5

You can use a combination of any and map if you really want to keep your lambda notation like so :

any(map(lambda e: isinstance(e, int) and e > 0, [1, 2, 'joe']))

But it is better to use a generator expression because it will not build the whole list twice.

Telephony answered 9/2, 2015 at 16:2 Comment(0)
O
4

filter can work, plus it returns you the matching elements

>>> filter(lambda e: isinstance(e, int) and e > 0, [1,2,'joe'])
[1, 2]
Oahu answered 6/1, 2010 at 16:48 Comment(1)
While this is workable (since an empty list is False), it has several disadvantages over using any. First, it will iterate over the entire list (even if the first element is False) and it will copy the True items over to a new list, increasing time and memory. Lastly, if all you're doing is checking if any of the items match something, it makes your intentions less clear. If you need to use only the matching items immediately after, I agree it could be useful (consider itertools.ifilter depending on how you're using the results).Hiltner
I
4

If you really want to inline a lambda in any() you can do this:

>>> any((lambda: isinstance(e, int))() for e in [1,2,'joe'])
True
>>> any((lambda: isinstance(e, int))() for e in ['joe'])
False

You just have to wrap up the unnamed lambda and ensure it is invoked on each pass by appending the ()

The advantage here is that you still get to take advantage of short circuiting the evaluation of any when you hit the first int

Interpolate answered 1/8, 2017 at 22:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.