Coder Social home page Coder Social logo

first's Introduction

first: The function you always missed in Python

first is an 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 key function that is used to judge the truth value of the element or a default value if None doesn’t fit your use case.

N.B. 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, "foo", or [None]. But not: None, False, [], or 0. In JavaScript, they call this “truthy”.

Project Status

I consider first done. It's short, it works. If it breaks in a future Python release, I will fix it. But other than that, no work on the project is intended.

If you need more functionality, the excellent boltons package contains a first-like function as part of its iterutils module.

Examples

A simple example to get started:

>>> from first import first
>>> first([0, None, False, [], (), 42])
42

However, it’s especially useful for dealing with regular expressions in if/elif/else branches:

import re

from first import first


re1 = re.compile('b(.*)')
re2 = re.compile('a(.*)')

m = first(regexp.match('abc') for regexp in [re1, re2])
if not m:
   print('no match!')
elif m.re is re1:
   print('re1', m.group(1))
elif m.re is re2:
   print('re2', m.group(1))

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], key=lambda x: x % 2 == 0)
4

default on the other hand allows you to specify a value that is returned if none of the elements is true:

>>> from first import first
>>> first([0, None, False, [], ()], default=42)
42

Usage

The package consists of one module consisting of one function:

from first import first

first(iterable, default=None, key=None)

This function returns the first element of iterable that is true if key is None. If there is no true element, the value of default is returned, which is None by default.

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.

Alternatives

first brings nothing to the table that wasn’t possible before. However the existing solutions aren’t very idiomatic for such a common and simple problem.

The following constructs are equivalent to first(seq) and work since Python 2.6:

next(itertools.ifilter(None, seq), None)
next(itertools.ifilter(bool, seq), None)
next((x for x in seq if x), None)

None of them is as pretty as I’d like them to be. The re example from above would look like the following:

next(itertools.ifilter(None, (regexp.match('abc') for regexp in [re1, re2])), None)
next((regexp.match('abc') for regexp in [re1, re2] if regexp.match('abc')), None)
next((match for match in itertools.imap(
    operator.methodcaller('match', 'abc'), [re1, re2]) if match), None)

Note that in the second case you have to call regexp.match() twice. The third example "fixes" that problem but also summons Cthulhu.

For comparison, one more time the first-version:

first(regexp.match('abc') for regexp in [re1, re2])

Idiomatic, clear and readable. Pythonic. :)

Background

The idea for first goes back to a discussion I had with Łukasz Langa about how the re example above is painful in Python. We figured such a function is missing Python, however it’s rather unlikely we’d get it in and even if, it wouldn’t get in before 3.4 anyway, which is years away as of yours truly is writing this.

So I decided to release it as a package for now. If it proves popular enough, it may even make it into Python’s stdlib in the end.

first's People

Contributors

bitdeli-chef avatar bobotig avatar hroncok avatar hynek avatar jd avatar jdufresne avatar kennethlove avatar nvie avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

first's Issues

Simplify the logic using key parameter

The current declaration is

    def first(iterable, default=None, key=None):

Why don't you rewrite it to be

    def first(iterable, default=None, key=lambda x: x):
        for el in iterable:
            if key(el):
                return el
        return default

Add a "default" parameter

While I don't think the "pred" parameter is useful, adding a "default=None" optional parameter would be very handy (for the same reasons it is handy in various other getter operations)

Add "first_n" function?

Hi @hynek,

What is your opinion on adding first_n function into this library?

>>> seq = [None, 2, None, None, None, 5, None, None, 8]
>>> first_n(seq, 2)
[2, 5]

>>> first_n(range(10), 3, key=lambda x: x % 2 == 0)
[0, 2, 4]

It is convenient to expect such thing in this library.

Current readable alternative (Variant 1):

n = 3
seq = range(10)
result = []
for item in seq:
    if len(result) == n:
        break
    if item % 2 == 0:
        result.append(item)
# result = [0, 2, 4]

Not that readable alternative (Variant 2):

from itertools import islice
n = 3
seq = range(10)
list(islice(filter(lambda item: item % 2 == 0, seq), 0, n))
# [0, 2, 4]

There is also a fancy implementation from SO (Variant 3):

from itertools import count
n = 3
seq = range(10)
list(filter(lambda item, c=count(): item % 2 == 0 and next(c) < n, seq))
# [0, 2, 4]

As you can see, all of those alternatives above are ugly and it would be great to have a thing that is as natural as first, but for n elements.

As for possible implementation, I like the idea in variant 2:

def first_n(iterable, n, default=None, key=None):
    result = list(islice(filter(key, iterable), 0, n))
    if not result and default is not None:
        return default
    return result
  • return result if it is non-falsy
  • return default if result is falsy and default is given
  • return empty list if result is falsy and no default is given

Examples:

>>> none_seq = [None, None, None]
>>> first_n(none_seq, 2)
[]

>>> first_n(none_seq, 2, default='default_value')
'default_value'

>>> seq2 = [None, None, 1, None, "hi", None, 2]
>>> first_n(seq2, 2)
[1, 'hi']

Please, let me know what you think. :)

Create a release on github

Currently packaging this library for Arch Linux because it's in the dependency chain of pipenv. Our packaging guidelines requires tests to be ran, but the pypi release does not contain them. It would be great if you could make a github release for 2.0.1

“pip install first” fails

pip installs the egg, but the first module/script is nowhere to be found so “from first import first” fails.

Python 2.7 on Linux.

Distribute testsuite in pypi sdist archive

If one downloads source from pypi it is not possible to execute tests and validate it the package is okay.
Could you please tweak MANIFEST.in to include test_first.py?

Rename pred to key

I believe key is the typical argname for stuff like this in Python. Take a look at how min(), max(), and sorted() also take a key argument. This is completely the same, so I think it's better to stick to known terminology.

(We might keep the pred arg for backwards-compatibility reasons.)

Replace the snark with an explanation of the alternatives

If you're going to make the case for inclusion in itertools, it's better to include a "compare-and-contrast" with the existing alternatives than it is to include snarky comments (although the comment about 3.4 being a long way away is a valid one)

The main competitors are:

next(filter(None, seq))
next(filter(bool, seq))
next (x for x in seq if x)

All of these have several problems:

  1. They're not particularly obvious (especially the filter-with-no-predicate trick)
  2. The don't deal gracefully with the "empty iterable" case (they throw StopIteration)
  3. next() is only available on 2.6+

The genexp version also repeats the iteration variable name 3 times!

re example in readme appears to be incorrect

The example in the readme will fail with an AttributeError in the case where neither regex matches. The check for "m is None" needs to be first, with the else clause unused.

The "pred" parameter is ambiguous and unnecessary

Given the current definition of the pred parameter, the following are equivalent:

x = first(seq, pred)
x = first(map(pred, seq))

The most likely alternative interpretation, returning the first value for which pred is true, also seems pointless, since it would be equivalent to:

x = first(filter(pred, seq))

Since it's entirely unclear whether map or filter is the more appropriate interpretation (I was quite surprised to discover you had chosen map), it seems more appropriate to make people use the appropriate builtin.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.