Skip to content

Prefer list comprehensions over map and filter #971

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 8 additions & 20 deletions docs/writing/structure.rst
Original file line number Diff line number Diff line change
Expand Up @@ -809,16 +809,12 @@ and can be used as a key for a dictionary.

One peculiarity of Python that can surprise beginners is that
strings are immutable. This means that when constructing a string from
its parts, it is much more efficient to accumulate the parts in a list,
which is mutable, and then glue ('join') the parts together when the
full string is needed. One thing to notice, however, is that list
comprehensions are better and faster than constructing a list in a loop
with calls to ``append()``.

One other option is using the map function, which can 'map' a function
('str') to an iterable ('range(20)'). This results in a map object,
which you can then ('join') together just like the other examples.
The map function can be even faster than a list comprehension in some cases.
its parts, appending each part to the string is inefficient because
the entirety of the string is copied on each append.
Instead, it is much more efficient to accumulate the parts in a list,
which is mutable, and then glue (``join``) the parts together when the
full string is needed. List comprehensions are usually the fastest and
most idiomatic way to do this.

**Bad**

Expand All @@ -830,7 +826,7 @@ The map function can be even faster than a list comprehension in some cases.
nums += str(n) # slow and inefficient
print nums

**Good**
**Better**

.. code-block:: python

Expand All @@ -840,20 +836,12 @@ The map function can be even faster than a list comprehension in some cases.
nums.append(str(n))
print "".join(nums) # much more efficient

**Better**

.. code-block:: python

# create a concatenated string from 0 to 19 (e.g. "012..1819")
nums = [str(n) for n in range(20)]
print "".join(nums)

**Best**

.. code-block:: python

# create a concatenated string from 0 to 19 (e.g. "012..1819")
nums = map(str, range(20))
nums = [str(n) for n in range(20)]
print "".join(nums)

One final thing to mention about strings is that using ``join()`` is not always
Expand Down
117 changes: 66 additions & 51 deletions docs/writing/style.rst
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,7 @@ Conventions
Here are some conventions you should follow to make your code easier to read.

Check if a variable equals a constant
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

You don't need to explicitly compare a value to True, or None, or 0 -- you can
just add it to the if statement. See `Truth Value Testing
Expand Down Expand Up @@ -588,80 +588,104 @@ Short Ways to Manipulate Lists

`List comprehensions
<http://docs.python.org/tutorial/datastructures.html#list-comprehensions>`_
provide a powerful, concise way to work with lists. Also, the :py:func:`map` and
:py:func:`filter` functions can perform operations on lists using a different,
more concise syntax.
provide a powerful, concise way to work with lists.

Filtering a list
~~~~~~~~~~~~~~~~
`Generator expressions
<http://docs.python.org/tutorial/classes.html#generator-expressions>`_
follow almost the same syntax as list comprehensions but return a generator
instead of a list.

**Bad**:
Creating a new list requires more work and uses more memory. If you are just going
to loop through the new list, prefer using an iterator instead.

Never remove items from a list while you are iterating through it.
**Bad**:

.. code-block:: python

# Filter elements greater than 4
a = [3, 4, 5]
for i in a:
if i > 4:
a.remove(i)
# needlessly allocates a list of all (gpa, name) entires in memory
valedictorian = max([(student.gpa, student.name) for student in graduates])

Don't make multiple passes through the list.
**Good**:

.. code-block:: python

while i in a:
a.remove(i)
valedictorian = max((student.gpa, student.name) for student in graduates)


Use list comprehensions when you really need to create a second list, for
example if you need to use the result multiple times.


If your logic is too complicated for a short list comprehension or generator
expression, consider using a generator function instead of returning a list.

**Good**:

Python has a few standard ways of filtering lists.
The approach you use depends on:
.. code-block:: python

def make_batches(items, batch_size):
"""
>>> list(make_batches([1, 2, 3, 4, 5], batch_size=3))
[[1, 2, 3], [4, 5]]
"""
current_batch = []
for item in items:
current_batch.append(item)
if len(current_batch) == batch_size:
yield current_batch
current_batch = []
yield current_batch

* Python 2.x vs. 3.x
* Lists vs. iterators
* Possible side effects of modifying the original list

Python 2.x vs. 3.x
::::::::::::::::::
Never use a list comprehension just for its side effects.

Starting with Python 3.0, the :py:func:`filter` function returns an iterator instead of a list.
Wrap it in :py:func:`list` if you truly need a list.
**Bad**:

.. code-block:: python

list(filter(...))
[print(x) for x in seqeunce]

List comprehensions and generator expressions work the same in both 2.x and 3.x (except that comprehensions in 2.x "leak" variables into the enclosing namespace):
**Good**:

* Comprehensions create a new list object.
* Generators iterate over the original list.
.. code-block:: python

The :py:func:`filter` function:
for x in sequence:
print(x)

* in 2.x returns a list (use itertools.ifilter if you want an iterator)
* in 3.x returns an iterator

Lists vs. iterators
:::::::::::::::::::
Filtering a list
~~~~~~~~~~~~~~~~

**Bad**:

Never remove items from a list while you are iterating through it.

.. code-block:: python

# Filter elements greater than 4
a = [3, 4, 5]
for i in a:
if i > 4:
a.remove(i)

Don't make multiple passes through the list.

.. code-block:: python

while i in a:
a.remove(i)

**Good**:

Creating a new list requires more work and uses more memory. If you a just going to loop through the new list, consider using an iterator instead.
Use a list comprehension or generator expression.

.. code-block:: python

# comprehensions create a new list object
filtered_values = [value for value in sequence if value != x]
# Or (2.x)
filtered_values = filter(lambda i: i != x, sequence)

# generators don't create another list
filtered_values = (value for value in sequence if value != x)
# Or (3.x)
filtered_values = filter(lambda i: i != x, sequence)
# Or (2.x)
filtered_values = itertools.ifilter(lambda i: i != x, sequence)



Possible side effects of modifying the original list
Expand All @@ -673,10 +697,6 @@ Modifying the original list can be risky if there are other variables referencin

# replace the contents of the original list
sequence[::] = [value for value in sequence if value != x]
# Or
sequence[::] = (value for value in sequence if value != x)
# Or
sequence[::] = filter(lambda value: value != x, sequence)


Modifying the values in a list
Expand Down Expand Up @@ -705,11 +725,6 @@ It's safer to create a new list object and leave the original alone.

# assign the variable "a" to a new list without changing "b"
a = [i + 3 for i in a]
# Or (Python 2.x):
a = map(lambda i: i + 3, a)
# Or (Python 3.x)
a = list(map(lambda i: i + 3, a))


Use :py:func:`enumerate` keep a count of your place in the list.

Expand Down