Permalink
Browse files

Add 'key' arg instead of 'pred'.

This makes the key function an actual predicate (a function that returns
a boolean), and uses this to select an element from the given iterable.

It's consistent with how min(), max(), sorted() use key functions to
select/sort elements: min(), max() _never_ return elements that weren't
in the original iterable, and sort does not change the values.

Similarly, I'd expect first() to return the first truthy element, but
its result should always be either an element from the original
iterable, or None.  Certainly never an element that was not in the
original list, as suggested in your example.

Also added unit tests and updated README.
  • Loading branch information...
1 parent 1ce9372 commit 106ee3cf389cf2bb76f2816bf22a845c44275f94 @nvie nvie committed Oct 12, 2012
Showing with 62 additions and 22 deletions.
  1. +11 −10 README.rst
  2. +11 −12 first.py
  3. +40 −0 test_first.py
View
21 README.rst
@@ -3,8 +3,8 @@ first: The function you always missed in Python
*first* is a MIT licensed Python package with a simple function that returns
the first true value from an iterable, or ``None`` if there is none. If you need
-more power, you can also supply a predicate whose result will be returned `iff`_
-true.
+more power, you can also supply a key function that is used to judge the truth
+value of the element.
I’m using the term “true” consistently with Python docs for ``any()`` and
``all()`` — it means that the value evaluates to true like: ``True``, ``1``,
@@ -40,12 +40,12 @@ However, it’s especially useful for dealing with regular expressions in
elif m.re is re2:
print('re2', m.group(1))
-The optional predicate gives you even *more* power. If you want to return the
-square of the first even number from a list, just do the following: ::
+The optional key function gives you even *more* selection power. If you
+want to return the first even number from a list, just do the following::
>>> from first import first
- >>> first([1, 1, 3, 4, 5], lambda x: (x ** 2) if (x % 2) == 0 else False)
- 16
+ >>> first([1, 1, 3, 4, 5], lambda x: x % 2 == 0)
+ 4
Usage
@@ -55,13 +55,14 @@ The package consists of one module consisting of one function::
from first import first
- first(iterable, pred=None)
+ first(iterable, key=None)
This function returns the first element of ``iterable`` that is true if
-``pred`` is ``None``. If there is no true element, ``None`` is returned.
+``key`` is ``None``. If there is no true element, ``None`` is returned.
-If a callable is supplied in ``pred``, the result of ``pred(element)`` is
-returned, if the result it true.
+If a callable is supplied in ``key``, the result of ``key(element)`` is
+used to judge the truth value of the element, but the element itself is
+returned.
*first* has no dependencies and should work with any Python available. Of
course, it works with the awesome `Python 3`_ everybody should be using.
View
23 first.py
@@ -20,12 +20,12 @@
>>> first([0, False, None, [], ()]) is None
True
-It also supports the passing of a predicate whose result will be returned iff
-the result is true:
+It also supports the passing of a key argument to help selecting the first
+match in a more advanced way.
>>> from first import first
->>> first([1, 1, 3, 4, 5], lambda x: (x ** 2) if (x % 2) == 0 else False)
-16
+>>> first([1, 1, 3, 4, 5], lambda x: x % 2 == 0)
+4
:copyright: (c) 2012 by Hynek Schlawack.
:license: MIT, see LICENSE for more details.
@@ -39,7 +39,7 @@
__copyright__ = 'Copyright 2012 Hynek Schlawack'
-def first(iterable, pred=None, default=None):
+def first(iterable, key=None, default=None):
"""
Return first element of `iterable` that evaluates true, else return None.
@@ -56,21 +56,20 @@ def first(iterable, pred=None, default=None):
>>> first([0, False, None, [], ()]) is None
True
- If `pred` is specified, the result of `pred` is returned if the result is
+ If `key` is specified, the result of `key` is returned if the result is
true.
- >>> first([1, 1, 3, 4, 5], lambda x: (x ** 2) if (x % 2) == 0 else False)
- 16
+ >>> first([1, 1, 3, 4, 5], lambda x: x % 2 == 0)
+ 4
"""
- if not pred:
+ if key is None:
for el in iterable:
if el:
return el
else:
for el in iterable:
- rv = pred(el)
- if rv:
- return rv
+ if key(el):
+ return el
return default
View
40 test_first.py
@@ -0,0 +1,40 @@
+import unittest
+from first import first
+
+
+isbool = lambda x: isinstance(x, bool)
+isint = lambda x: isinstance(x, int)
+odd = lambda x: isint(x) and x % 2 != 0
+even = lambda x: isint(x) and x % 2 == 0
+is_meaning_of_life = lambda x: x == 42
+
+
+class TestFirst(unittest.TestCase):
+ def test_empty_iterables(self):
+ s = set()
+ l = []
+ assert first(s) is None
+ assert first(l) is None
+
+ def test_default_value(self):
+ s = set()
+ l = []
+ assert first(s, default=42) == 42
+ assert first(l, default=3.14) == 3.14
+
+ l = [0, False, []]
+ assert first(l, default=3.14) == 3.14
+
+ def test_selection(self):
+ l = [(), 0, False, 3, []]
+
+ assert first(l, default=42) == 3
+ assert first(l, key=isint) == 0
+ assert first(l, key=isbool) is False
+ assert first(l, key=odd) == 3
+ assert first(l, key=even) == 0
+ assert first(l, key=is_meaning_of_life) is None
+
+
+if __name__ == '__main__':
+ unittest.main()

0 comments on commit 106ee3c

Please sign in to comment.